Repository: pktgen/Pktgen-DPDK Branch: main Commit: f5f4ac0e84c7 Files: 305 Total size: 1.7 MB Directory structure: gitextract_j5vqx0kd/ ├── .clang-format ├── .claude/ │ └── settings.local.json ├── .editorconfig ├── .githooks/ │ └── pre-commit ├── .github/ │ └── workflows/ │ ├── clang-format.yml │ ├── doc.yml │ └── markdownlint.yml ├── .gitignore ├── .markdownlint.yml ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── 2-ports ├── 2-vf-ports ├── CLAUDE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.TXT ├── CONTRIBUTING.md ├── INSTALL.md ├── LICENSE ├── Makefile ├── Pktgen.lua ├── README.md ├── VERSION ├── app/ │ ├── .gitignore │ ├── cli-functions.c │ ├── cli-functions.h │ ├── l2p.c │ ├── l2p.h │ ├── lpktgenlib.c │ ├── lpktgenlib.h │ ├── meson.build │ ├── pktgen-arp.c │ ├── pktgen-arp.h │ ├── pktgen-capture.c │ ├── pktgen-capture.h │ ├── pktgen-cmds.c │ ├── pktgen-cmds.h │ ├── pktgen-constants.h │ ├── pktgen-cpu.c │ ├── pktgen-cpu.h │ ├── pktgen-display.c │ ├── pktgen-display.h │ ├── pktgen-dump.c │ ├── pktgen-dump.h │ ├── pktgen-ether.c │ ├── pktgen-ether.h │ ├── pktgen-gre.c │ ├── pktgen-gre.h │ ├── pktgen-gtpu.c │ ├── pktgen-gtpu.h │ ├── pktgen-ipv4.c │ ├── pktgen-ipv4.h │ ├── pktgen-ipv6.c │ ├── pktgen-ipv6.h │ ├── pktgen-latency.c │ ├── pktgen-latency.h │ ├── pktgen-log.c │ ├── pktgen-log.h │ ├── pktgen-main.c │ ├── pktgen-main.h │ ├── pktgen-pcap.c │ ├── pktgen-pcap.h │ ├── pktgen-port-cfg.c │ ├── pktgen-port-cfg.h │ ├── pktgen-random.c │ ├── pktgen-random.h │ ├── pktgen-range.c │ ├── pktgen-range.h │ ├── pktgen-seq.c │ ├── pktgen-seq.h │ ├── pktgen-stats.c │ ├── pktgen-stats.h │ ├── pktgen-sys.c │ ├── pktgen-sys.h │ ├── pktgen-tcp.c │ ├── pktgen-tcp.h │ ├── pktgen-txbuff.h │ ├── pktgen-udp.c │ ├── pktgen-udp.h │ ├── pktgen-version.h │ ├── pktgen-vlan.c │ ├── pktgen-vlan.h │ ├── pktgen.c │ ├── pktgen.h │ ├── xorshift64star.c │ └── xorshift64star.h ├── cfg/ │ ├── 2-ports.cfg │ ├── 2-vf-ports.cfg │ ├── bond.cfg │ ├── client_memif.cfg │ ├── client_mif.cfg │ ├── cnet-fwd-2.cfg │ ├── cnet-fwd.cfg │ ├── dapi-l3fwd.cfg │ ├── default-100G.cfg │ ├── default-gui.cfg │ ├── default.cfg │ ├── dfs.cfg │ ├── dnet-echo.cfg │ ├── four-ports.cfg │ ├── half-bond.cfg │ ├── hs-fwd.cfg │ ├── ioat.cfg │ ├── lat.cfg │ ├── lb-fwd.cfg │ ├── many-cores.cfg │ ├── multi-port.cfg │ ├── one-port.cfg │ ├── pcap.cfg │ ├── pdump.cfg │ ├── pktgen-1.cfg │ ├── pktgen-2.cfg │ ├── server_memif.cfg │ ├── server_mif.cfg │ ├── socket.cfg │ ├── two-ports-shared.cfg │ ├── two-ports.cfg │ ├── tx_perf.cfg │ ├── vfio-fwd.cfg │ ├── xdp-100.cfg │ ├── xdp-40.cfg │ └── xl710.cfg ├── docs/ │ ├── LUA_API.md │ ├── QUICKSTART.md │ ├── STYLE.md │ ├── api/ │ │ ├── doxy-api-index.md │ │ ├── doxy-api.conf.in │ │ ├── doxy-html-custom.sh │ │ ├── generate_doxygen.sh │ │ ├── generate_examples.sh │ │ └── meson.build │ ├── meson.build │ └── source/ │ ├── changes.rst │ ├── cli_design.rst │ ├── commands.rst │ ├── conf.py │ ├── contents.rst │ ├── copyright.rst │ ├── custom.css │ ├── getting_started.rst │ ├── index.rst │ ├── license.rst │ ├── lua.rst │ ├── meson.build │ ├── running.rst │ ├── scripts.rst │ ├── socket.rst │ ├── usage_eal.rst │ └── usage_pktgen.rst ├── examples/ │ ├── meson.build │ └── pktperf/ │ ├── README.md │ ├── meson.build │ ├── parse.c │ ├── pktperf.c │ ├── pktperf.h │ ├── port.c │ ├── stats.c │ └── utils.c ├── lib/ │ ├── cli/ │ │ ├── DESIGN.md │ │ ├── README │ │ ├── cli.c │ │ ├── cli.h │ │ ├── cli.rst │ │ ├── cli_auto_complete.c │ │ ├── cli_auto_complete.h │ │ ├── cli_cmap.c │ │ ├── cli_cmap.h │ │ ├── cli_cmds.c │ │ ├── cli_cmds.h │ │ ├── cli_common.h │ │ ├── cli_env.c │ │ ├── cli_env.h │ │ ├── cli_file.c │ │ ├── cli_file.h │ │ ├── cli_gapbuf.c │ │ ├── cli_gapbuf.h │ │ ├── cli_help.c │ │ ├── cli_help.h │ │ ├── cli_history.c │ │ ├── cli_history.h │ │ ├── cli_input.c │ │ ├── cli_input.h │ │ ├── cli_lib.rst │ │ ├── cli_map.c │ │ ├── cli_map.h │ │ ├── cli_scrn.c │ │ ├── cli_scrn.h │ │ ├── cli_search.c │ │ ├── cli_search.h │ │ ├── cli_vt100.c │ │ └── meson.build │ ├── common/ │ │ ├── cksum.c │ │ ├── cksum.h │ │ ├── cmdline_parse_args.c │ │ ├── cmdline_parse_args.h │ │ ├── copyright_info.c │ │ ├── copyright_info.h │ │ ├── coreinfo.c │ │ ├── coreinfo.h │ │ ├── lscpu.c │ │ ├── lscpu.h │ │ ├── meson.build │ │ ├── pg_compat.h │ │ ├── pg_delay.h │ │ ├── pg_ether.h │ │ ├── pg_inet.c │ │ ├── pg_inet.h │ │ ├── pg_strings.c │ │ ├── pg_strings.h │ │ ├── port_config.c │ │ ├── port_config.h │ │ ├── utils.c │ │ └── utils.h │ ├── hmap/ │ │ ├── README.md │ │ ├── hmap.c │ │ ├── hmap.h │ │ ├── hmap_helper.h │ │ ├── hmap_log.h │ │ └── meson.build │ ├── lua/ │ │ ├── lua_config.c │ │ ├── lua_config.h │ │ ├── lua_dapi.c │ │ ├── lua_dapi.h │ │ ├── lua_dpdk.c │ │ ├── lua_dpdk.h │ │ ├── lua_pktmbuf.c │ │ ├── lua_pktmbuf.h │ │ ├── lua_socket.c │ │ ├── lua_socket.h │ │ ├── lua_stdio.c │ │ ├── lua_stdio.h │ │ ├── lua_utils.c │ │ ├── lua_utils.h │ │ ├── lua_vec.c │ │ ├── lua_vec.h │ │ └── meson.build │ ├── meson.build │ ├── plugin/ │ │ ├── meson.build │ │ ├── plugin.c │ │ └── plugin.h │ ├── utils/ │ │ ├── _atoip.c │ │ ├── _atoip.h │ │ ├── heap.c │ │ ├── heap.h │ │ ├── inet_pton.c │ │ ├── meson.build │ │ ├── parson_json.c │ │ ├── parson_json.h │ │ ├── portlist.c │ │ └── portlist.h │ └── vec/ │ ├── meson.build │ ├── vec.c │ └── vec.h ├── meson.build ├── meson_options.txt ├── pcap/ │ ├── big.pcap │ ├── gtpv1-u-1024.pcap │ ├── large.pcap │ ├── test1.pcap │ └── traffic_sample.pcap ├── scripts/ │ ├── latency-samples.lua │ ├── latency.lua │ ├── port_stats_dump.lua │ ├── rfc2544.lua │ ├── rfc2544_tput_test.lua │ └── traffic-profile.lua ├── style/ │ ├── call_GNU_Indent.pro │ ├── call_GNU_Indent.sh │ ├── call_Uncrustify.cfg │ └── call_Uncrustify.sh ├── test/ │ ├── dump.lua │ ├── generate-sequences.sh │ ├── gtpu-range.lua │ ├── hello-world.lua │ ├── info.lua │ ├── mac.pkt.3.4.ip-range-gen.txt │ ├── mac.pkt.sequences.txt │ ├── main.lua │ ├── port_info.lua │ ├── portstats.lua │ ├── portstats_with_delay.lua │ ├── range.lua │ ├── screen_off.lua │ ├── set_gtpu_seq.lua │ ├── set_seq.lua │ ├── simple_range.lua │ ├── test.lua │ ├── test1.lua │ ├── test2.lua │ ├── test3.lua │ ├── test_range.lua │ ├── test_save.lua │ ├── test_seq.lua │ ├── tx-rx-loopback.lua │ └── vlan_udp_range.lua ├── themes/ │ ├── black-yellow.theme │ └── white-black.theme └── tools/ ├── call-sphinx-build.py ├── dpdk-version.sh ├── format-clang.sh ├── meson.build ├── pktgen-build.sh ├── run.py ├── sudoGDB └── vfio-setup.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ --- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: 0 AlignAfterOpenBracket: Align AlignConsecutiveMacros: true AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: TopLevel AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: MultiLine BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: false AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH - STAILQ_FOREACH - TAILQ_FOREACH_SAFE - PKTDEV_FOREACH - TAILQ_FOREACH - json_array_foreach - json_object_object_foreach - json_object_object_foreachC - CIRCLEQ_FOREACH - jcfg_application_foreach - jcfg_defaults_foreach - jcfg_umem_foreach - jcfg_lport_foreach - jcfg_lgroup_foreach - jcfg_options_foreach - jcfg_thread_foreach IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: false IndentPPDirectives: None IndentWidth: 4 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 4 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 4 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 100 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: true SortIncludes: false SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 8 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TabWidth: 4 UseTab: Never ... ================================================ FILE: .claude/settings.local.json ================================================ { "permissions": { "allow": [ "Bash(ninja:*)", "Bash(wc:*)", "Bash(ls:*)", "Read(//work/projects/intel/pktgen-dpdk/**)", "Bash(find:*)", "Bash(clang-format:*)", "Bash(git -C /work/projects/intel/pktgen-dpdk diff --stat lib/)", "Bash(grep -rn \"pktgen_stats_clear\\\\|\\\\.qstats\\\\|\\\\.curr\\\\|\\\\.prev\\\\|\\\\.rate\\\\|rte_eth_stats_get\" /work/projects/intel/pktgen-dpdk/app/*.c)" ] } } ================================================ FILE: .editorconfig ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright 2019 6WIND S.A. # See https://editorconfig.org/ for syntax reference. root = true [**] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 indent_style = space indent_size = 4 tab_width = 4 max_line_length = 100 [*.py] indent_style = space indent_size = 4 [*.rst] indent_style = space indent_size = 3 [*.build] indent_style = space indent_size = 4 [*.yml] indent_size = 2 [COMMIT_EDITMSG] max_line_length = 72 ================================================ FILE: .githooks/pre-commit ================================================ #!/usr/bin/env bash # Pre-commit hook for Markdown linting and optional auto-fix. # Usage: ln -sf ../../.githooks/pre-commit .git/hooks/pre-commit # Requires Node.js (npx). Installs markdownlint-cli2 on demand (no permanent dependency). set -euo pipefail REPO_ROOT="$(git rev-parse --show-toplevel)" cd "$REPO_ROOT" STAGED_MD_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(md|MD)$' || true) [ -z "$STAGED_MD_FILES" ] && exit 0 if ! command -v npx >/dev/null 2>&1; then echo "[pre-commit] npx (Node.js) not found. Install Node.js to lint Markdown." >&2 exit 1 fi echo "[pre-commit] Linting Markdown files:" echo "$STAGED_MD_FILES" | sed 's/^/ - /' if ! npx --yes -- markdownlint-cli2 --version >/dev/null 2>&1; then echo "[pre-commit] Installing markdownlint-cli2 (ephemeral)." >&2 npm install --no-save markdownlint-cli2 >/dev/null 2>&1 || { echo "[pre-commit] Failed to install markdownlint-cli2." >&2 exit 1 } fi CONFIG_FILE=".markdownlint.yml" [ -f "$CONFIG_FILE" ] || CONFIG_FILE=".markdownlint.yaml" LINT_OK=1 if ! npx --yes -- markdownlint-cli2 $STAGED_MD_FILES --config "$CONFIG_FILE"; then LINT_OK=0 fi if [ $LINT_OK -eq 0 ]; then echo "[pre-commit] Markdown issues detected. Attempting optional auto-fix (markdownlint legacy)." >&2 if npx --yes -- markdownlint --version >/dev/null 2>&1; then npx --yes -- markdownlint --fix $STAGED_MD_FILES || true git add $STAGED_MD_FILES || true fi echo "[pre-commit] Commit aborted. Review fixes and re-stage." >&2 exit 1 fi echo "[pre-commit] Markdown lint passed." exit 0 #! /bin/bash for filename in $(git diff --cached --name-only | grep '.*\.[c|h]$'); do if [ -f $filename ]; then clang-format -style=file -i $filename; git add $filename; fi done ================================================ FILE: .github/workflows/clang-format.yml ================================================ name: clang-format Check on: push: branches: [main] pull_request: jobs: formatting-check: name: Formatting Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: # Fetch enough history to diff against the base branch on PRs, # or against the previous commit on direct pushes. fetch-depth: 0 - name: Install clang-format-21 run: | wget -qO- https://apt.llvm.org/llvm.sh | sudo bash -s -- 21 sudo apt-get install -y clang-format-21 sudo update-alternatives --install /usr/bin/clang-format clang-format \ /usr/bin/clang-format-21 100 clang-format --version - name: Resolve changed C/C++ files id: changed run: | if [ "${{ github.event_name }}" = "pull_request" ]; then BASE="${{ github.event.pull_request.base.sha }}" else BASE="${{ github.event.before }}" fi # Gracefully handle the first push to a branch (no previous commit). if git cat-file -t "$BASE" 2>/dev/null | grep -q commit; then FILES=$(git diff --name-only --diff-filter=ACMR "$BASE" HEAD \ | grep -E '\.(c|h|cpp|hpp|cc|hh)$' || true) else FILES=$(git ls-files '*.c' '*.h' '*.cpp' '*.hpp' '*.cc' '*.hh') fi echo "files<> "$GITHUB_OUTPUT" echo "$FILES" >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" if [ -z "$FILES" ]; then echo "No C/C++ files changed – skipping format check." echo "skip=true" >> "$GITHUB_OUTPUT" else echo "Checking $(echo "$FILES" | wc -l) file(s):" echo "$FILES" echo "skip=false" >> "$GITHUB_OUTPUT" fi - name: Check formatting if: steps.changed.outputs.skip == 'false' run: | echo "${{ steps.changed.outputs.files }}" \ | xargs -r clang-format --dry-run --Werror ================================================ FILE: .github/workflows/doc.yml ================================================ name: Doc Deploy on: push: branches: [main] # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow one concurrent deployment concurrency: group: "pages" cancel-in-progress: true jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install dependencies run: | sudo apt install python3-sphinx meson ninja-build - name: Build docs run: | meson -Donly_docs=true builddir make docs - name: Setup Pages uses: actions/configure-pages@v4 - name: Upload artifact uses: actions/upload-artifact@v4 with: path: "./builddir/docs/source/html" - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .github/workflows/markdownlint.yml ================================================ name: Markdown Lint on: pull_request: paths: - '**/*.md' - '.markdownlint.yml' push: branches: [ main ] paths: - '**/*.md' - '.markdownlint.yml' jobs: markdownlint: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install markdownlint tools run: | npm install --no-save markdownlint-cli2 - name: Run markdownlint run: | npx markdownlint-cli2 "**/*.md" "!usr/**" "!builddir/**" - name: Upload markdownlint report (always) if: always() run: | # Create simple summary artifact (non-blocking) echo "Markdown lint completed" > markdownlint-summary.txt - name: Store summary artifact if: always() uses: actions/upload-artifact@v4 with: name: markdownlint-summary path: markdownlint-summary.txt ================================================ FILE: .gitignore ================================================ *.o *.a *.d *~ _install _postinstall _preinstall _postbuild _postclean lib/common/common/ docs/build/ .project .pydevproject node_modules .vscode gulpfile.js x86_64-* lib/lua/build builddir/ usr/local tx_perf ================================================ FILE: .markdownlint.yml ================================================ # Markdownlint configuration # Adjusted for Pktgen project documentation style. # Rule references: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md # Disable long line rule; technical lines (URLs, tables, commands) often exceed 80 chars. MD013: false # Allow inline HTML only if absolutely needed (currently we removed it). MD033: true # Disallow inline HTML (set to false to allow) # Require fenced code block style consistency (use backticks). MD048: style: backtick # Enforce heading style ATX (# ...) MD003: style: atx # No multiple consecutive blank lines MD012: true # Trailing spaces not allowed (except for deliberate hard-breaks) MD009: strict: true # Lists: allow different markers but keep consistent indentation MD004: true # Ordered list numbering can be all '1.' for simplicity MD029: style: one # Code blocks and lists separated by blank lines MD031: true # First line should be a top-level heading MD041: true # Horizontal rules style (enforce ---) MD035: style: --- # Emphasis markers style (prefer *) MD049: style: asterisk # Strong emphasis style (prefer **) MD050: style: asterisk # Heading levels should only increment by one level at a time MD001: true ================================================ FILE: .pre-commit-config.yaml ================================================ # Pre-commit configuration for FastIPC # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks # Global exclusions - ignore build directories and specifications exclude: '^(builddir/.*|specs/.*|.github/.*|.githooks/.*)' repos: # General code quality hooks - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: trailing-whitespace args: [--markdown-linebreak-ext=md] - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files args: ['--maxkb=1000'] - id: check-case-conflict - id: check-merge-conflict - id: check-symlinks - id: debug-statements - id: detect-private-key - id: mixed-line-ending args: ['--fix=lf'] # C/C++ specific formatting and linting - repo: https://github.com/pre-commit/mirrors-clang-format rev: v22.1.0 hooks: - id: clang-format args: ['-i'] types_or: [c, c++] # Additional C/C++ static analysis (manual use) # Uncomment to enable clang-tidy in pre-commit (requires compilation database) # - repo: local # hooks: # - id: clang-tidy # name: clang-tidy # entry: clang-tidy # language: system # files: \.(c|h)$ # args: [--format-style=file] # Meson build file validation (manual) # Run manually with: meson setup --dry-run build-test # Documentation and markdown - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.47.0 hooks: - id: markdownlint args: ['--fix', '--disable', 'MD013', 'MD033'] # Shell script linting # - repo: https://github.com/shellcheck-py/shellcheck-py # rev: v0.10.0.1 # hooks: # - id: shellcheck # Security scanning #- repo: https://github.com/Yelp/detect-secrets # rev: v1.5.0 # hooks: # - id: detect-secrets # args: ['--baseline', '.secrets.baseline'] #ci: # autofix_commit_msg: | # [pre-commit.ci] auto fixes from pre-commit.com hooks # # for more information, see https://pre-commit.ci # autofix_prs: true # autoupdate_branch: '' # autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate' # autoupdate_schedule: weekly # skip: [] # submodules: false ================================================ FILE: .readthedocs.yaml ================================================ # .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-22.04 tools: python: "3.11" # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/source/conf.py # We recommend specifying your dependencies to enable reproducible builds: # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html # python: # install: # - requirements: docs/requirements.txt ================================================ FILE: 2-ports ================================================ # # Pktgen 25.08.2 # Copyright(c) <2010-2025>, Intel Corporation. All rights reserved., Powered by DPDK 26.03.0-rc0 # Command line arguments: (DPDK args are defaults) # ./usr/local/bin/pktgen -c fe -n 3 -m 512 --proc-type primary -- -v -T -P -G -m [2-3:4].0 -m [5-6:7].1 -f themes/black-yellow.theme ####################################################################### # Pktgen Configuration script information: # Flags 00008814 # Number of ports: 2 # Number ports per page: 4 # Number descriptors: RX 1024 TX: 1024 # Promiscuous mode is Enabled # Global configuration: # geometry 132x56 disable mac_from_arp ######################### Port 0 ################################## # # Port: 0, Burst (Rx/Tx): 64/ 32, Rate:100%, Flags: 1000, TX Count:Forever # Sequence count:0, Prime:1 VLAN ID:0001, Link: # # Set up the primary port information: set 0 count 0 set 0 size 64 set 0 rate 100 set 0 rxburst 64 set 0 txburst 32 set 0 sport 1234 set 0 dport 5678 set 0 prime 1 set 0 type ipv4 set 0 proto tcp set 0 dst ip 192.168.1.1 set 0 src ip 192.168.0.1/24 set 0 tcp flags ack set 0 tcp seq 74616 set 0 tcp ack 74640 set 0 dst mac 6c:fe:54:a0:84:f0 set 0 src mac 6c:fe:54:a0:86:80 set 0 vlan 1 set 0 pattern abc set 0 jitter 50 disable 0 mpls range 0 mpls entry 0x0 disable 0 qinq set 0 qinqids 0 0 disable 0 gre disable 0 gre_eth disable 0 vxlan set 0 vxlan 0x0 0 0 # # Port flag values: disable 0 icmp disable 0 pcap disable 0 range disable 0 latency disable 0 process disable 0 capture disable 0 vlan # # Range packet information: range 0 src mac start 6c:fe:54:a0:86:80 range 0 src mac min 00:00:00:00:00:00 range 0 src mac max 00:00:00:00:00:00 range 0 src mac inc 00:00:00:00:00:00 range 0 dst mac start 6c:fe:54:a0:86:81 range 0 dst mac min 00:00:00:00:00:00 range 0 dst mac max 00:00:00:00:00:00 range 0 dst mac inc 00:00:00:00:00:00 range 0 src ip start 192.168.0.1 range 0 src ip min 192.168.0.1 range 0 src ip max 192.168.0.254 range 0 src ip inc 0.0.0.0 range 0 dst ip start 192.168.1.1 range 0 dst ip min 192.168.1.1 range 0 dst ip max 192.168.1.254 range 0 dst ip inc 0.0.0.1 range 0 proto tcp range 0 src port start 1234 range 0 src port min 0 range 0 src port max 65535 range 0 src port inc 1 range 0 dst port start 5678 range 0 dst port min 0 range 0 dst port max 65535 range 0 dst port inc 1 range 0 tcp flags ack range 0 tcp seq start 74616 range 0 tcp seq min 0 range 0 tcp seq max 536870911 range 0 tcp seq inc 0 range 0 tcp ack start 74640 range 0 tcp ack min 0 range 0 tcp ack max 536870911 range 0 tcp ack inc 0 range 0 ttl start 64 range 0 ttl min 0 range 0 ttl max 255 range 0 ttl inc 0 range 0 vlan start 1 range 0 vlan min 1 range 0 vlan max 4095 range 0 vlan inc 0 range 0 cos start 0 range 0 cos min 0 range 0 cos max 7 range 0 cos inc 0 range 0 tos start 0 range 0 tos min 0 range 0 tos max 255 range 0 tos inc 0 range 0 gre key 0 range 0 size start 64 range 0 size min 64 range 0 size max 1518 range 0 size inc 0 # # Set up the sequence data for the port. set 0 seq_cnt 0 ######################### Port 1 ################################## # # Port: 1, Burst (Rx/Tx): 64/ 32, Rate:100%, Flags: 1000, TX Count:Forever # Sequence count:0, Prime:1 VLAN ID:0001, Link: # # Set up the primary port information: set 1 count 0 set 1 size 64 set 1 rate 100 set 1 rxburst 64 set 1 txburst 32 set 1 sport 1234 set 1 dport 5678 set 1 prime 1 set 1 type ipv4 set 1 proto tcp set 1 dst ip 192.168.0.1 set 1 src ip 192.168.1.1/24 set 1 tcp flags ack set 1 tcp seq 74616 set 1 tcp ack 74640 set 1 dst mac 6c:fe:54:a0:84:f1 set 1 src mac 6c:fe:54:a0:86:81 set 1 vlan 1 set 1 pattern abc set 1 jitter 50 disable 1 mpls range 1 mpls entry 0x0 disable 1 qinq set 1 qinqids 0 0 disable 1 gre disable 1 gre_eth disable 1 vxlan set 1 vxlan 0x0 0 0 # # Port flag values: disable 1 icmp disable 1 pcap disable 1 range disable 1 latency disable 1 process disable 1 capture disable 1 vlan # # Range packet information: range 1 src mac start 6c:fe:54:a0:86:81 range 1 src mac min 00:00:00:00:00:00 range 1 src mac max 00:00:00:00:00:00 range 1 src mac inc 00:00:00:00:00:00 range 1 dst mac start 6c:fe:54:a0:86:80 range 1 dst mac min 00:00:00:00:00:00 range 1 dst mac max 00:00:00:00:00:00 range 1 dst mac inc 00:00:00:00:00:00 range 1 src ip start 192.168.1.1 range 1 src ip min 192.168.1.1 range 1 src ip max 192.168.1.254 range 1 src ip inc 0.0.0.0 range 1 dst ip start 192.168.2.1 range 1 dst ip min 192.168.2.1 range 1 dst ip max 192.168.2.254 range 1 dst ip inc 0.0.0.1 range 1 proto tcp range 1 src port start 1234 range 1 src port min 0 range 1 src port max 65535 range 1 src port inc 1 range 1 dst port start 5678 range 1 dst port min 0 range 1 dst port max 65535 range 1 dst port inc 1 range 1 tcp flags ack range 1 tcp seq start 74616 range 1 tcp seq min 0 range 1 tcp seq max 536870911 range 1 tcp seq inc 0 range 1 tcp ack start 74640 range 1 tcp ack min 0 range 1 tcp ack max 536870911 range 1 tcp ack inc 0 range 1 ttl start 64 range 1 ttl min 0 range 1 ttl max 255 range 1 ttl inc 0 range 1 vlan start 1 range 1 vlan min 1 range 1 vlan max 4095 range 1 vlan inc 0 range 1 cos start 0 range 1 cos min 0 range 1 cos max 7 range 1 cos inc 0 range 1 tos start 0 range 1 tos min 0 range 1 tos max 255 range 1 tos inc 0 range 1 gre key 0 range 1 size start 64 range 1 size min 64 range 1 size max 1518 range 1 size inc 0 # # Set up the sequence data for the port. set 1 seq_cnt 0 ################################ Done ################################# ================================================ FILE: 2-vf-ports ================================================ # # Pktgen 25.07.1 # Copyright(c) <2010-2025>, Intel Corporation. All rights reserved., Powered by DPDK 25.07.0 # Command line arguments: (DPDK args are defaults) # ./usr/local/bin/pktgen -c 7c000 -n 3 -m 512 --proc-type primary -- -v -T -P -G -m [15:16].0 -m [17:18].1 -f themes/black-yellow.theme -f 2-ports ####################################################################### # Pktgen Configuration script information: # Flags 00008814 # Number of ports: 2 # Number ports per page: 4 # Number descriptors: RX 1024 TX: 1024 # Promiscuous mode is Enabled # Global configuration: # geometry 132x56 disable mac_from_arp ######################### Port 0 ################################## # # Port: 0, Burst (Rx/Tx): 64/ 32, Rate:100%, Flags: 1000, TX Count:Forever # Sequence count:0, Prime:1 VLAN ID:0001, Link: # # Set up the primary port information: set 0 count 0 set 0 size 64 set 0 rate 100 set 0 rxburst 64 set 0 txburst 32 set 0 sport 1234 set 0 dport 5678 set 0 prime 1 set 0 type ipv4 set 0 proto tcp set 0 dst ip 192.168.66.161 set 0 src ip 192.168.66.101/24 set 0 tcp flags ack set 0 tcp seq 74616 set 0 tcp ack 74640 set 0 dst mac 52:54:00:00:16:01 set 0 src mac 06:bd:a3:87:a7:96 set 0 vlan 1 set 0 pattern abc set 0 jitter 50 disable 0 mpls range 0 mpls entry 0x0 disable 0 qinq set 0 qinqids 0 0 disable 0 gre disable 0 gre_eth disable 0 vxlan set 0 vxlan 0x0 0 0 # # Port flag values: disable 0 icmp disable 0 pcap disable 0 range disable 0 latency disable 0 process disable 0 capture disable 0 vlan # # Range packet information: range 0 src mac start 06:bd:a3:87:a7:96 range 0 src mac min 00:00:00:00:00:00 range 0 src mac max 00:00:00:00:00:00 range 0 src mac inc 00:00:00:00:00:00 range 0 dst mac start 2e:f9:d1:05:fb:e7 range 0 dst mac min 00:00:00:00:00:00 range 0 dst mac max 00:00:00:00:00:00 range 0 dst mac inc 00:00:00:00:00:00 range 0 src ip start 192.168.0.1 range 0 src ip min 192.168.0.1 range 0 src ip max 192.168.0.254 range 0 src ip inc 0.0.0.0 range 0 dst ip start 192.168.1.1 range 0 dst ip min 192.168.1.1 range 0 dst ip max 192.168.1.254 range 0 dst ip inc 0.0.0.1 range 0 proto tcp range 0 src port start 1234 range 0 src port min 0 range 0 src port max 65535 range 0 src port inc 1 range 0 dst port start 5678 range 0 dst port min 0 range 0 dst port max 65535 range 0 dst port inc 1 range 0 tcp flags ack range 0 tcp seq start 74616 range 0 tcp seq min 0 range 0 tcp seq max 536870911 range 0 tcp seq inc 0 range 0 tcp ack start 74640 range 0 tcp ack min 0 range 0 tcp ack max 536870911 range 0 tcp ack inc 0 range 0 ttl start 64 range 0 ttl min 0 range 0 ttl max 255 range 0 ttl inc 0 range 0 vlan start 1 range 0 vlan min 1 range 0 vlan max 4095 range 0 vlan inc 0 range 0 cos start 0 range 0 cos min 0 range 0 cos max 7 range 0 cos inc 0 range 0 tos start 0 range 0 tos min 0 range 0 tos max 255 range 0 tos inc 0 range 0 gre key 0 range 0 size start 64 range 0 size min 64 range 0 size max 1518 range 0 size inc 0 # # Set up the sequence data for the port. set 0 seq_cnt 0 ######################### Port 1 ################################## # # Port: 1, Burst (Rx/Tx): 64/ 32, Rate:100%, Flags: 1000, TX Count:Forever # Sequence count:0, Prime:1 VLAN ID:0001, Link: # # Set up the primary port information: set 1 count 0 set 1 size 64 set 1 rate 100 set 1 rxburst 64 set 1 txburst 32 set 1 sport 1234 set 1 dport 5678 set 1 prime 1 set 1 type ipv4 set 1 proto tcp set 1 dst ip 192.168.66.162 set 1 src ip 192.168.66.102/24 set 1 tcp flags ack set 1 tcp seq 74616 set 1 tcp ack 74640 set 1 dst mac 52:54:00:00:16:02 set 1 src mac 2e:f9:d1:05:fb:e7 set 1 vlan 1 set 1 pattern abc set 1 jitter 50 disable 1 mpls range 1 mpls entry 0x0 disable 1 qinq set 1 qinqids 0 0 disable 1 gre disable 1 gre_eth disable 1 vxlan set 1 vxlan 0x0 0 0 # # Port flag values: disable 1 icmp disable 1 pcap disable 1 range disable 1 latency disable 1 process disable 1 capture disable 1 vlan # # Range packet information: range 1 src mac start 2e:f9:d1:05:fb:e7 range 1 src mac min 00:00:00:00:00:00 range 1 src mac max 00:00:00:00:00:00 range 1 src mac inc 00:00:00:00:00:00 range 1 dst mac start 06:bd:a3:87:a7:96 range 1 dst mac min 00:00:00:00:00:00 range 1 dst mac max 00:00:00:00:00:00 range 1 dst mac inc 00:00:00:00:00:00 range 1 src ip start 192.168.1.1 range 1 src ip min 192.168.1.1 range 1 src ip max 192.168.1.254 range 1 src ip inc 0.0.0.0 range 1 dst ip start 192.168.2.1 range 1 dst ip min 192.168.2.1 range 1 dst ip max 192.168.2.254 range 1 dst ip inc 0.0.0.1 range 1 proto tcp range 1 src port start 1234 range 1 src port min 0 range 1 src port max 65535 range 1 src port inc 1 range 1 dst port start 5678 range 1 dst port min 0 range 1 dst port max 65535 range 1 dst port inc 1 range 1 tcp flags ack range 1 tcp seq start 74616 range 1 tcp seq min 0 range 1 tcp seq max 536870911 range 1 tcp seq inc 0 range 1 tcp ack start 74640 range 1 tcp ack min 0 range 1 tcp ack max 536870911 range 1 tcp ack inc 0 range 1 ttl start 64 range 1 ttl min 0 range 1 ttl max 255 range 1 ttl inc 0 range 1 vlan start 1 range 1 vlan min 1 range 1 vlan max 4095 range 1 vlan inc 0 range 1 cos start 0 range 1 cos min 0 range 1 cos max 7 range 1 cos inc 0 range 1 tos start 0 range 1 tos min 0 range 1 tos max 255 range 1 tos inc 0 range 1 gre key 0 range 1 size start 64 range 1 size min 64 range 1 size max 1518 range 1 size inc 0 # # Set up the sequence data for the port. set 1 seq_cnt 0 ################################ Done ################################# ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. --- ## Build Commands The project uses **Meson/Ninja**. A `builddir/` is typically already initialised. ```bash # Incremental build (most common) ninja -C builddir # Full configure + build from scratch meson setup builddir meson compile -C builddir # Makefile wrapper (delegates to tools/pktgen-build.sh) make build # release build make rebuild # clean + release build make debug # debug build (no optimisation) make debugopt # debug with -O2 make buildlua # release + Lua scripting enabled make clean # Build options (pass to meson setup) meson setup builddir -Denable_lua=true meson setup builddir -Denable_docs=true meson setup builddir -Donly_docs=true # docs only, skip app/lib # Code formatting ninja -C builddir clang-format-check # check only (CI does this) ninja -C builddir clang-format # auto-fix in place # Documentation make docs # Sphinx + Doxygen # Tests ninja -C builddir test # meson test runner # Integration tests are Lua scripts in test/ that connect to a running # pktgen instance over its TCP socket (port 22022). ``` Build is strict: **warning_level=3** and **werror=true**. Any new warning is a build failure. --- ## Pre-commit Hook Setup ```bash ln -sf ../../.githooks/pre-commit .git/hooks/pre-commit chmod +x .githooks/pre-commit ``` This runs `clang-format -i` on staged C files and `markdownlint` on staged Markdown. --- ## Commit Message Convention From `CONTRIBUTING.md`: ``` ``` Example prefix pattern observed in recent commits: `refactor:`, `fix:`, `stats:`, etc. --- ## High-Level Architecture Pktgen is a DPDK-based wire-rate packet generator. The binary is `builddir/app/pktgen`. ### Module layout ``` lib/ hmap/ Hash-map (type-safe, 64-bit unified storage) common/ Shared utilities: checksums, IP parsing, strings, CPU info, port config utils/ Portlist parsing, heap, inet_pton, JSON (parson) vec/ Dynamic-array (vector) container plugin/ dlopen-based plugin loader (up to 32 plugins, start/stop lifecycle) cli/ Full interactive shell: node tree, gap-buffer editor, TAB completion, history, help, env vars, map-driven argument parsing lua/ Lua 5.3/5.4 scripting bindings (conditional on -Denable_lua=true) app/ pktgen-main.c Entry point: arg parsing, EAL init, port setup, CLI loop start pktgen.c Core TX loop logic, packet rate calculation, timer callbacks pktgen.h Global pktgen_t struct, page/flag enums, per-port iteration macros pktgen-port-cfg.h port_info_t (per-port state), SEND_* flag bits, pkt_seq_t layout pktgen-cmds.c ~112 KB command implementation dispatcher cli-functions.c ~74 KB CLI map tables + command handler glue (registers maps) l2p.c / l2p.h Lcore-to-Port mapping matrix, mempool ownership pktgen-{ipv4,ipv6,tcp,udp,arp,vlan,gre,gtpu,ether}.c Protocol header builders pktgen-range.c Incrementing-field packet generation pktgen-seq.c 16-slot packet sequence pktgen-random.c Per-field random bitfield masks pktgen-pcap.c PCAP file load/replay pktgen-capture.c Packet capture to memory pktgen-latency.c Latency sampling & histograms pktgen-stats.c Per-port / per-queue stats and rate calculation pktgen-display.c Terminal UI page rendering (10 display pages) pktgen-log.c Ring-buffer message log ``` ### Build dependency order (lib subdirectories) ``` hmap → common → utils → vec → plugin → cli → lua ``` All lib modules compile to static libraries and are linked into the single `pktgen` executable. ### Data-flow summary 1. **Initialisation** (`pktgen-main.c`): parse `-m` matrix string → populate `l2p_t` (which lcores own which ports/queues) → allocate per-queue mempools (`rx_mp`, `tx_mp`, `sp_mp`) → build default packet templates via `pktgen_packet_ctor()` → launch one worker per lcore. 2. **TX loop** (`pktgen.c:pktgen_launch_one_lcore`): each worker polls its assigned port/queue. The active sending mode is determined by the port's `port_flags` atomic (e.g. `SEND_SINGLE_PKTS`, `SEND_RANGE_PKTS`, `SEND_SEQ_PKTS`, `SEND_PCAP_PKTS`). Rate limiting uses cycle-based inter-burst timing (`tx_cycles`). 3. **RX path**: received packets are optionally processed (`PROCESS_INPUT_PKTS` — handles ARP/ICMP echo), captured (`CAPTURE_PKTS`), or used for latency measurement (`SAMPLING_LATENCIES`). 4. **CLI / command path**: the interactive shell runs on the main lcore. Commands are dispatched via `cli_map` pattern matching in `cli-functions.c` → actual logic in `pktgen-cmds.c`. Changing a port setting typically sets a flag (`SETUP_TRANSMIT_PKTS`) so the worker rebuilds its packet template on the next iteration. 5. **Display refresh**: a periodic timer (`UPDATE_DISPLAY_TICK_RATE`) triggers `pktgen_page_display()` which re-renders the current screen page using stats collected via `rte_eth_stats_get` / extended stats. ### Key global state - `pktgen_t pktgen` — single global (defined in `pktgen.c`, declared `extern` in `pktgen.h`). Holds port count, display state, CPU info, capture buffers, Lua handles. - `l2p_t` — lcore↔port mapping. Accessed via `l2p_get_port_pinfo(pid)` and the per-lcore `l2p_get_lport(lid)`. - `port_info_t` — per-port struct (embedded inside `l2p_port_t.pinfo`). Contains packet templates (`seq_pkt[NUM_TOTAL_PKTS]`), range config, stats, latency, random-field masks, and the 64-bit `port_flags` atomic. - `this_cli` — `RTE_PER_LCORE` pointer to `struct cli`. The CLI tree, gap buffer, history, and the `cmd_maps` registry all live here. ### Port-flag model `port_flags` is a 64-bit atomic on each `port_info_t`. Bits are grouped: | Group | Bits | Semantics | |---|---|---| | Non-exclusive RX/misc | 0–9 | ARP, ICMP echo, capture, latency sampling | | Exclusive TX mode | 12–15 | Exactly one of SINGLE/PCAP/RANGE/SEQ must be set | | Exclusive pkt-type | 16–23 | VLAN, MPLS, GRE, VxLAN, random, latency | | Control | 28–31 | SETUP_TRANSMIT_PKTS, SENDING_PACKETS, SEND_FOREVER | The macros `EXCLUSIVE_MODES` and `EXCLUSIVE_PKT_MODES` define the mutual-exclusion masks. ### CLI auto-completion architecture TAB completion has two layers: 1. **Tree completion** — walks `cli_node` children of the current directory, matches prefixes of command/dir/file names. 2. **Map completion** — once the first token (command name) is typed, `cli_get_cmd_map(argv[0])` returns the registered `cli_map` table. The completer scans all map entries whose fixed tokens match what has been typed so far, then offers candidates or placeholder hints (e.g. ``, ``) for the current argument position. Map registration happens automatically: `cli_help_add(group, map, help)` calls `cli_register_cmd_maps(map)` which extracts every unique first-token and stores `{token → map}` in the growable `cmd_maps` array on `this_cli`. Do not call `cli_register_cmd_map()` manually after `cli_help_add()` — it would be a redundant no-op. Map format tokens: `%d` (32-bit), `%D` (64-bit), `%u`/`%U` (unsigned), `%h`/`%H` (hex), `%b` (8-bit), `%n` (number), `%s` (string), `%c` (comma-list), `%m` (MAC), `%4` (IPv4), `%6` (IPv6), `%P` (portlist), `%C` (corelist), `%k` (kvargs), `%l` (list), `%|opt1|opt2|…` (choice token). ### Packet-slot layout (`pkt_seq_t` array) Each port has `NUM_TOTAL_PKTS = 20` slots: | Index | Role | |---|---| | 0–15 | Sequence packets (cycled in `SEND_SEQ_PKTS` mode) | | 16 (`SINGLE_PKT`) | Default single-packet template | | 17 (`SPECIAL_PKT`) | Temporary / scratch packet | | 18 (`RANGE_PKT`) | Template rebuilt each burst in range mode | | 19 (`LATENCY_PKT`) | Latency probe packet | --- ## Coding Conventions - **Formatting**: LLVM-based `.clang-format`, 100-column limit, 4-space indent, right-aligned pointers. Run `clang-format -i` on any file you touch. - **New files**: must carry the SPDX header (`SPDX-License-Identifier: BSD-3-Clause`) and a copyright line. - **Port iteration**: use the `forall_ports(_action)` or `foreach_port(_portlist, _action)` macros from `pktgen.h` rather than hand-rolling port loops. - **TAILQ / CIRCLEQ**: the CLI and help systems use POSIX tail-queues extensively. New linked structures should follow the same pattern. - **Atomic port flags**: always use `rte_atomic64_t` operations when reading or writing `port_info_t.port_flags` from worker lcores. - **cli_map entries**: every entry in a `cli_map` array must have a corresponding `case` in the command handler's switch on `m->index`. Orphan entries cause phantom command registrations and broken auto-complete. --- ## CI Checks (GitHub Actions) Three workflows run on every PR: | Workflow | What it checks | |---|---| | `clang-format.yml` | All C/C++ files pass `clang-format --dry-run` (clang-format 21) | | `markdownlint.yml` | All `.md` files pass `markdownlint-cli2` (config in `.markdownlint.yml`) | | `doc.yml` | Sphinx docs build cleanly (runs on push to `main` only) | Markdown rules of note: bare URLs must be wrapped in `< >`, numbered lists should use `1.` style, fenced code blocks must have a language hint. ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct We are committed to fostering a welcoming, respectful, and productive environment for all contributors and users of Pktgen-DPDK. This project adopts (by reference) the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) (Version 2.1) as the foundation of expected behavior. ## 1. Scope Applies to all project spaces: GitHub issues, pull requests, discussions, documentation, community chats, and any project-related events. ## 2. Summary of Expectations - Be respectful, inclusive, and patient. - Provide constructive feedback; focus on ideas, not individuals. - Use welcoming and professional language; avoid harassment or discrimination. - Assume good intent while clarifying misunderstandings. - Respect differing viewpoints and experiences. ## 3. Unacceptable Behavior (Examples) - Harassment, intimidation, or discrimination of any form. - Personal or political attacks. - Trolling, insults, or derogatory comments. - Public or private harassment. - Publishing others’ private information without explicit permission. - Conduct inconsistent with professional standards. ## 4. Reporting If you experience or witness unacceptable behavior: - Preferred: Open a confidential issue labeled `coc-report` (omit sensitive personal details; maintainers will follow up privately), or - Email the maintainers (if listed in `README.md`) with details: date/time, participants, context, and any supporting material. Reports will be handled promptly, respectfully, and—when requested—confidentially. ## 5. Enforcement Maintainers are responsible for clarifying and enforcing this Code. Actions may include (progressively, as appropriate): 1. Informal warning 1. Formal written warning 1. Temporary exclusion from discussions or repository interactions 1. Permanent ban from project spaces Severe violations (e.g. threats, doxxing, hate speech) may result in immediate permanent removal without prior warning. ## 6. Appeals If you believe an enforcement decision was made in error, you may request a review by contacting the maintainers with a concise rationale. ## 7. Attribution This document is adapted from the Contributor Covenant, Version 2.1. See: --- Questions or feedback regarding this Code of Conduct are welcome—open an issue to discuss improvements. ================================================ FILE: CONTRIBUTING.TXT ================================================ Contributors Guide ================== This document outlines how to contribute code to the PKTGEN project. Getting the code ---------------- The PKTGEN code can be cloned from the repository on Github as follows: git clone https://github.com/pktgen/Pktgen-DPDK.git The code can be browsed at https://github.com/pktgen/Pktgen-DPDK Submitting Patches ------------------ Contributions to PKTGEN should be submitted as merge requests on Github. The commit message must end with a "Signed-off-by:" line which is added using: git commit --signoff # or -s The purpose of the signoff is explained in the Linux kernel guidelines Developer's Certificate of Origin: https://www.kernel.org/doc/html/latest/process/submitting-patches.html Note: All developers must ensure that they have read, understood and complied with the Developer's Certificate of Origin section of the documentation prior to applying the signoff and submitting a patch. The DPDK Code Contributors guidelines contain information that is useful for submitting patches to PKTGEN: http://dpdk.org/doc/guides/contributing/index.html Coding Guidelines ----------------- * C code should follow the DPDK coding standards. * Lua code should follow existing code. Maintainer ---------- The PKTGEN maintainer is: Keith Wiles ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Pktgen-DPDK Thank you for your interest in improving Pktgen-DPDK. This short guide complements the main `README.md` and highlights the key expectations for contributions. ## 1. Before You Start - Skim the project overview and feature list in the README. - Make sure you can build and run the tool (see "Quick Start" and "Clone and Build" sections). - Search existing issues and pull requests to avoid duplication. ## 2. Development Environment - Build system: Meson/Ninja (optional simple `Makefile` wrapper provided). - Required: A recent DPDK build with a valid `libdpdk.pc` on your `PKG_CONFIG_PATH`. - Recommended: Enable the pre-commit hook for markdown linting: - `ln -sf ../../.githooks/pre-commit .git/hooks/pre-commit && chmod +x .githooks/pre-commit` ## 3. Coding & Style - Follow existing C coding patterns; prefer readability and minimal global state changes. - Keep commits focused; one logical change per commit when possible. - Document non-obvious code with concise comments. - Avoid introducing new external dependencies without discussion. ## 4. Documentation Standards - All Markdown must pass `markdownlint` (CI will enforce). - Use fenced code blocks with language hints. - Wrap bare URLs in `< >`. - Numbered lists may all use `1.` style. - If adding new user-facing features, update the relevant README section or create a doc under `docs/`. - Refer to the extended style guidance in [`docs/STYLE.md`](./docs/STYLE.md) for formatting patterns and examples. ## 5. Commit Messages Use the following structure: ```text ``` Examples: ```text stats: fix link status refresh on state change Previously link duplex/speed changes were ignored unless carrier toggled. Update logic to trigger display refresh on any field difference. ``` ## 6. Submitting Pull Requests - Fork, branch from `main`. - Rebase (not merge) to update your branch before final push. - Ensure `meson compile` succeeds and (if applicable) tests or sample scripts still run. - CI must be green (markdownlint, build, etc.). - Reference related issues with `Fixes #NNN` when appropriate. ## 7. Reporting Issues Include (when relevant): - Pktgen version (`cat VERSION`) - DPDK version and configuration - NIC model(s) and driver versions - Reproduction steps and minimal config (`cfg/` file if relevant) - Expected vs. actual behavior ## 8. Performance / Feature Discussions Open an issue first for large changes (subsystems, new protocol modules, architectural refactors). Describe: - Problem statement / use case - Proposed design sketch - Anticipated impacts (performance, config format, user CLI) ## 9. Security Do not publicly disclose potential security-impacting bugs without prior coordination—open a private issue if possible or contact maintainers directly. See also the community standards in the [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md). ## 10. Licensing By contributing you agree your changes are under the existing project license (BSD-3-Clause). Ensure any new files include the appropriate SPDX identifier header where applicable. ## 11. Quick Reference | Area | Where to Look | |------|---------------| | Build & Run | README Quick Start / Build sections | | Config examples | `cfg/` directory | | Lua automation | README Automation section / `test/*.lua` | | Themes | `themes/` directory | | Plugins | `lib/plugin/` | --- Questions? Open an issue or start a discussion. Happy hacking! ================================================ FILE: INSTALL.md ================================================ # Pktgen - Traffic Generator powered by DPDK --- Pktgen is a traffic generator powered by DPDK at wire rate traffic with 64 byte frames.** ## (Pktgen) Sounds like 'Packet-Gen' --- **Copyright © \<2010-2026\>, Intel Corporation. All rights reserved.** Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Intel Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SPDX-License-Identifier: BSD-3-Clause Pktgen: Created 2010-2024 by Keith Wiles @ Intel.com --- ## Installation INSTALL for setting up Pktgen with DPDK on Ubuntu 22.04 to 23.10 desktop it should work on most Linux systems as long as the kernel has hugeTLB page support and builds DPDK. ## Please Note > Pktgen-DPDK main repo is located @ > Documentation can be found @ ### Submitting bug fixes or enhancements to Pktgen-DPDK > Please fork Pktgen-DPDK from GitHub and submit a pull-request as I do not accept patches sent to DPDK mailing list. If you have any questions or issues please create an issue on Github. > Tested with Ubuntu 23.10 kernel version 6.5.0-28-generic (Mantic), and other earlier versions should work. > Please use the latest DPDK and latest Pktgen versions other combinations may work, but with limited resources and time the latest versions are the only ones tested. > > Please install the latest version of DPDK from dpdk.org and follow the build instructions using meson/ninja. Then install DPDK on your system. ## Building Pktgen Pktgen has been converted to use meson/ninja for configuration and building. **Makefiles have been removed.** This may require DPDK to be built using meson/ninja. At least a libdpdk.pc file must be present in the system for Pktgen to locate the headers and libraries. >Please read the DPDK.org documentation to understand more on building DPDK. The following is the minimum set of instructions to build DPDK. You may need to install meson and ninja, if not already installed. May need to install the BSD headers to build Pktgen code base for Ubuntu do the following: ```console sudo apt-get install libbsd-dev ``` ```console git clone git://dpdk.org/dpdk Or if git is blocked git clone http://dpdk.org/git/dpdk cd dpdk meson setup build ninja -C build sudo ninja -C build install sudo ldconfig # make sure ld.so is pointing new DPDK libraries ``` DPDK places the libdpdk.pc (pkg-config file) in a non-standard location and you need to set enviroment variable PKG_CONFIG_PATH to the location of the file. On Ubuntu 20.04 build of DPDK it places the file here /usr/local/lib/x86_64-linux-gnu/pkgconfig/libdpdk.pc ```console export PKG_CONFIG_PATH=/usr/local/lib/x86_64-linux-gnu/pkgconfig ``` Building Pktgen after you have built and installed DPDK. The new build system uses meson/ninja, but Pktgen has a build script called 'tools/pktgen-build.sh' and uses a very simple Makefile to help build Pktgen without having to fully understand meson/ninja command line. If you prefer you can still use meson/ninja directly. ```console git clone https://github.com/pktgen/Pktgen-DPDK cd pktgen-dpdk # or Pktgen-DPDK or whatever name you gave the cloned directory make or make build # Same as 'make' or make rebuild # Rebuild Pktgen, which removes the 'builddir' then builds it again via meson/ninja or make rebuildlua # to enable Lua builds # Use 'make help' to read the help message for building. # DPDK does not add a /etc/ld.so.conf.d/ like file. This means you may need to # edit /etc/ld.so.conf.d/x86_64-linux-gnu.conf file and add /usr/local/lib/x86_64-linux-gnu # Then do 'sudo ldconfig' to locate the DPDK libraries. # If you want to use vfio-pci then edit /etc/default/grub and add 'intel_iommu=on' to the LINUX default line # Then use 'update-grub' command then reboot the system. ``` Editing the meson_options.txt can be done, but normally you should use a meson command line options to enable/disable options. ## Pktgen Information The pktgen output display needs 132 columns and about 42 lines to display correctly. I am using an xterm of 132x42, but you can have a larger display and maybe a bit smaller. If you are displaying more then 4-6 ports then you will need a wider display. Pktgen allows you to view a set of ports if they do not all fit on the screen at one time via the 'page' command. Type 'help' at the 'Pktgen>' prompt to see the complete Pktgen command line commands. Pktgen uses VT100 control codes or escape codes to display the screens, which means your terminal must support VT100. ### Note `Hyperterminal in Windows is not going to work for Pktgen as it has a few problems with VT100 codes.` ## Quick comment on Pktgen modes Pktgen has a number of modes to send packets single, range, random, sequence and PCAP modes. Each mode has its own set of packet buffers and you must configure each mode to work correctly. The single packet mode is the information displayed at startup screen or when using the 'page main or page 0' command. The other screens can be accessed using 'page seq|range|rnd|pcap|stats' command. The pktgen program as built can send up to 16 packets per port in a sequence and you can configure a port using the 'seq' pktgen command. A script file can be loaded from the shell command line via the -f option and you can 'load' a script file from within pktgen as well. ## Setup Pktgen-DPDK At the pktgen-DPDK level directory we have the 'tools/setup.sh' script, which needs to be run as root once per boot. The script contains a commands to setup the environment. Before you run the script you will need to run: ```bash export RTE_SDK= export RTE_TARGET=x86_64-native-linux-gcc ``` Make sure you run the setup script as root via `./tools/setup.sh`. The setup script is a bash script and tries to setup the system correctly, but you may have to change the script to match your number of huge pages you configured above and ports. The `modprobe uio` command, in the setup script, loads the UIO support module into the kernel plus it loads the igb-uio.ko module into the kernel. The two echo commands, in the setup script, finish setting up the huge pages one for each socket. If you only have a single socket system then comment out the second echo command. The last command is to display the huge TLB setup. Edit your .bashrc or .profile or .cshrc to add the environment variables. I am using bash: `# vi ~/.bashrc` Add the following lines: Change the $RTE_SDK to the location of the DPDK version directory. Your SDK directory maybe named differently, but should point to the DPDK SDK directory. ```bash export RTE_SDK= export RTE_TARGET=x86_64-native-linux-gcc ``` or use clang if you have it installed ```bash export RTE_TARGET=x86_64-native-linux-clang ``` Create the DPDK build tree if you haven't already: ```bash cd $RTE_SDK make install T=x86_64-native-linux-gcc -j ``` The above command will create the x86_64-native-linux-gcc directory in the top level of the current-dkdp directory. The above command will build the basic DPDK libraries and build tree. Next we build pktgen: ```bash cd make -j ``` For CentOS and pcap support you may need to try (for libpcap-devel): ```bash yum install dnf-plugins-core yum config-manager --set-enabled PowerTools yum repolist ``` You should now have pktgen built. To get started, see README.md. ================================================ FILE: LICENSE ================================================ # BSD LICENSE # # Copyright(c) <2010-2025> Intel Corporation. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in # the documentation and/or other materials provided with the # distribution. # * Neither the name of Intel Corporation nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Created 2010 by Keith Wiles @ windriver.com # # SPDX-License-Identifier: BSD-3-Clause # ================================================ FILE: Makefile ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2019-2025> Intel Corporation # # Head Makefile for compiling Pktgen-DPDK, but just a wrapper around # meson and ninja using the tools/pktgen-build.sh script. # # Use 'make' or 'make build' to build Pktgen-DPDK. If the build directory does # not exist it will be created with these two build types. # Build=./tools/pktgen-build.sh build: FORCE ${Build} build rebuild: FORCE ${Build} clean build buildlua: FORCE ${Build} clean buildlua debuglua: FORCE ${Build} clean debuglua debug: FORCE ${Build} clean debug debugopt: FORCE ${Build} clean debugopt clean: FORCE ${Build} clean install: FORCE ${Build} install uninstall: FORCE ${Build} uninstall docs: FORCE ${Build} docs help: FORCE ${Build} help FORCE: @echo ">>> Use 'make help' for more commands\n" ================================================ FILE: Pktgen.lua ================================================ -- SPDX-License-Identifier: BSD-3-Clause -- Create some short cuts to the real functions. gsub = string.gsub gmatch = string.gmatch strrep = string.rep strsub = string.sub strfmt = string.format strmatch = string.match strrep = string.rep strfind = string.find strlen = string.len tolower = string.lower unlink = os.remove system = os.execute tinsert = table.insert tgetn = table.getn tconcat = table.concat -- =========================================================================== -- getPwd - Get the current working directory path. -- Works for both dos and cygwin shell, which may emit an error if one failes. -- function getPwd( func ) local s = syscmd("pwd", func); if ( s == nil ) then s = syscmd("sh pwd", func); end return s; end -- =========================================================================== -- d2u - convert dos backslashes to unix forward slashes. -- function d2u( str ) return gsub(str or "", "\\", "/"); end -- =========================================================================== -- u2d - convert unix forward slashes to dos backslashes. -- function u2d( str ) return gsub(str or "", "/", "\\"); end -- =========================================================================== -- str2tbl - convert a string to a table. -- function str2tbl( str ) local t = {}; local s; for s in gmatch(str or "", "(.-)\n") do tinsert(t, s); end return t end -- =========================================================================== -- trims the string of beginning and trailing spaces and tabs. -- function trim(txt) return gsub(txt, "%s*(.-)%s*$", "%1", 1); end -- =========================================================================== -- A formatted output routine just like the real printf. -- function printf(...) io.write(strfmt(...)); io.flush(); end -- =========================================================================== -- returns the table size or number of items in table. -- function getn( t ) local i = 0; if ( (t ~= nil) and (type(t) == "table") ) then i = tgetn(t); if ( i > 0 ) then return i; end for k in pairs(t) do i = i + 1; end end return i; end -- =========================================================================== -- returns the 'basename' and the 'basedir' -- function basename(filename) local fn, dn; -- Convert the '\' to '/' in the path name. filename = d2u(filename); fn = gsub(filename, "(.*/)(.*)", "%2") or filename; dn = gsub(filename, "(.*/)(.*)", "%1") dn = strsub(dn, 1, -2); return fn, dn; end -- =========================================================================== -- Default routine to read data from syscmd function. -- local function __doRead(fn) local data, f; -- Open and read all of the data. f = assert(io.open(fn)); data = f:read("*all"); f:close(); unlink(fn); return data; end -- =========================================================================== -- Execute the system command return the command output if needed. -- function syscmd( cmd, funcPtr ) local tmpFile = "syscmd_tmp"; system( cmd .. " > " .. tmpFile ); funcPtr = funcPtr or __doRead; return funcPtr(tmpFile); -- tmpFile is removed by the function. end -- =========================================================================== -- Execute the string and return true/false. -- function isTrue(f) local s; if ( f == nil ) then return 0; end s = "if ( "..f.." ) then return 1 else return 0 end"; return assert(loadstring(s))(); end -- =========================================================================== -- Output a message and return. -- function msg(m, ...) if ( m ~= nil ) then io.write("++ "..strfmt(m, ...)); io.flush(); end end -- =========================================================================== -- Display an error message and exit. -- function errmsg(m, ...) if ( m ~= nil ) then printf("** %s", strfmt(m, ...)); end os.exit(1); end -- =========================================================================== -- Output a 'C' like block comment. -- function cPrintf(m, ...) printf("/* "); io.write(strfmt(m, ...)); printf(" */\n"); end -- =========================================================================== -- Output a 'C' like comment. -- function comment(msg) printf("/* %s */\n", msg or "ooops"); end -- Standard set of functions for normal operation. -- ----------------------------------------------------------------------------- -- serializeIt - Convert a variable to text or its type of variable. -- local function serializeIt(v) local s; local t = type(v); if (t == "number") then s = tostring(v); elseif (t == "table") then s = tostring(v); elseif (t == "string") then s = strfmt("%q", v); elseif (t == "boolean") then s = tostring(v); elseif (t == "function") then s = strfmt("()"); elseif (t == "userdata") then s = tostring(v); elseif (t == "nil") then s = "nil"; else s = strfmt("<%s>", tostring(v)); end return s; end ----------------------------------------------------------------------------- -- Serialize a value -- k - is the variable name string. -- o - is the orignal variable name for tables. -- v - the value of the variable. -- saved - is the saved table to detect loops. -- tab - is the current tab depth. -- local function doSerialize(k, o, v, saved, tab) local s, t; local space = function(t) return strrep(" ", t); end; tab = tab or 0; t = type(v); saved = saved or {}; if (t == "table") then if ( saved[v] ~= nil ) then return strfmt("%s[%s] = %s,\n", space(tab), serializeIt(o), saved[v]); else local kk, vv, mt; saved[v] = k; if ( tab == 0 ) then s = strfmt("%s%s = {\n", space(tab), tostring(k)); else s = strfmt("%s[%s] = {\n", space(tab), serializeIt(o)); end for kk,vv in pairs(v) do local fn = strfmt("%s[%s]", tostring(k), serializeIt(kk)); s = s .. doSerialize(fn, kk, vv, saved, tab+2); end if ( tab == 0 ) then return s .. strfmt("%s}\n", space(tab)); else return s .. strfmt("%s},\n", space(tab)); end end else return strfmt("%s[%s] = %s,\n", space(tab), serializeIt(o), serializeIt(v)); end end ----------------------------------------------------------------------------- -- serialize - For a given key serialize the global variable. -- k is a string for display and t is the table to display. -- e.g. printf(serialize("foo", { ["bar"] = "foobar" } )); -- function serialize(k, t) if ( k == nil ) then k = "Globals"; t = _G; -- Dump out globals end if ( t == nil ) then t = _G; end return doSerialize(k, k, t, {}, 0); end function prints(k, t) io.write(serialize(k, t)); io.flush(); end function sleep(t) pktgen.delay(t * 1000); end ================================================ FILE: README.md ================================================ # Pktgen — DPDK Traffic Generator High‑performance, scriptable packet generator capable of wire‑rate transmission with 64‑byte frames. Pronounced: “packet‑gen” [Documentation](https://pktgen.github.io/Pktgen-DPDK/) · [Releases](https://github.com/pktgen/Pktgen-DPDK/releases) --- ## Table of Contents 1. [Overview](#1-overview) 1. [Features](#2-features-partial-list) 1. [Quick Start](#3-quick-start) 1. [Building](#4-building-details) 1. [Configuration Files](#5-configuration-files-cfg) 1. [Configuration Key Reference](#6-configuration-key-reference) 1. [Runtime Modes & Pages](#7-runtime-modes--pages) 1. [Automation & Remote Control](#8-automation--remote-control) 1. [Advanced Topics](#9-advanced-topics) 1. [Contributing](#10-contributing) 1. [License](#11-license) 1. [Related Links](#12-related-links) 1. [Acknowledgments](#13-acknowledgments) --- ## 1. Overview Pktgen is a multi‑port, multi‑core traffic generator built on top of [DPDK]. It targets realistic, repeatable performance and functional packet tests while remaining fully controllable via an interactive console, Lua scripts, or a remote TCP socket. > Primary repository: ## 2. Features (Partial List) - Wire‑rate 64B packet generation (hardware and core count permitting) - Multi‑port / multi‑queue scaling - IPv4 / IPv6, TCP / UDP, VLAN, GRE, GTP-U support - Packet sequence, range, random, pcap replay and latency modes - Latency & jitter measurement, per‑queue and extended stats pages - Lua scripting (local or remote) + TCP control socket (default port 22022) - Configurable theming and multiple display pages (main, seq, range, rnd, pcap, stats, xstats) - Plugin architecture (`lib/plugin`), capture and pcap dumping - Dynamic rate control and pacing recalculated on size/speed changes ## 3. Quick Start Prerequisites (typical Ubuntu 22.04+): - Latest DPDK (build + install using Meson/Ninja) - libbsd (`sudo apt install libbsd-dev`) - Hugepages configured (e.g. 1G or 2M pages) and NICs bound to `vfio-pci` or `igb_uio` - Python 3.x for helper scripts Clone and build: ```bash git clone https://github.com/pktgen/Pktgen-DPDK.git cd Pktgen-DPDK meson setup builddir meson compile -C builddir ``` Initial device setup (only once per boot) then run a config: ```bash sudo ./tools/run.py -s default # bind devices & prepare environment sudo ./tools/run.py default # launch using cfg/default.cfg ``` Minimal manual run (no helper script) example (adjust cores/ports): ```bash sudo ./builddir/app/pktgen -l 0-3 -n 4 -- -P -m "[1:2].0" -T ``` ## 4. Building (Details) Pktgen uses Meson/Ninja. A convenience `Makefile` and `tools/pktgen-build.sh` wrap standard steps. 1. Build and install DPDK so `libdpdk.pc` is installed (often under `/usr/local/lib/x86_64-linux-gnu/pkgconfig`). 1. Export or append to `PKG_CONFIG_PATH` if that path is non‑standard: ```bash export PKG_CONFIG_PATH=/usr/local/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH ``` 1. Build Pktgen: ```bash meson setup builddir meson compile -C builddir ``` Or use: ```bash make # uses Meson/Ninja under the hood make rebuild # clean reconfigure & build make rebuildlua ``` 1. (Optional) Install Pktgen artifacts via Meson if desired. Troubleshooting hints: - If runtime fails to find DPDK libs: run `sudo ldconfig` or add the library path to `/etc/ld.so.conf.d/`. - Enable IOMMU for vfio: edit GRUB adding `intel_iommu=on` (or `amd_iommu=on`) then `update-grub` and reboot. ## 5. Configuration Files (`cfg/`) Configuration files are Python fragments consumed by `tools/run.py`. They define two dictionaries: `setup` (device binding, privilege wrapper) and `run` (execution parameters). Trimmed example: ```python description = 'Simple default configuration' setup = { 'devices': ( '81:00.0','81:00.1' ), 'uio': 'vfio-pci' } run = { 'app_name': 'pktgen', 'cores': '14,15-16', # control lcore, then worker/core ranges 'map': ('[15:16].0',), # tx:rx core pair -> port 'opts': ('-T','-P'), # -T: color theme, -P: promiscuous 'theme': 'themes/black-yellow.theme' } ``` Common keys (not exhaustive): - `devices`: PCI BDFs to bind. - `blocklist`: exclude listed devices. - `cores`: control and worker core list/ranges. - `map`: mapping of core pairs to ports `[tx:rx].port`. - `opts`: extra runtime flags passed after `--`. - `nrank`, `proc`, `log`, `prefix`: process / logging / multi‑process tuning. See existing examples in `cfg/` (e.g. `default.cfg`, `two-ports.cfg`, `pktgen-1.cfg`, `pktgen-2.cfg`). ## 6. Configuration Key Reference | Key | Location | Type | Example | Description | |-----|----------|------|---------|-------------| | devices | setup | tuple/list | `('81:00.0','81:00.1')` | PCI BDFs to bind to DPDK | | uio | setup | string | `vfio-pci` | Kernel driver to bind (vfio-pci / igb_uio / uio_pci_generic) | | exec | setup/run | tuple | `('sudo')` | Wrapper for privileged execution | | app_name | run | string | `pktgen` | Binary name; resolved via `app_path` list | | app_path | run | tuple/list | `('./app/%(target)s/%(app_name)s', ...)` | Candidate paths to locate binary | | cores | run | string | `14,15-16` | Control + worker cores; ranges and commas allowed | | map | run | tuple/list | `('[15:16].0',)` | TX:RX core pair mapped to port id | | opts | run | tuple/list | `('-T','-P')` | Extra runtime flags passed after `--` | | theme | run | string | `themes/black-yellow.theme` | Color/theme selection | | blocklist | run | tuple/list | `('81:00.2',)` | Exclude listed PCI devices | | nrank | run | string/int | `4` | Multi-process ranking parameter (advanced) | | proc | run | string | `auto` | Process type / role selection | | log | run | string/int | `7` | Log verbosity level | | prefix | run | string | `pg` | DPDK shared resource (memzone) prefix | > Not all keys are required; unused advanced keys can be omitted. Refer to examples in `cfg/` for patterns. ## 7. Runtime Modes & Pages Modes: single (default), sequence, range, random, pcap replay, latency. Display pages correspond to configuration areas: `page main|seq|range|rnd|pcap|stats|xstats`. Each mode maintains separate packet template buffers—configure the active mode explicitly. ## 8. Automation & Remote Control Pktgen exposes a TCP socket (default port `22022`) offering a Lua REPL‑like interface (no prompt). Examples: Interactive with socat: ```bash socat -d -d READLINE TCP4:localhost:22022 ``` Run a Lua script remotely: ```bash socat - TCP4:localhost:22022 < test/hello-world.lua ``` Single command: ```bash echo "f,e=loadfile('test/hello-world.lua'); f();" | socat - TCP4:localhost:22022 ``` Example script (`test/hello-world.lua`): ```lua package.path = package.path .. ";?.lua;test/?.lua;app/?.lua;" printf("Lua Version: %s\n", pktgen.info.Lua_Version) printf("Pktgen Version: %s\n", pktgen.info.Pktgen_Version) printf("Pktgen Copyright: %s\n", pktgen.info.Pktgen_Copyright) printf("Pktgen Authors: %s\n", pktgen.info.Pktgen_Authors) printf("\nHello World!!!!\n") ``` ## 9. Advanced Topics - Multiple instances: see `pktgen-1.cfg` / `pktgen-2.cfg` for running concurrently (ensure isolated devices/cores). - Themes: located under `themes/`, selected via `-T` or config `theme` key. - Latency: latency packets can be injected in any mode; view stats on `page stats` / latency display. - Capture & PCAP: capture to pcap files or replay existing pcaps (`pcap/` directory contains samples). - Performance tuning: pin isolated cores, match NUMA locality (ports & mempools), ensure sufficient mbufs, verify TSC stability. - Plugins: extend via modules under `lib/plugin`. ## 10. Contributing Please fork and submit pull requests via GitHub. Patches sent to the DPDK mailing list are not accepted for this repo. For detailed guidelines (coding style, commit message format, documentation rules) see [`CONTRIBUTING.md`](./CONTRIBUTING.md). Extended Markdown formatting conventions are documented in [`docs/STYLE.md`](./docs/STYLE.md). Community expectations and reporting process: see [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md). Report issues / feature requests: When filing issues include: - Pktgen version (`cat VERSION` → current: 25.08.0) - DPDK version & build config - NIC model(s) & driver - Reproduction steps + minimal config ### 10.1 Markdown Linting (Pre-Commit Hook) Documentation style is enforced via markdownlint. Enable the hook: ```bash ln -sf ../../.githooks/pre-commit .git/hooks/pre-commit chmod +x .githooks/pre-commit ``` Requirements: ```bash node --version # ensure Node.js installed npm install --no-save markdownlint-cli2 # optional; hook auto-installs if missing ``` On commit, staged `*.md` files are linted. If violations are found the commit is aborted; some fixable rules may be auto-corrected—re-add and recommit. Staged C source/header files (`*.c`, `*.h`) are auto-formatted with `clang-format` (if present) before commit. ## 11. License SPDX-License-Identifier: BSD-3-Clause Copyright © 2010-2026 Intel Corporation Full license text is available in [`LICENSE`](./LICENSE). ## 12. Related Links - Documentation: - Install guide (legacy / extended details): [`INSTALL.md`](./INSTALL.md) - Example pcaps: `pcap/` ## 13. Acknowledgments Created and maintained by Keith Wiles @ Intel Corporation with contributions from the community. --- If this tool helps your testing, consider starring the project or contributing improvements. [DPDK]: https://www.dpdk.org/ ================================================ FILE: VERSION ================================================ 26.03.0 ================================================ FILE: app/.gitignore ================================================ pktgen t/libtap/libtap.a t/libtap/tap.o build/ t/*.t !t/*.pl.t ================================================ FILE: app/cli-functions.c ================================================ /*- * Copyright(c) <2020-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2018 by Keith Wiles @ intel.com */ #include "cli-functions.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pktgen.h" #include #include #include #include "copyright_info.h" #include "pktgen-cmds.h" #include "pktgen-main.h" #include "lpktgenlib.h" #include "pktgen-display.h" #include "pktgen-random.h" #include "pktgen-log.h" #include "pg_ether.h" #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) #include #include #endif #include static inline uint16_t valid_gtpu_teid(port_info_t *info __rte_unused, char *val) { uint16_t gtpu_teid; gtpu_teid = atoi(val); return gtpu_teid; } /**********************************************************/ static const char *title_help[] = { " *** Pktgen Help information ***", "", NULL, }; static const char *status_help[] = { "", " Flags: P------------------------- - Promiscuous mode enabled", " E - ICMP Echo enabled", " B - Bonding enabled LACP 802.3ad", " I - Process packets on input enabled", " L - Sends latency packets", " i - Randomizing the source IP address", " p - Randomizing the source port", " R - Perform bit randomization (`rnd` page)", " C - Capture received packets", " <.......> - Modes: Single, pcap, sequence, latency, " "Rate", " <......> - Modes: VLAN, VxLAN, MPLS, QnQ, GRE IPv4, GRE ETH", "Notes: - Use enable|disable or on|off to set the state.", " - a list of ports (no spaces) as 2,4,6-9,12 or 3-5,8 or 5 or the word " "'all'", " Colors best seen on a black background for now", CLI_HELP_PAUSE, NULL}; #define SMMI "%|start|minimum|maximum|increment|min|max|inc" // clang-format off static struct cli_map range_map[] = { {20, "range %P dst mac " SMMI " %m"}, {21, "range %P src mac " SMMI " %m"}, {22, "range %P dst mac %m %m %m %m"}, {23, "range %P src mac %m %m %m %m"}, {30, "range %P dst ip " SMMI " %4"}, {31, "range %P src ip " SMMI " %4"}, {32, "range %P dst ip %4 %4 %4 %4"}, {33, "range %P src ip %4 %4 %4 %4"}, {34, "range %P dst ip " SMMI " %6"}, {35, "range %P src ip " SMMI " %6"}, {36, "range %P dst ip %6 %6 %6 %6"}, {37, "range %P src ip %6 %6 %6 %6"}, {40, "range %P proto %|tcp|udp"}, {41, "range %P type %|ipv4|ipv6"}, {42, "range %P tcp %|flag|flags %c"}, {44, "range %P tcp seq %d %d %d %d"}, {45, "range %P tcp ack %d %d %d %d"}, {46, "range %P tcp seq " SMMI " %d"}, {47, "range %P tcp ack " SMMI " %d"}, {50, "range %P dst port " SMMI " %d"}, {51, "range %P src port " SMMI " %d"}, {52, "range %P dst port %d %d %d %d"}, {53, "range %P src port %d %d %d %d"}, {55, "range %P ttl " SMMI " %b"}, {56, "range %P ttl %b %b %b %b"}, {60, "range %P vlan " SMMI " %d"}, {61, "range %P vlan %d %d %d %d"}, {70, "range %P size " SMMI " %d"}, {71, "range %P size %d %d %d %d"}, {80, "range %P mpls entry %h"}, {85, "range %P qinq index %d %d"}, {90, "range %P gre key %d"}, {91, "range %P gre_key %d"}, {100, "range %P gtpu " SMMI " %d"}, {101, "range %P gtpu %d %d %d %d"}, {160, "range %P cos " SMMI " %d"}, {161, "range %P cos %d %d %d %d"}, {170, "range %P tos " SMMI " %d"}, {171, "range %P tos %d %d %d %d"}, {172, "range %P hop_limits " SMMI " %b"}, {173, "range %P hop_limits %d %d %d %d"}, {174, "range %P traffic_class " SMMI " %d"}, {175, "range %P traffic_class %d %d %d %d"}, {-1, NULL} }; // clang-format on static const char *range_help[] = { "", " -- Setup the packet range values --", " note: SMMI = start|min|max|inc (start, minimum, maximum, increment)", "", "range src|dst mac - Set destination/source MAC address", " e.g: range 0 src mac start 00:00:00:00:00:00", " range 0 dst mac max 00:12:34:56:78:90", " or range 0 src mac 00:00:00:00:00:00 00:00:00:00:00:00 00:12:34:56:78:90 " "00:00:00:01:01:01", "range src|dst ip - Set source IP start address", " e.g: range 0 dst ip start 0.0.0.0", " range 0 dst ip min 0.0.0.0", " range 0 dst ip max 1.2.3.4", " range 0 dst ip inc 0.0.1.0", " or range 0 dst ip 0.0.0.0 0.0.0.0 1.2.3.4 0.0.1.0", "range type ipv4|ipv6 - Set the range packet type to IPv4 or IPv6", "range proto tcp|udp - Set the IP protocol type", "range tcp flags - Set comma delimited TCP flags: " "cwr,ece,urg,ack,psh,rst,syn,fin,clr", "range tcp seq - Set the TCP sequence number", " or range tcp seq ", "range tcp ack - Set the TCP acknowledge number", " or range tcp ack ", "range src|dst port - Set UDP/TCP source/dest port number", " or range src|dst port ", "range vlan - Set vlan id start address", " or range vlan ", "range size - Set pkt size start address", " or range size ", "range teid - Set TEID value", " or range teid ", "range mpls entry - Set MPLS entry value", "range qinq index - Set QinQ index values", "range gre key - Set GRE key value", "range cos - Set cos value", "range tos - Set tos value", "range ttl - Set TTL", "range hop_limits - Set Hop Limits", "range traffic_class - Set Traffic Class value", CLI_HELP_PAUSE, NULL}; static int range_cmd(int argc, char **argv) { struct cli_map *m; portlist_t portlist; struct pg_ipaddr ip; struct rte_ether_addr mac[4]; char *what, *p; const char *val; m = cli_mapping(range_map, argc, argv); if (!m) return cli_cmd_error("Range command error", "Range", argc, argv); portlist_parse(argv[1], pktgen.nb_ports, &portlist); what = argv[4]; val = (const char *)argv[5]; switch (m->index) { case 20: if (pg_ether_aton(val, &mac[0]) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", val); break; } foreach_port(portlist, range_set_dest_mac(pinfo, what, &mac[0])); break; case 21: if (pg_ether_aton(val, &mac[0]) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", val); break; } foreach_port(portlist, range_set_src_mac(pinfo, what, &mac[0])); break; case 22: if (pg_ether_aton(argv[4], &mac[0]) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", argv[4]); break; } if (pg_ether_aton(argv[5], &mac[1]) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", argv[5]); break; } if (pg_ether_aton(argv[6], &mac[2]) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", argv[6]); break; } if (pg_ether_aton(argv[7], &mac[3]) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", argv[7]); break; } foreach_port(portlist, range_set_dest_mac(pinfo, "start", &mac[0]); range_set_dest_mac(pinfo, "min", &mac[1]); range_set_dest_mac(pinfo, "max", &mac[2]); range_set_dest_mac(pinfo, "inc", &mac[3])); break; case 23: if (pg_ether_aton(argv[4], &mac[0]) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", argv[4]); break; } if (pg_ether_aton(argv[5], &mac[1]) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", argv[5]); break; } if (pg_ether_aton(argv[6], &mac[2]) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", argv[6]); break; } if (pg_ether_aton(argv[7], &mac[3]) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", argv[7]); break; } foreach_port(portlist, range_set_src_mac(pinfo, "start", &mac[0]); range_set_src_mac(pinfo, "min", &mac[1]); range_set_src_mac(pinfo, "max", &mac[2]); range_set_src_mac(pinfo, "inc", &mac[3])); break; case 30: /* Remove the /XX mask value is supplied */ p = strchr(argv[4], '/'); if (p) *p = '\0'; _atoip(val, 0, &ip, sizeof(ip)); foreach_port(portlist, range_set_dst_ip(pinfo, what, &ip)); break; case 31: /* Remove the /XX mask value is supplied */ p = strchr(argv[4], '/'); if (p) *p = '\0'; _atoip(argv[5], 0, &ip, sizeof(ip)); foreach_port(portlist, range_set_src_ip(pinfo, what, &ip)); break; case 32: // clang-format off foreach_port(portlist, _atoip(argv[4], PG_IPADDR_V4, &ip, sizeof(ip)); range_set_dst_ip(pinfo, (char *)(uintptr_t)"start", &ip); _atoip(argv[5], PG_IPADDR_V4, &ip, sizeof(ip)); range_set_dst_ip(pinfo, (char *)(uintptr_t)"min", &ip); _atoip(argv[6], PG_IPADDR_V4, &ip, sizeof(ip)); range_set_dst_ip(pinfo, (char *)(uintptr_t)"max", &ip); _atoip(argv[7], PG_IPADDR_V4, &ip, sizeof(ip)); range_set_dst_ip(pinfo, (char *)(uintptr_t)"inc", &ip)); // clang-format on break; case 33: // clang-format off foreach_port(portlist, _atoip(argv[4], PG_IPADDR_V4, &ip, sizeof(ip)); range_set_src_ip(pinfo, (char *)(uintptr_t)"start", &ip); _atoip(argv[5], PG_IPADDR_V4, &ip, sizeof(ip)); range_set_src_ip(pinfo, (char *)(uintptr_t)"min", &ip); _atoip(argv[6], PG_IPADDR_V4, &ip, sizeof(ip)); range_set_src_ip(pinfo, (char *)(uintptr_t)"max", &ip); _atoip(argv[7], PG_IPADDR_V4, &ip, sizeof(ip)); range_set_src_ip(pinfo, (char *)(uintptr_t)"inc", &ip)); // clang-format on break; case 34: /* Remove the /XX mask value is supplied */ p = strchr(argv[4], '/'); if (p) *p = '\0'; _atoip(val, 0, &ip, sizeof(ip)); foreach_port(portlist, range_set_dst_ip(pinfo, what, &ip)); break; case 35: /* Remove the /XX mask value is supplied */ p = strchr(argv[4], '/'); if (p) *p = '\0'; _atoip(argv[5], 0, &ip, sizeof(ip)); foreach_port(portlist, range_set_src_ip(pinfo, what, &ip)); break; case 36: // clang-format off foreach_port(portlist, _atoip(argv[4], PG_IPADDR_V6, &ip, sizeof(ip)); range_set_dst_ip(pinfo, (char *)(uintptr_t)"start", &ip); _atoip(argv[5], PG_IPADDR_V6, &ip, sizeof(ip)); range_set_dst_ip(pinfo, (char *)(uintptr_t)"min", &ip); _atoip(argv[6], PG_IPADDR_V6, &ip, sizeof(ip)); range_set_dst_ip(pinfo, (char *)(uintptr_t)"max", &ip); _atoip(argv[7], PG_IPADDR_V6, &ip, sizeof(ip)); range_set_dst_ip(pinfo, (char *)(uintptr_t)"inc", &ip)); // clang-format on break; case 37: // clang-format off foreach_port(portlist, _atoip(argv[4], PG_IPADDR_V6, &ip, sizeof(ip)); range_set_src_ip(pinfo, (char *)(uintptr_t)"start", &ip); _atoip(argv[5], PG_IPADDR_V6, &ip, sizeof(ip)); range_set_src_ip(pinfo, (char *)(uintptr_t)"min", &ip); _atoip(argv[6], PG_IPADDR_V6, &ip, sizeof(ip)); range_set_src_ip(pinfo, (char *)(uintptr_t)"max", &ip); _atoip(argv[7], PG_IPADDR_V6, &ip, sizeof(ip)); range_set_src_ip(pinfo, (char *)(uintptr_t)"inc", &ip)); // clang-format on break; case 40: foreach_port(portlist, range_set_proto(pinfo, argv[3])); break; case 41: foreach_port(portlist, range_set_pkt_type(pinfo, argv[3])); break; case 42: foreach_port(portlist, range_set_tcp_flags(pinfo, argv[4])); break; case 44: // clang-format off foreach_port(portlist, range_set_tcp_seq(pinfo, (char *)(uintptr_t)"start", atoi(argv[4])); range_set_tcp_seq(pinfo, (char *)(uintptr_t)"min", atoi(argv[5])); range_set_tcp_seq(pinfo, (char *)(uintptr_t)"max", atoi(argv[6])); range_set_tcp_seq(pinfo, (char *)(uintptr_t)"inc", atoi(argv[7]))); // clang-format on break; case 45: // clang-format off foreach_port(portlist, range_set_tcp_ack(pinfo, (char *)(uintptr_t)"start", atoi(argv[4])); range_set_tcp_ack(pinfo, (char *)(uintptr_t)"min", atoi(argv[5])); range_set_tcp_ack(pinfo, (char *)(uintptr_t)"max", atoi(argv[6])); range_set_tcp_ack(pinfo, (char *)(uintptr_t)"inc", atoi(argv[7]))); // clang-format on break; case 46: foreach_port(portlist, range_set_tcp_seq(pinfo, what, atoi(val))); break; case 47: foreach_port(portlist, range_set_tcp_ack(pinfo, what, atoi(val))); break; case 50: foreach_port(portlist, range_set_dst_port(pinfo, what, atoi(val))); break; case 51: foreach_port(portlist, range_set_src_port(pinfo, what, atoi(val))); break; case 52: // clang-format off foreach_port(portlist, range_set_dst_port(pinfo, (char *)(uintptr_t)"start", atoi(argv[4])); range_set_dst_port(pinfo, (char *)(uintptr_t)"min", atoi(argv[5])); range_set_dst_port(pinfo, (char *)(uintptr_t)"max", atoi(argv[6])); range_set_dst_port(pinfo, (char *)(uintptr_t)"inc", atoi(argv[7]))); // clang-format on break; case 53: // clang-format off foreach_port(portlist, range_set_src_port(pinfo, (char *)(uintptr_t)"start", atoi(argv[4])); range_set_src_port(pinfo, (char *)(uintptr_t)"min", atoi(argv[5])); range_set_src_port(pinfo, (char *)(uintptr_t)"max", atoi(argv[6])); range_set_src_port(pinfo, (char *)(uintptr_t)"inc", atoi(argv[7]))); // clang-format on break; case 55: foreach_port(portlist, range_set_ttl(pinfo, argv[3], atoi(argv[4]))); break; case 56: // clang-format off foreach_port(portlist, range_set_ttl(pinfo, (char *)(uintptr_t)"start", atoi(argv[3])); range_set_ttl(pinfo, (char *)(uintptr_t)"min", atoi(argv[4])); range_set_ttl(pinfo, (char *)(uintptr_t)"max", atoi(argv[5])); range_set_ttl(pinfo, (char *)(uintptr_t)"inc", atoi(argv[6]))); // clang-format on break; case 60: foreach_port(portlist, range_set_vlan_id(pinfo, argv[3], atoi(what))); break; case 61: // clang-format off foreach_port(portlist, range_set_vlan_id(pinfo, (char *)(uintptr_t)"start", atoi(argv[3])); range_set_vlan_id(pinfo, (char *)(uintptr_t)"min", atoi(argv[4])); range_set_vlan_id(pinfo, (char *)(uintptr_t)"max", atoi(argv[5])); range_set_vlan_id(pinfo, (char *)(uintptr_t)"inc", atoi(argv[6]))); // clang-format on break; case 70: foreach_port(portlist, range_set_pkt_size(pinfo, argv[3], strcmp("inc", argv[3]) ? atoi(argv[4]) : atoi(argv[3]))); break; case 71: // clang-format off foreach_port(portlist, range_set_pkt_size(pinfo, (char *)(uintptr_t)"start", atoi(argv[3])); range_set_pkt_size(pinfo, (char *)(uintptr_t)"min", atoi(argv[4])); range_set_pkt_size(pinfo, (char *)(uintptr_t)"max", atoi(argv[5])); range_set_pkt_size(pinfo, (char *)(uintptr_t)"inc", atoi(argv[6]));); // clang-format on break; case 80: foreach_port(portlist, range_set_mpls_entry(pinfo, strtoul(what, NULL, 16))); break; case 85: foreach_port(portlist, range_set_qinqids(pinfo, atoi(what), atoi(val))); break; case 90: case 91: foreach_port(portlist, range_set_gre_key(pinfo, strtoul(what, NULL, 10))); break; case 100: foreach_port(portlist, range_set_gtpu_teid(pinfo, argv[3], strcmp("inc", argv[3]) ? valid_gtpu_teid(pinfo, what) : atoi(what))); break; case 101: // clang-format off foreach_port( portlist, range_set_gtpu_teid(pinfo, (char *)(uintptr_t)"start", valid_gtpu_teid(pinfo, argv[3])); range_set_gtpu_teid(pinfo, (char *)(uintptr_t)"min", valid_gtpu_teid(pinfo, argv[4])); range_set_gtpu_teid(pinfo, (char *)(uintptr_t)"max", valid_gtpu_teid(pinfo, argv[5])); range_set_gtpu_teid(pinfo, (char *)(uintptr_t)"inc", atoi(argv[6]));); // clang-format on break; case 160: foreach_port(portlist, range_set_cos_id(pinfo, argv[3], atoi(what))); break; case 161: // clang-format off foreach_port(portlist, range_set_cos_id(pinfo, (char *)(uintptr_t)"start", atoi(argv[3])); range_set_cos_id(pinfo, (char *)(uintptr_t)"min", atoi(argv[4])); range_set_cos_id(pinfo, (char *)(uintptr_t)"max", atoi(argv[5])); range_set_cos_id(pinfo, (char *)(uintptr_t)"inc", atoi(argv[6]));); // clang-format on break; case 170: foreach_port(portlist, range_set_tos_id(pinfo, argv[3], atoi(what))); break; case 171: // clang-format off foreach_port(portlist, range_set_tos_id(pinfo, (char *)(uintptr_t)"start", atoi(argv[3])); range_set_tos_id(pinfo, (char *)(uintptr_t)"min", atoi(argv[4])); range_set_tos_id(pinfo, (char *)(uintptr_t)"max", atoi(argv[5])); range_set_tos_id(pinfo, (char *)(uintptr_t)"inc", atoi(argv[6]))); // clang-format on break; case 172: foreach_port(portlist, range_set_hop_limits(pinfo, argv[3], atoi(what))); break; case 173: // clang-format off foreach_port(portlist, range_set_hop_limits(pinfo, (char *)(uintptr_t)"start", atoi(argv[3])); range_set_hop_limits(pinfo, (char *)(uintptr_t)"min", atoi(argv[4])); range_set_hop_limits(pinfo, (char *)(uintptr_t)"max", atoi(argv[5])); range_set_hop_limits(pinfo, (char *)(uintptr_t)"inc", atoi(argv[6]))); // clang-format on break; case 174: foreach_port(portlist, range_set_traffic_class(pinfo, argv[3], atoi(what))); break; case 175: // clang-format off foreach_port(portlist, range_set_traffic_class(pinfo, (char *)(uintptr_t)"start", atoi(argv[3])); range_set_traffic_class(pinfo, (char *)(uintptr_t)"min", atoi(argv[4])); range_set_traffic_class(pinfo, (char *)(uintptr_t)"max", atoi(argv[5])); range_set_traffic_class(pinfo, (char *)(uintptr_t)"inc", atoi(argv[6]))); // clang-format on break; default: return cli_cmd_error("Range command error", "Range", argc, argv); } pktgen_update_display(); return 0; } #define set_types \ "count|" /* 0 */ \ "size|" /* 1 */ \ "rate|" /* 2 */ \ "burst|" /* 3 */ \ "tx_cycles|" /* 4 */ \ "sport|" /* 5 */ \ "dport|" /* 6 */ \ "prime|" /* 7 */ \ "dump|" /* 8 */ \ "vlan|" /* 9 */ \ "vlanid|" /* 10 */ \ "seq_cnt|" /* 11 */ \ "seqCnt|" /* 12 */ \ "seqcnt|" /* 13 */ \ "ttl|" /* 14 */ \ "txburst|" /* 15 */ \ "rxburst" /* 16 */ // clang-format off static struct cli_map set_map[] = { {10, "set %P %|" set_types " %d"}, {11, "set %P jitter %D"}, {20, "set %P type %|arp|ipv4|ipv6|ip4|ip6|vlan"}, {21, "set %P proto %|udp|tcp|icmp"}, {22, "set %P src mac %m"}, {23, "set %P dst mac %m"}, {24, "set %P pattern %|abc|none|user|zero"}, {25, "set %P user pattern %s"}, {30, "set %P src ip %4"}, {31, "set %P dst ip %4"}, {32, "set %P src ip %6"}, {33, "set %P dst ip %6"}, {34, "set %P tcp %|flag|flags %c"}, {36, "set %P tcp seq %d"}, {37, "set %P tcp ack %d"}, {40, "set ports_per_page %d"}, {50, "set %P qinqids %d %d"}, {60, "set %P rnd %d %d %s"}, {70, "set %P cos %d"}, {80, "set %P tos %d"}, {90, "set %P vxlan %h %d %d"}, {100, "set %P latsampler %|simple|poisson %d %d %s"}, {-1, NULL} }; // clang format on static const char *set_help[] = { "", " note: - a list of ports (no spaces) e.g. 2,4,6-9,12 or the word " "'all'", "set count - number of packets to transmit", "set size - size of the packet to transmit", "set rate - Packet rate in percentage", "set txburst|burst - number of packets in a TX burst", "set rxburst - number of packets in a RX burst", "set tx_cycles - DEBUG to set the number of cycles per TX burst", "set sport - Source port number for UDP/TCP", "set dport - Destination port number for UDP/TCP", "set ttl - Set the TTL value for the single port more", "set seq_cnt|seqcnt|seqCnt ", " - Set the number of packet in the sequence to send [0-16]", "set prime - Set the number of packets to send on prime command", "set dump - Dump the next 1-32 received packets to the screen", " Dumped packets are in the log, use 'page log' to view", "set vlan|vlanid - Set the VLAN ID value for the portlist", "set jitter - Set the jitter threshold in micro-seconds", "set src|dst mac - Set MAC addresses 00:11:22:33:44:55 or 0011:2233:4455 " "format", "set type ipv4|ipv6|vlan|arp - Set the packet type to IPv4 or IPv6 or VLAN", "set proto udp|tcp|icmp - Set the packet protocol to UDP or TCP or ICMP per port", "set pattern - Set the fill pattern type", " type - abc - Default pattern of abc string", " none - No fill pattern, maybe random data", " zero - Fill of zero bytes", " user - User supplied string of max 16 bytes", "set user pattern - A 16 byte string, must set 'pattern user' command", "set [src|dst] ip ipaddr - Set IP addresses, Source must include network mask e.g. " "10.1.2.3/24", "set tcp flags - Set comma delimited TCP flags: cwr,ece,urg,ack,psh,rst,syn,fin,clr", "set tcp seq - Set the TCP sequence number", "set tcp ack - Set the TCP acknowledge number", "set qinqids - Set the Q-in-Q ID's for the portlist", "set rnd - Set random mask for all transmitted packets from " "portlist", " idx: random mask index slot", " off: offset in bytes to apply mask value", " mask: up to 32 bit long mask specification (empty to disable):", " 0: bit will be 0", " 1: bit will be 1", " .: bit will be ignored (original value is retained)", " X: bit will get random value", "set cos - Set the CoS value for the portlist", "set tos - Set the ToS value for the portlist", "set vxlan - Set the vxlan values", "set latsampler [simple|poisson] - Set latency " "sampler parameters", " num-samples: number of samples.", " rate: sampling rate i.e., samples per second.", " outfile: path to output file to dump all sampled latencies", "set ports_per_page - Set ports per page value 1 - 6", CLI_HELP_PAUSE, NULL}; static int set_cmd(int argc, char **argv) { portlist_t portlist; struct rte_ether_addr mac; char *what, *p; int value, n; struct cli_map *m; struct pg_ipaddr ip; uint16_t id1, id2; uint32_t u1, u2; int ip_ver; m = cli_mapping(set_map, argc, argv); if (!m) return cli_cmd_error("Set command is invalid", "Set", argc, argv); portlist_parse(argv[1], pktgen.nb_ports, &portlist); what = argv[2]; value = atoi(argv[3]); switch (m->index) { case 10: n = cli_map_list_search(m->fmt, argv[2], 2); foreach_port(portlist, _do(switch (n) { case 0: single_set_tx_count(pinfo, value); break; case 1: single_set_pkt_size(pinfo, atoi(argv[3])); break; case 2: single_set_tx_rate(pinfo, argv[3]); break; case 3: single_set_tx_burst(pinfo, value); break; case 4: debug_set_tx_cycles(pinfo, value); break; case 5: single_set_port_value(pinfo, what[0], value); break; case 6: single_set_port_value(pinfo, what[0], value); break; case 7: pktgen_set_port_prime(pinfo, value); break; case 8: debug_set_port_dump(pinfo, value); break; case 9: /* vlanid and vlan are valid */ case 10: single_set_vlan_id(pinfo, value); break; case 11: /* FALLTHRU */ case 12: /* FALLTHRU */ case 13: pktgen_set_port_seqCnt(pinfo, value); break; case 14: single_set_ttl_value(pinfo, value); break; case 15: single_set_tx_burst(pinfo, value); break; case 16: single_set_rx_burst(pinfo, value); break; default: return cli_cmd_error("Set command is invalid", "Set", argc, argv); })); break; case 11: foreach_port(portlist, single_set_jitter(pinfo, strtoull(argv[3], NULL, 0))); break; case 20: foreach_port(portlist, single_set_pkt_type(pinfo, argv[3])); break; case 21: foreach_port(portlist, single_set_proto(pinfo, argv[3])); break; case 22: if (pg_ether_aton(argv[4], &mac) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", argv[4]); break; } foreach_port(portlist, single_set_src_mac(pinfo, &mac)); break; case 23: if (pg_ether_aton(argv[4], &mac) == NULL) { cli_printf("Failed to parse MAC address from (%s)\n", argv[4]); break; } foreach_port(portlist, single_set_dst_mac(pinfo, &mac)); break; case 24: foreach_port(portlist, pattern_set_type(pinfo, argv[3])); break; case 25: foreach_port(portlist, pattern_set_user_pattern(pinfo, argv[4])); break; case 30: p = strchr(argv[4], '/'); if (!p) cli_printf("src IP address should contain subnet value, default /32 for IPv4\n"); ip_ver = _atoip(argv[4], PG_IPADDR_V4 | PG_IPADDR_NETWORK, &ip, sizeof(ip)); foreach_port(portlist, single_set_ipaddr(pinfo, 's', &ip, ip_ver)); break; case 31: /* Remove the /XX mask value if supplied */ p = strchr(argv[4], '/'); if (p) { cli_printf("Subnet mask not required, removing subnet mask value\n"); *p = '\0'; } ip_ver = _atoip(argv[4], PG_IPADDR_V4, &ip, sizeof(ip)); foreach_port(portlist, single_set_ipaddr(pinfo, 'd', &ip, ip_ver)); break; case 32: p = strchr(argv[4], '/'); if (!p) cli_printf("src IP address should contain subnet value, default /128 for IPv6\n"); ip_ver = _atoip(argv[4], PG_IPADDR_V6 | PG_IPADDR_NETWORK, &ip, sizeof(ip)); foreach_port(portlist, single_set_ipaddr(pinfo, 's', &ip, ip_ver)); break; case 33: /* Remove the /XX mask value if supplied */ p = strchr(argv[4], '/'); if (p) { cli_printf("Subnet mask not required, removing subnet mask value\n"); *p = '\0'; } ip_ver = _atoip(argv[4], PG_IPADDR_V6, &ip, sizeof(ip)); foreach_port(portlist, single_set_ipaddr(pinfo, 'd', &ip, ip_ver)); break; case 34: foreach_port(portlist, single_set_tcp_flags(pinfo, argv[4])); break; case 36: foreach_port(portlist, single_set_tcp_seq(pinfo, atoi(argv[4]))); break; case 37: foreach_port(portlist, single_set_tcp_ack(pinfo, atoi(argv[4]))); break; case 40: pktgen_set_page_size(atoi(argv[2])); break; case 50: id1 = strtol(argv[3], NULL, 0); id2 = strtol(argv[4], NULL, 0); foreach_port(portlist, single_set_qinqids(pinfo, id1, id2)); break; case 60: { char mask[34] = {0}, *m; char cb; id1 = strtol(argv[3], NULL, 0); id2 = strtol(argv[4], NULL, 0); m = argv[5]; if (strcmp(m, "off")) { int idx; /* Filter invalid characters from provided mask. This way the user can * more easily enter long bitmasks, using for example '_' as a separator * every 8 bits. */ for (n = 0, idx = 0; (idx < 32) && ((cb = m[n]) != '\0'); n++) if ((cb == '0') || (cb == '1') || (cb == '.') || (cb == 'X') || (cb == 'x')) mask[idx++] = cb; } foreach_port(portlist, enable_random(pinfo, pktgen_set_random_bitfield(pinfo->rnd_bitfields, id1, id2, mask) ? ENABLE_STATE : DISABLE_STATE)); } break; case 70: id1 = strtol(argv[3], NULL, 0); foreach_port(portlist, single_set_cos(pinfo, id1)); break; case 80: id1 = strtol(argv[3], NULL, 0); foreach_port(portlist, single_set_tos(pinfo, id1)); break; case 90: id1 = strtol(argv[3], NULL, 0); id2 = strtol(argv[4], NULL, 0); u1 = strtol(argv[5], NULL, 0); foreach_port(portlist, single_set_vxlan(pinfo, id1, id2, u1)); break; case 100: u1 = strtol(argv[4], NULL, 0); u2 = strtol(argv[5], NULL, 0); foreach_port(portlist, single_set_latsampler_params(pinfo, argv[3], u1, u2, argv[6])); break; default: return cli_cmd_error("Command invalid", "Set", argc, argv); } pktgen_update_display(); return 0; } // clang-format off static struct cli_map pcap_map[] = { {10, "pcap %D"}, {20, "pcap show"}, {21, "pcap show all"}, {30, "pcap filter %P %s"}, {-1, NULL} }; // clang-format on static const char *pcap_help[] = { "", "pcap show - Show PCAP information", "pcap - Move the PCAP file index to the given packet number,\n " " 0 - rewind, -1 - end of file", "pcap filter - PCAP filter string to filter packets on receive", CLI_HELP_PAUSE, NULL}; static int pcap_cmd(int argc, char **argv) { struct cli_map *m; pcap_info_t *pcap; uint32_t max_cnt; uint32_t value; portlist_t portlist; port_info_t *pinfo; m = cli_mapping(pcap_map, argc, argv); if (!m) return cli_cmd_error("PCAP command invalid", "PCAP", argc, argv); pinfo = l2p_get_port_pinfo(pktgen.curr_port); switch (m->index) { case 10: pcap = l2p_get_pcap(pinfo->pid); value = strtoul(argv[1], NULL, 10); if (pcap) { max_cnt = pcap->pkt_count; if (value >= max_cnt) pcap->pkt_index = max_cnt - RTE_MIN(PCAP_PAGE_SIZE, (int)max_cnt); else pcap->pkt_index = value; pktgen.flags |= PRINT_LABELS_FLAG; } else pktgen_log_error(" ** PCAP file is not loaded on port %d", pktgen.curr_port); break; case 20: pcap = l2p_get_pcap(pinfo->pid); if (pcap) pktgen_pcap_info(pcap, pktgen.curr_port, 1); else pktgen_log_error(" ** PCAP file is not loaded on port %d", pktgen.curr_port); break; case 21: for (int pid = 0; pid < pktgen.nb_ports; pid++) { pcap = l2p_get_pcap(pid); if (pcap) pktgen_pcap_info(pcap, pid, 1); } break; case 30: portlist_parse(argv[2], pktgen.nb_ports, &portlist); foreach_port(portlist, pcap_filter(pinfo, argv[3])); break; default: return cli_cmd_error("PCAP command invalid", "PCAP", argc, argv); } pktgen_update_display(); return 0; } // clang-format off static struct cli_map start_map[] = { {10, "start %P"}, {20, "stop %P"}, {40, "start %P prime"}, {50, "start %P arp %|request|gratuitous|req|grat"}, {60, "start %P latsampler"}, {70, "stop %P latsampler"}, {-1, NULL} }; // clang-format on static const char *start_help[] = { "", "start - Start transmitting packets", "stop - Stop transmitting packets", "stp - Stop all ports from transmitting", "str - Start all ports transmitting", "start prime - Transmit packets on each port listed. See set prime " "command above", "start arp - Send a ARP type packet", " type - request | gratuitous | req | grat", "start latsampler - Start latency sampler, make sure to set sampling " "parameters before starting", "stop latsampler - Stop latency sampler, dumps to file if specified", CLI_HELP_PAUSE, NULL}; static int start_stop_cmd(int argc, char **argv) { struct cli_map *m; portlist_t portlist; m = cli_mapping(start_map, argc, argv); if (!m) return cli_cmd_error("Start/Stop command invalid", "Start", argc, argv); portlist_parse(argv[1], pktgen.nb_ports, &portlist); switch (m->index) { case 10: foreach_port(portlist, pktgen_start_transmitting(pinfo)); break; case 20: foreach_port(portlist, pktgen_stop_transmitting(pinfo)); break; case 40: foreach_port(portlist, pktgen_prime_ports(pinfo)); break; case 50: if (argv[3][0] == 'g') foreach_port(portlist, pktgen_send_arp_requests(pinfo, GRATUITOUS_ARP)); else foreach_port(portlist, pktgen_send_arp_requests(pinfo, 0)); break; case 60: foreach_port(portlist, pktgen_start_latency_sampler(pinfo)); break; case 70: foreach_port(portlist, pktgen_stop_latency_sampler(pinfo)); break; default: return cli_cmd_error("Start/Stop command invalid", "Start", argc, argv); } pktgen_update_display(); return 0; } // clang-format off static struct cli_map theme_map[] = { {0, "theme"}, {10, "theme %|on|off"}, {20, "theme %s %s %s %s"}, {30, "theme save %s"}, {-1, NULL} }; // clang-format on static const char *theme_help[] = { "", "theme - Set color for item with fg/bg color and attribute value", "theme show - List the item strings, colors and attributes to the " "items", "theme save - Save the current color theme to a file", CLI_HELP_PAUSE, NULL}; static int theme_cmd(int argc, char **argv) { struct cli_map *m; m = cli_mapping(theme_map, argc, argv); if (!m) return cli_cmd_error("Theme command invalid", "Theme", argc, argv); switch (m->index) { case 0: pktgen_theme_show(); break; case 10: pktgen_theme_state(argv[1]); pktgen_clear_display(); break; case 20: pktgen_set_theme_item(argv[1], argv[2], argv[3], argv[4]); break; case 30: pktgen_theme_save(argv[2]); break; default: return cli_help_show_group("Theme"); } return 0; } #define ed_type \ "process|" /* 0 */ \ "mpls|" /* 1 */ \ "qinq|" /* 2 */ \ "gre|" /* 3 */ \ "gre_eth|" /* 4 */ \ "vlan|" /* 5 */ \ "random|" /* 6 */ \ "latency|" /* 7 */ \ "pcap|" /* 8 */ \ "blink|" /* 9 */ \ "icmp|" /* 10 */ \ "range|" /* 11 */ \ "capture|" /* 12 */ \ "bonding|" /* 13 */ \ "vxlan|" /* 14 */ \ "rate|" /* 15 */ \ "rnd_s_ip|" /* 16 */ \ "rnd_s_pt|" /* 17 */ \ "lat" /* 18 */ // clang-format off static struct cli_map enable_map[] = { {10, "enable %P %|" ed_type}, {20, "disable %P %|" ed_type}, {30, "enable %|screen|mac_from_arp"}, {31, "disable %|screen|mac_from_arp"}, {40, "enable clock_gettime"}, {41, "disable clock_gettime"}, {-1, NULL} }; // clang-format off static const char *enable_help[] = { "", "enable|disable process - Enable or Disable processing of ARP/ICMP/IPv4/IPv6 " "packets", "enable|disable mpls - Enable/disable sending MPLS entry in packets", "enable|disable qinq - Enable/disable sending Q-in-Q header in packets", "enable|disable gre - Enable/disable GRE support", "enable|disable gre_eth - Enable/disable GRE with Ethernet frame payload", "enable|disable vlan - Enable/disable VLAN tagging", "enable|disable rnd_s_ip - Enable/disable randomizing the source IP address on " "every packet", "enable|disable rnd_s_pt - Enable/disable randomizing the source port on every packet", "enable|disable random - Enable/disable Random packet support through the `rnd` page", "enable|disable latency - Enable/disable latency testing", "enable|disable pcap - Enable or Disable sending pcap packets on a portlist", "enable|disable blink - Blink LED on port(s)", "enable|disable icmp - Enable/Disable sending ICMP packets", "enable|disable range - Enable or Disable the given portlist for sending a range " "of packets", "enable|disable capture - Enable/disable packet capturing on a portlist, will " "capture RX packets", " Disable capture on a port to save the data into the " "current working directory.", "enable|disable bonding - Enable call TX with zero packets for bonding driver", "enable|disable vxlan - Send VxLAN packets", "enable|disable mac_from_arp - Enable/disable MAC address from ARP packet", "enable|disable clock_gettime - Enable/disable use of new clock_gettime() instead of rdtsc()", "enable|disable screen - Enable/disable updating the screen and unlock/lock window", " off - screen disable shortcut", " on - screen enable shortcut", CLI_HELP_PAUSE, NULL}; static int en_dis_cmd(int argc, char **argv) { struct cli_map *m; portlist_t portlist; int n, state; m = cli_mapping(enable_map, argc, argv); if (!m) return cli_cmd_error("Enable/Disable invalid command", "Enable", argc, argv); portlist_parse(argv[1], pktgen.nb_ports, &portlist); switch (m->index) { case 10: case 20: n = cli_map_list_search(m->fmt, argv[2], 2); state = estate(argv[0]); switch (n) { case 0: // process foreach_port(portlist, enable_process(pinfo, state)); break; case 1: // mpls foreach_port(portlist, enable_mpls(pinfo, state)); break; case 2: // qinq foreach_port(portlist, enable_qinq(pinfo, state)); break; case 3: // gre foreach_port(portlist, enable_gre(pinfo, state)); break; case 4: // gre_eth foreach_port(portlist, enable_gre_eth(pinfo, state)); break; case 5: // vlan foreach_port(portlist, enable_vlan(pinfo, state)); break; case 6: // random foreach_port(portlist, enable_random(pinfo, state)); break; case 7: // latency foreach_port(portlist, enable_latency(pinfo, state)); break; case 8: // pcap foreach_port(portlist, enable_pcap(pinfo, state)); break; case 9: // blink foreach_port(portlist, debug_blink(pinfo, state)); if (pktgen.blinklist) pktgen.flags |= BLINK_PORTS_FLAG; else pktgen.flags &= ~BLINK_PORTS_FLAG; break; case 10: // icmp foreach_port(portlist, enable_icmp_echo(pinfo, state)); break; case 11: // range foreach_port(portlist, enable_range(pinfo, state)); break; case 12: // capture foreach_port(portlist, pktgen_set_capture(pinfo, state)); break; case 13: // bonding #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) foreach_port(portlist, enable_bonding(pinfo, state)); #endif break; case 14: // vxlan foreach_port(portlist, enable_vxlan(pinfo, state)); break; case 16: // rnd_s_ip foreach_port(portlist, enable_rnd_s_ip(pinfo, state)); break; case 17: // rnd_s_pt foreach_port(portlist, enable_rnd_s_pt(pinfo, state)); break; case 18: // lat type alias latency foreach_port(portlist, enable_latency(pinfo, state)); break; default: return cli_cmd_error("Enable/Disable invalid command", "Enable", argc, argv); } break; case 30: case 31: state = estate(argv[0]); if (argv[1][0] == 'm') enable_mac_from_arp(state); else pktgen_screen(state); break; case 40: case 41: enable_clock_gettime(estate(argv[0])); break; default: return cli_cmd_error("Enable/Disable invalid command", "Enable", argc, argv); } pktgen_update_display(); return 0; } // clang-format off static struct cli_map dbg_map[] = { {20, "dbg %|tx_dbg|dbg"}, {21, "dbg tx_rate %P"}, {30, "dbg %|mempool|dump %P %s"}, {50, "dbg memzone"}, {51, "dbg memseg"}, {60, "dbg hexdump %H %d"}, {61, "dbg hexdump %H"}, {80, "dbg break"}, {90, "dbg memcpy"}, {91, "dbg memcpy %d %d"}, #ifdef TX_DEBUG {100, "dbg %|enable|disable|on|off tx pcap %P"}, #endif {-1, NULL} }; // clang-format on static const char *dbg_help[] = { "", "dbg tx_dbg|dbg - Enable tx debug output", "dbg tx_rate - Show packet rate for all ports", "dbg mempool|dump - Dump out the mempool info for a given type", " types - rx|tx", "dbg memzone - List all of the current memzones", "dbg memseg - List all of the current memsegs", "dbg hexdump - hex dump memory at given address", "dbg break - break into the debugger", "dbg memcpy [loop-cnt KBytes] - run a memcpy test", #ifdef TX_DEBUG "dbg enable|disable tx pcap - Enable/disable writing pcap file for transmitted " "packets", #endif CLI_HELP_PAUSE, NULL}; static void rte_memcpy_perf(unsigned int cnt, unsigned int kb, int flag) { char *buf[2], *src, *dst; uint64_t start_time, total_time; uint64_t total_bits, bits_per_tick; unsigned int i; kb *= 1024; buf[0] = malloc(kb + RTE_CACHE_LINE_SIZE); buf[1] = malloc(kb + RTE_CACHE_LINE_SIZE); src = RTE_PTR_ALIGN(buf[0], RTE_CACHE_LINE_SIZE); dst = RTE_PTR_ALIGN(buf[1], RTE_CACHE_LINE_SIZE); start_time = rte_get_tsc_cycles(); for (i = 0; i < cnt; i++) { if (flag) rte_memcpy(dst, src, kb); else memcpy(dst, src, kb); } total_time = rte_get_tsc_cycles() - start_time; total_bits = ((uint64_t)cnt * (uint64_t)kb) * 8L; bits_per_tick = total_bits / total_time; free(buf[0]); free(buf[1]); #define MEGA (uint64_t)(1024 * 1024) printf("%3d Kbytes for %8d loops, ", (kb / 1024), cnt); printf("%3ld bits/tick, ", bits_per_tick); printf("%6ld Mbits/sec with %s\n", (bits_per_tick * pktgen_get_timer_hz()) / MEGA, (flag) ? "rte_memcpy" : "memcpy"); } static int dbg_cmd(int argc, char **argv) { struct cli_map *m; portlist_t portlist; unsigned int len, cnt; const void *addr; m = cli_mapping(dbg_map, argc, argv); if (!m) return cli_cmd_error("Debug invalid command", "Debug", argc, argv); len = 32; cnt = 100000; switch (m->index) { case 20: if ((pktgen.flags & TX_DEBUG_FLAG) == 0) pktgen.flags |= TX_DEBUG_FLAG; else pktgen.flags &= ~TX_DEBUG_FLAG; pktgen_clear_display(); break; case 21: portlist_parse(argv[2], pktgen.nb_ports, &portlist); foreach_port(portlist, debug_tx_rate(pinfo)); break; case 30: portlist_parse(argv[2], pktgen.nb_ports, &portlist); foreach_port(portlist, debug_mempool_dump(pinfo, argv[3])); break; case 50: rte_memzone_dump(stdout); break; case 51: rte_dump_physmem_layout(stdout); break; case 60: case 61: addr = (void *)(uintptr_t)strtoull(argv[2], NULL, 0); if (argc == 3) len = 64; else len = strtoul(argv[3], NULL, 0); rte_hexdump(stdout, "", addr, len); break; case 80: kill(0, SIGINT); break; case 91: cnt = atoi(argv[2]); len = atoi(argv[3]); /*FALLTHRU*/ case 90: rte_memcpy_perf(cnt, len, 0); rte_memcpy_perf(cnt, len, 1); break; #ifdef TX_DEBUG case 100: /* enable/disable TX packets written to PCAP file */ portlist_parse(argv[4], pktgen.nb_ports, &portlist); foreach_port(portlist, pktgen_pcap_handler(pinfo, estate(argv[1]))); break; #endif default: return cli_cmd_error("Debug invalid command", "Debug", argc, argv); } return 0; } /** * * Set a sequence config for given port and slot. * * DESCRIPTION * Set up the sequence packets for a given port and slot. * * RETURNS: N/A * * SEE ALSO: * "%|seq|sequence %d %P %m %m %4 %4 %d %d %|ipv4|ipv6 %|udp|tcp|icmp %d %d %d" */ static int seq_1_set_cmd(int argc __rte_unused, char **argv) { char *proto = argv[10], *p; char *eth = argv[9]; int seqnum = atoi(argv[1]); portlist_t portlist; struct pg_ipaddr dst, src; struct rte_ether_addr dmac, smac; uint32_t teid; if ((proto[0] == 'i') && (eth[3] == '6')) { cli_printf("Must use IPv4 with ICMP type packets\n"); return -1; } if (seqnum >= NUM_SEQ_PKTS) { cli_printf("sequence number too large\n"); return -1; } teid = (argc == 14) ? strtoul(argv[13], NULL, 10) : 0; p = strchr(argv[5], '/'); /* remove subnet if found */ if (p) *p = '\0'; _atoip(argv[5], 0, &dst, sizeof(dst)); p = strchr(argv[6], '/'); if (!p) { cli_printf( "src IP address should contain subnet value, default /32 for IPv4, /128 for IPv6\n"); } _atoip(argv[6], PG_IPADDR_NETWORK, &src, sizeof(src)); portlist_parse(argv[2], pktgen.nb_ports, &portlist); if (pg_ether_aton(argv[3], &dmac) == NULL) { cli_printf("invalid MAC string (%s)\n", argv[3]); return -1; } if (pg_ether_aton(argv[4], &smac) == NULL) { cli_printf("invalid MAC string (%s)\n", argv[4]); return -1; } foreach_port(portlist, pktgen_set_seq(pinfo, seqnum, &dmac, &smac, &dst, &src, atoi(argv[7]), atoi(argv[8]), eth[3], proto[0], atoi(argv[11]), atoi(argv[12]), teid)); pktgen_update_display(); return 0; } /** * * Set a sequence config for given port and slot. * * DESCRIPTION * Set up the sequence packets for a given port and slot. * * RETURNS: N/A * * SEE ALSO: * "%|seq|sequence %d %P dst %m src %m dst %4 src %4 sport %d dport %d %|ipv4|ipv6 %|udp|tcp|icmp * vlan %d size %d teid %d" */ static int seq_2_set_cmd(int argc __rte_unused, char **argv) { char *proto = argv[16], *p; char *eth = argv[15]; int seqnum = atoi(argv[1]); portlist_t portlist; struct pg_ipaddr dst, src; struct rte_ether_addr dmac, smac; uint32_t teid; if ((proto[0] == 'i') && (eth[3] == '6')) { cli_printf("Must use IPv4 with ICMP type packets\n"); return -1; } if (seqnum >= NUM_SEQ_PKTS) { cli_printf("Sequence number too large\n"); return -1; } teid = (argc == 23) ? strtoul(argv[22], NULL, 10) : 0; p = strchr(argv[8], '/'); /* remove subnet if found */ if (p) *p = '\0'; _atoip(argv[8], 0, &dst, sizeof(dst)); p = strchr(argv[10], '/'); if (p == NULL) { cli_printf( "src IP address should contain subnet value, default /32 for IPv4, /128 for IPv6\n"); } _atoip(argv[10], PG_IPADDR_NETWORK, &src, sizeof(src)); portlist_parse(argv[2], pktgen.nb_ports, &portlist); if (pg_ether_aton(argv[4], &dmac) == NULL) { cli_printf("invalid MAC string (%s)\n", argv[4]); return -1; } if (pg_ether_aton(argv[6], &smac) == NULL) { cli_printf("invalid MAC string (%s)\n", argv[6]); return -1; } foreach_port(portlist, pktgen_set_seq(pinfo, seqnum, &dmac, &smac, &dst, &src, atoi(argv[12]), atoi(argv[14]), eth[3], proto[0], atoi(argv[18]), atoi(argv[20]), teid)); pktgen_update_display(); return 0; } /** * * Set a sequence config for given port and slot. * * DESCRIPTION * Set up the sequence packets for a given port and slot. * * RETURNS: N/A * * SEE ALSO: * "%|seq|sequence %d %P cos %d tos %d" */ static int seq_3_set_cmd(int argc __rte_unused, char **argv) { int seqnum = atoi(argv[1]); portlist_t portlist; uint32_t cos, tos; if (seqnum >= NUM_SEQ_PKTS) { cli_printf("Sequence number too large\n"); return -1; } cos = strtoul(argv[4], NULL, 10); tos = strtoul(argv[6], NULL, 10); portlist_parse(argv[2], pktgen.nb_ports, &portlist); foreach_port(portlist, pktgen_set_cos_tos_seq(pinfo, seqnum, cos, tos)); pktgen_update_display(); return 0; } /** * * Set a sequence config for given port and slot. * * DESCRIPTION * Set up the sequence packets for a given port and slot. * * RETURNS: N/A * * SEE ALSO: * "%|seq|sequence %d %P vxlan %d gid %d vid %d" */ static int seq_4_set_cmd(int argc __rte_unused, char **argv) { int seqnum = atoi(argv[1]); portlist_t portlist; uint32_t flag, gid, vid; if (seqnum >= NUM_SEQ_PKTS) { cli_printf("Sequence number too large\n"); return -1; } flag = strtoul(argv[4], NULL, 0); gid = strtoul(argv[6], NULL, 10); vid = strtoul(argv[8], NULL, 10); portlist_parse(argv[2], pktgen.nb_ports, &portlist); foreach_port(portlist, pktgen_set_vxlan_seq(pinfo, seqnum, flag, gid, vid)); pktgen_update_display(); return 0; } static int seq_5_set_cmd(int argc __rte_unused, char **argv) { int seqnum = atoi(argv[1]); portlist_t portlist; if (seqnum >= NUM_SEQ_PKTS) { cli_printf("Sequence number too large\n"); return -1; } portlist_parse(argv[2], pktgen.nb_ports, &portlist); foreach_port(portlist, seq_set_tcp_flags(pinfo, seqnum, argv[5])); pktgen_update_display(); return 0; } // clang-format off static struct cli_map seq_map[] = { {10, "%|seq|sequence %d %P %m %m %4 %4 %d %d %|ipv4|ipv6 %|udp|tcp|icmp %d %d"}, {11, "%|seq|sequence %d %P %m %m %4 %4 %d %d %|ipv4|ipv6 %|udp|tcp|icmp %d %d %d"}, {12, "%|seq|sequence %d %P dst %m src %m dst %4 src %4 sport %d dport %d %|ipv4|ipv6 " "%|udp|tcp|icmp vlan %d size %d"}, {13, "%|seq|sequence %d %P dst %m src %m dst %4 src %4 sport %d dport %d %|ipv4|ipv6 " "%|udp|tcp|icmp vlan %d size %d teid %d"}, {15, "%|seq|sequence %d %P cos %d tos %d"}, {16, "%|seq|sequence %d %P vxlan %d gid %d vid %d"}, {17, "%|seq|sequence %d %P vxlan %h gid %d vid %d"}, {18, "%|seq|sequence %d %P tcp %|flag|flags %c"}, {-1, NULL} }; // clang-format on static const char *seq_help[] = { "", "sequence dst src dst src sport dport " "ipv4|ipv6 udp|tcp|icmp vlan size [teid ]", "sequence ipv4|ipv6 " "udp|tcp|icmp []", "sequence cos tos ", "sequence vxlan gid vid ", " - Set the sequence packet information, make sure the " "src-IP", " has the netmask value eg 1.2.3.4/24", "sequence tcp %|flag|flags - Set comma delimited TCP flags: " "cwr,ece,urg,ack,psh,rst,syn,fin,clr", CLI_HELP_PAUSE, NULL}; static int seq_cmd(int argc, char **argv) { struct cli_map *m; m = cli_mapping(seq_map, argc, argv); if (!m) return cli_cmd_error("Sequence invalid command", "Sequence", argc, argv); switch (m->index) { case 10: case 11: seq_1_set_cmd(argc, argv); break; case 12: case 13: seq_2_set_cmd(argc, argv); break; case 15: seq_3_set_cmd(argc, argv); break; case 16: case 17: seq_4_set_cmd(argc, argv); break; case 18: seq_5_set_cmd(argc, argv); break; default: return cli_cmd_error("Sequence invalid command", "Sequence", argc, argv); } return 0; } #ifdef LUA_ENABLED /** * * script_cmd - Command to execute a script. * * DESCRIPTION * Load the script file and execute the commands. * * RETURNS: N/A * * SEE ALSO: */ static int script_cmd(int argc __rte_unused, char **argv) { lua_State *L = pktgen.ld->L; if (L == NULL) { pktgen_log_error("Lua is not initialized!"); return -1; } if (is_help(argc, argv)) { cli_printf("\nUsage: %s \n", argv[0]); return 0; } if (luaL_dofile(L, argv[1]) != 0) pktgen_log_error("%s", lua_tostring(L, -1)); return 0; } /** * * cmd_exec_lua_parsed - Command to execute lua code on command line. * * DESCRIPTION * Execute a string of lua code * * RETURNS: N/A * * SEE ALSO: */ static int exec_lua_cmd(int argc __rte_unused, char **argv __rte_unused) { lua_State *L = pktgen.ld->L; char buff[1024], *p; int i; size_t n, sz; if (L == NULL) { pktgen_log_error("Lua is not initialized!"); return -1; } if (is_help(argc, argv)) { cli_printf("\nUsage: %s \n", argv[0]); return 0; } sz = sizeof(buff); memset(buff, 0, sz); sz--; /* Make sure a NULL is at the end of the string */ n = 0; for (i = 1, p = buff; i < argc; i++) { if ((strlen(argv[i]) + 1) > (sz - n)) { cli_printf("Input line too long > %ld bytes\n", sizeof(buff)); return -1; } n += snprintf(&p[n], sz - n, "%s ", argv[i]); } if (luaL_dostring(L, buff) != 0) pktgen_log_error("%s", lua_tostring(L, -1)); return 0; } #endif // clang-format off static struct cli_map misc_map[] = { {10, "clear %P stats"}, {20, "geometry"}, {30, "load %s"}, #ifdef LUA_ENABLED {40, "script %l"}, {50, "lua %l"}, #endif {60, "save %s"}, {70, "redisplay"}, {100, "reset %P"}, {110, "restart %P"}, {130, "port %d"}, {140, "ping4 %P"}, #ifdef INCLUDE_PING6 {141, "ping6 %P"}, #endif {-1, NULL} }; // clang-format on static const char *misc_help[] = { "", "save - Save a configuration file using the filename", "load - Load a command/script file from the given path", #ifdef LUA_ENABLED "script - Execute the Lua script code in file (www.lua.org).", "lua 'lua string' - Execute the Lua code in the string needs quotes", #endif "geometry - Show the display geometry Columns by Rows (ColxRow)", "clear stats - Clear the statistics", "clr - Clear all Statistics", "reset - Reset the configuration the ports to the default", "rst - Reset the configuration for all ports", "ports per page [1-6] - Set the number of ports displayed per page", "port - Sets the sequence packets to display for a given port", "restart - Restart or stop a ethernet port and restart", "ping4 - Send a IPv4 ICMP echo request on the given portlist", #ifdef INCLUDE_PING6 "ping6 - Send a IPv6 ICMP echo request on the given portlist", #endif CLI_HELP_PAUSE, NULL}; static int misc_cmd(int argc, char **argv) { struct cli_map *m; portlist_t portlist; uint16_t rows, cols; m = cli_mapping(misc_map, argc, argv); if (!m) return cli_cmd_error("Misc invalid command", "Misc", argc, argv); switch (m->index) { case 10: portlist_parse(argv[1], pktgen.nb_ports, &portlist); foreach_port(portlist, pktgen_clear_stats(pinfo)); pktgen_clear_display(); break; case 20: pktgen_display_get_geometry(&rows, &cols); break; case 30: if (cli_execute_cmdfile(argv[1])) cli_printf("load command failed for %s\n", argv[1]); break; #ifdef LUA_ENABLED case 40: script_cmd(argc, argv); break; case 50: exec_lua_cmd(argc, argv); break; #endif case 60: pktgen_save(argv[1]); break; case 70: pktgen_clear_display(); break; case 100: portlist_parse(argv[1], pktgen.nb_ports, &portlist); foreach_port(portlist, pktgen_reset(pinfo)); break; case 110: portlist_parse(argv[1], pktgen.nb_ports, &portlist); foreach_port(portlist, pktgen_port_restart(pinfo)); break; case 130: pktgen_set_port_number((uint16_t)atoi(argv[1])); break; case 140: portlist_parse(argv[1], pktgen.nb_ports, &portlist); foreach_port(portlist, pktgen_ping4(pinfo)); pktgen_force_update(); break; #ifdef INCLUDE_PING6 case 141: portlist_parse(argv[1], pktgen.nb_ports, &portlist); foreach_port(portlist, pktgen_ping6(info)); pktgen_update_display(); break; #endif default: return cli_cmd_error("Misc invalid command", "Misc", argc, argv); } return 0; } // clang-format off static struct cli_map page_map[] = { {10, "page %d"}, {11, "page %|main|range|cpu|system|sys|next|sequence|seq|rnd|" "log|latency|lat|stats|qstats|xstats"}, {-1, NULL} }; static const char *page_help[] = { "", "page [0-7] - Show the port pages or configuration or sequence page", "page main - Display page zero", "page range - Display the range packet page", "page cpu - Display the CPU page", "page system | sys - Display some information about the CPU system", "page sequence | seq - sequence will display a set of packets for a given port", " Note: use the 'port ' to display a new port " "sequence", "page rnd - Display the random bitfields to packets for a given port", " Note: use the 'port ' to display a new port " "sequence", "page log - Display the log messages page", "page latency | lat - Display the latency page", "page qstats | stats - Display per port queue stats", "page xstats - Display port XSTATS values", CLI_HELP_PAUSE, NULL }; // clang-format on static int page_cmd(int argc, char **argv) { struct cli_map *m; m = cli_mapping(page_map, argc, argv); if (!m) return cli_cmd_error("Page invalid command", "Page", argc, argv); switch (m->index) { case 10: case 11: pktgen_set_page(argv[1]); break; default: return cli_cmd_error("Page invalid command", "Page", argc, argv); } return 0; } // clang-format off static struct cli_map plugin_map[] = { {10, "plugin"}, {20, "plugin load %s"}, {21, "plugin load %s %s"}, {30, "plugin %|rm|del|delete %s"}, {-1, NULL} }; static const char *plugin_help[] = { "", "plugin - Show the plugins currently installed", "plugin load - Load a plugin file", "plugin load - Load a plugin file at path", "plugin rm|delete - Remove or delete a plugin", CLI_HELP_PAUSE, NULL }; // clang-format on static int plugin_cmd(int argc, char **argv) { struct cli_map *m; int inst; m = cli_mapping(plugin_map, argc, argv); if (!m) return cli_cmd_error("Plugin invalid command", "Plugin", argc, argv); switch (m->index) { case 10: plugin_dump(stdout); break; case 20: case 21: if ((inst = plugin_create(argv[2], argv[3])) < 0) { printf("Plugin not loaded %s, %s\n", argv[2], argv[3]); return -1; } if (plugin_start(inst, NULL), 0) { plugin_destroy(inst); return -1; } break; case 30: inst = plugin_find_by_name(argv[2]); if (inst < 0) return -1; plugin_stop(inst); plugin_destroy(inst); break; default: return cli_cmd_error("Plugin invalid command", "Plugin", argc, argv); } return 0; } #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) // clang-format off static struct cli_map bonding_map[] = { {10, "bonding %P show"}, {20, "bonding show"}, {-1, NULL} }; // clang-format on static const char *bonding_help[] = { "", "bonding show - Show the bonding configuration for ", "bonding show - Show all bonding configurations", CLI_HELP_PAUSE, NULL}; static void bonding_dump(port_info_t *pinfo) { uint16_t pid; RTE_ETH_FOREACH_DEV(pid) { struct rte_eth_bond_8023ad_conf conf; if (pinfo->pid != pid) continue; if (rte_eth_bond_8023ad_conf_get(pid, &conf) < 0) continue; printf("Port %d information:\n", pid); show_bonding_mode(pinfo); printf("\n"); } } static int bonding_cmd(int argc, char **argv) { struct cli_map *m; portlist_t portlist; m = cli_mapping(bonding_map, argc, argv); if (!m) return cli_cmd_error("bonding invalid command", "Bonding", argc, argv); switch (m->index) { case 10: portlist_parse(argv[1], pktgen.nb_ports, &portlist); foreach_port(portlist, bonding_dump(pinfo)); break; case 20: portlist = 0xFFFFFFFF; foreach_port(portlist, bonding_dump(pinfo)); break; default: return cli_cmd_error("Bonding invalid command", "Bonding", argc, argv); } return 0; } #endif // clang-format off static struct cli_map latency_map[] = { {10, "latency %P rate %u"}, {20, "latency %P entropy %u"}, {-1, NULL} }; static const char *latency_help[] = { "", "latency rate - Rate in milli-seconds to send a latency packet", "latency entropy - Entropy value to adjust the src-port by (SPORT + (i % N)) (default: 1)", " e.eg. latency 0 entropy 16", CLI_HELP_PAUSE, NULL }; // clang-format on static int latency_cmd(int argc, char **argv) { struct cli_map *m; portlist_t portlist; int value; m = cli_mapping(latency_map, argc, argv); if (!m) return cli_cmd_error("Latency invalid command", "Latency", argc, argv); portlist_parse(argv[1], pktgen.nb_ports, &portlist); value = atoi(argv[3]); switch (m->index) { case 10: foreach_port(portlist, latency_set_rate(pinfo, value)); break; case 20: foreach_port(portlist, latency_set_entropy(pinfo, (uint16_t)value)); break; default: return cli_cmd_error("Latency invalid command", "Latency", argc, argv); } pktgen_update_display(); return 0; } // clang-format off static struct cli_map hmap_map[] = { {10, "hmap list"}, {20, "hmap dump all"}, {30, "hmap dump %s"}, {-1, NULL} }; static const char *hmap_help[] = { "", "hmap list - Show the hmap names currently active", "hmap dump all - Dump all hmap information", "hmap dump - Dump the hmap information for ", CLI_HELP_PAUSE, NULL }; // clang-format on static int hmap_cmd(int argc, char **argv) { struct cli_map *m; m = cli_mapping(hmap_map, argc, argv); if (!m) return cli_cmd_error("hmap invalid command", "Hashmap", argc, argv); switch (m->index) { case 10: hmap_list_names(NULL); break; case 20: hmap_list_all(NULL, true); break; case 30: hmap_list_by_name(NULL, argv[2], true); break; default: return cli_cmd_error("Hashmap invalid command", "Hashmap", argc, argv); } pktgen_update_display(); return 0; } /**********************************************************/ /**********************************************************/ /****** CONTEXT (list of instruction) */ static int help_cmd(int argc, char **argv); // clang-format off static struct cli_tree default_tree[] = { c_dir("/pktgen/bin"), c_cmd("help", help_cmd, "help command"), c_cmd("clear", misc_cmd, "clear stats, ..."), c_alias("clr", "clear all stats", "clear all port stats"), c_cmd("geometry", misc_cmd, "show the screen geometry"), c_alias("geom", "geometry", "show the screen geometry"), c_cmd("load", misc_cmd, "load command file"), #ifdef LUA_ENABLED c_cmd("script", misc_cmd, "run a Lua script"), c_cmd("lua", misc_cmd, "execute a Lua string"), #endif c_cmd("save", misc_cmd, "save the current state"), c_cmd("redisplay", misc_cmd, "redisplay the screen"), c_alias("cls", "redisplay", "redraw screen"), c_cmd("reset", misc_cmd, "reset pktgen configuration"), c_alias("rst", "reset all", "reset all ports"), c_cmd("restart", misc_cmd, "restart port"), c_cmd("port", misc_cmd, "Switch between ports"), c_cmd("ping4", misc_cmd, "Send a ping packet for IPv4"), #ifdef INCLUDE_PING6 c_cmd("ping6", misc_cmd, "Send a ping packet for IPv6"), #endif c_cmd("sequence", seq_cmd, "sequence command"), c_alias("seq", "sequence", "sequence command"), c_cmd("page", page_cmd, "change page displays"), c_cmd("theme", theme_cmd, "Set, save, show the theme"), c_cmd("range", range_cmd, "Range commands"), c_cmd("enable", en_dis_cmd, "enable features"), c_cmd("disable", en_dis_cmd, "disable features"), c_cmd("start", start_stop_cmd, "start features"), c_cmd("stop", start_stop_cmd, "stop features"), c_alias("str", "start all", "start all ports sending packets"), c_alias("stp", "stop all", "stop all ports sending packets"), c_cmd("pcap", pcap_cmd, "pcap commands"), c_cmd("set", set_cmd, "set a number of options"), c_cmd("dbg", dbg_cmd, "debug commands"), c_cmd("plugin", plugin_cmd, "Plugin a shared object file"), #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) c_cmd("bonding", bonding_cmd, "Bonding commands"), #endif c_cmd("latency", latency_cmd, "Latency setup commands"), c_cmd("hmap", hmap_cmd, "hashmap commands"), c_alias("on", "enable screen", "Enable screen updates"), c_alias("off", "disable screen", "Disable screen updates"), c_end() }; // clang-format on static int init_tree(void) { /* Add the system default commands in /sbin directory */ if (cli_default_tree_init()) return -1; /* Add the Pktgen directory tree */ if (cli_add_tree(cli_root_node(), default_tree)) return -1; cli_help_add("Title", NULL, title_help); cli_help_add("Page", page_map, page_help); cli_help_add("Enable", enable_map, enable_help); cli_help_add("Set", set_map, set_help); cli_help_add("Range", range_map, range_help); cli_help_add("Sequence", seq_map, seq_help); cli_help_add("PCAP", pcap_map, pcap_help); cli_help_add("Start", start_map, start_help); cli_help_add("Debug", dbg_map, dbg_help); cli_help_add("Misc", misc_map, misc_help); cli_help_add("Theme", theme_map, theme_help); cli_help_add("Plugin", plugin_map, plugin_help); cli_help_add("Latency", latency_map, latency_help); cli_help_add("Hashmap", hmap_map, hmap_help); #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) cli_help_add("Bonding", bonding_map, bonding_help); #endif cli_help_add("Status", NULL, status_help); /* Make sure the pktgen commands are executable in search path */ if (cli_add_bin_path("/pktgen/bin")) return -1; return 0; } static int my_prompt(int cont __rte_unused) { int nb; pktgen_display_set_color("pktgen.prompt"); nb = cli_printf("Pktgen:%s> ", cli_path_string(NULL, NULL)); pktgen_display_set_color("stats.stat.values"); return nb; } int pktgen_cli_create(void) { int ret = -1; if (!cli_create()) { if (!cli_setup_with_tree(init_tree)) { cli_set_prompt(my_prompt); ret = 0; } } return ret; } /** * * Display the help screen and pause if needed. * * DESCRIPTION * Display the help and use pause to show screen full of messages. * * RETURNS: N/A * * SEE ALSO: */ static int help_cmd(int argc __rte_unused, char **argv __rte_unused) { int paused; paused = scrn_is_paused(); if (!paused) scrn_pause(); scrn_setw(1); scrn_cls(); scrn_pos(1, 1); cli_help_show_all("** Pktgen Help Information **"); if (!paused) { scrn_setw(pktgen.last_row + 1); scrn_resume(); pktgen_clear_display(); } return 0; } ================================================ FILE: app/cli-functions.h ================================================ /*- * Copyright(c) <2020-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2018 by Keith Wiles @ intel.com */ #ifndef _CLI_COMMANDS_H_ #define _CLI_COMMANDS_H_ /** * @file * * Pktgen interactive CLI command tree creation. */ #ifdef __cplusplus extern "C" { #endif /** * Build and register the full Pktgen CLI command tree. * * Registers all command maps, directory nodes, and help strings with the * CLI engine. Must be called once during initialisation before entering * the interactive shell loop. * * @return * 0 on success, -1 on failure. */ int pktgen_cli_create(void); #ifdef __cplusplus } #endif #endif /* _CLI_COMMANDS_H_ */ ================================================ FILE: app/l2p.c ================================================ /*- * Copyright(c) <2012-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2012 by Keith Wiles @ intel.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "l2p.h" #include "utils.h" static l2p_t l2p_info, *l2p = &l2p_info; l2p_t * l2p_get(void) { return l2p; } /** * Create and initialize the lcore to port object * * RETURNS: Pointer to l2p_t or NULL on error. */ void l2p_create(void) { for (int i = 0; i < RTE_MAX_ETHPORTS; i++) l2p->ports[i].pid = RTE_MAX_ETHPORTS + 1; } static struct rte_mempool * l2p_pktmbuf_create(const char *type, l2p_port_t *port, uint16_t qid, int nb_mbufs, int cache_size) { struct rte_mempool *mp; char name[RTE_MEMZONE_NAMESIZE]; uint64_t sz; int sid; sid = pg_eth_dev_socket_id(port->pid); snprintf(name, sizeof(name) - 1, "%s-P%u/Q%u/S%u", type, port->pid, qid, sid); const int bufSize = pktgen.mbuf_buf_size; sz = nb_mbufs * bufSize; sz = RTE_ALIGN_CEIL(sz + sizeof(struct rte_mempool), 1024); pktgen.mem_used += sz; pktgen.total_mem_used += sz; /* create the mbuf pool */ mp = rte_pktmbuf_pool_create(name, nb_mbufs, cache_size, DEFAULT_PRIV_SIZE, bufSize, sid); if (mp == NULL) rte_exit(EXIT_FAILURE, "Cannot create mbuf pool (%s) port %d, queue %u, nb_mbufs %d, NUMA %d: %s\n", name, port->pid, qid, nb_mbufs, sid, rte_strerror(rte_errno)); pktgen_log_info(" Create: '%-*s' - Memory used (MBUFs %'6u x size %'6u) = %'8lu KB @ %p\n", 24, name, nb_mbufs, bufSize, sz / 1024, mp); return mp; } static int parse_cores(uint16_t pid, const char *cores, int mode) { l2p_t *l2p = l2p_get(); l2p_port_t *port = &l2p->ports[pid]; char *core_map = NULL; int num_cores = 0, l, h, num_fields; char *fields[3] = {0}, *f0, *f1; int mbuf_count = MAX_MBUFS_PER_PORT(DEFAULT_RX_DESC, DEFAULT_TX_DESC); char name[64]; core_map = alloca(MAX_ALLOCA_SIZE); if (!core_map) rte_exit(EXIT_FAILURE, "out of memory for core string\n"); snprintf(core_map, MAX_ALLOCA_SIZE - 1, "%s", cores); num_fields = rte_strsplit(core_map, strlen(core_map), fields, RTE_DIM(fields), '-'); if (num_fields <= 0 || num_fields > 2) rte_exit(EXIT_FAILURE, "invalid core mapping '%s'\n", cores); f0 = fields[0]; f1 = (fields[1] == NULL) ? f0 : fields[1]; f0 = pg_strtrimset(f0, "[]"); f0 = pg_strtrimset(f0, "{}"); f1 = pg_strtrimset(f1, "[]"); f1 = pg_strtrimset(f1, "{}"); l = strtol(f0, NULL, 10); h = strtol(f1, NULL, 10); do { l2p_lport_t *lport; int32_t sid = pg_eth_dev_socket_id(port->pid); lport = l2p->lports[l]; if (lport == NULL) { snprintf(name, sizeof(name) - 1, "lport-%u:%u", l, port->pid); lport = rte_zmalloc_socket(name, sizeof(l2p_lport_t), RTE_CACHE_LINE_SIZE, sid); if (!lport) rte_exit(EXIT_FAILURE, "Failed to allocate memory for lport info\n"); lport->lid = l; l2p->lports[l] = lport; } else printf("Err: lcore %u already in use\n", l); num_cores++; lport->port = port; lport->mode = mode; switch (mode) { case LCORE_MODE_RX: if (port->num_rx_qids >= MAX_QUEUES_PER_PORT) rte_exit(EXIT_FAILURE, "Too many RX queues for port %u (max %u)\n", port->pid, (unsigned)MAX_QUEUES_PER_PORT); lport->rx_qid = port->num_rx_qids++; break; case LCORE_MODE_TX: if (port->num_tx_qids >= MAX_QUEUES_PER_PORT) rte_exit(EXIT_FAILURE, "Too many TX queues for port %u (max %u)\n", port->pid, (unsigned)MAX_QUEUES_PER_PORT); lport->tx_qid = port->num_tx_qids++; break; case LCORE_MODE_BOTH: if (port->num_rx_qids >= MAX_QUEUES_PER_PORT) rte_exit(EXIT_FAILURE, "Too many RX queues for port %u (max %u)\n", port->pid, (unsigned)MAX_QUEUES_PER_PORT); if (port->num_tx_qids >= MAX_QUEUES_PER_PORT) rte_exit(EXIT_FAILURE, "Too many TX queues for port %u (max %u)\n", port->pid, (unsigned)MAX_QUEUES_PER_PORT); lport->rx_qid = port->num_rx_qids++; lport->tx_qid = port->num_tx_qids++; break; default: rte_exit(EXIT_FAILURE, "invalid port mode\n"); break; } if (mode == LCORE_MODE_RX || mode == LCORE_MODE_BOTH) { const uint16_t qid = lport->rx_qid; if (port->rx_mp[qid] == NULL) { /* Create the Rx mbuf pool one per port/queue */ port->rx_mp[qid] = l2p_pktmbuf_create("RX", port, qid, mbuf_count, MEMPOOL_CACHE_SIZE); if (port->rx_mp[qid] == NULL) rte_exit(EXIT_FAILURE, "Cannot init port %d qid %u for Default RX mbufs", port->pid, qid); } } if (mode == LCORE_MODE_TX || mode == LCORE_MODE_BOTH) { const uint16_t qid = lport->tx_qid; if (port->tx_mp[qid] == NULL) { port->tx_mp[qid] = l2p_pktmbuf_create("TX", port, qid, mbuf_count, MEMPOOL_CACHE_SIZE); if (port->tx_mp[qid] == NULL) rte_exit(EXIT_FAILURE, "Cannot init port %d qid %u for Default TX mbufs", port->pid, qid); } if (port->sp_mp[qid] == NULL) { /* Used for sending special packets like ARP requests */ port->sp_mp[qid] = l2p_pktmbuf_create("SP", port, qid, MAX_SPECIAL_MBUFS, 0); if (port->sp_mp[qid] == NULL) rte_exit(EXIT_FAILURE, "Cannot init port %d qid %u for Special TX mbufs", pid, qid); } } } while (l++ < h); return num_cores; } static int parse_mapping(const char *map) { l2p_t *l2p = l2p_get(); char *fields[3] = {0}, *f0, *f1, *lcores[3] = {0}, *c0, *c1; char *mapping = NULL; int num_fields, num_cores, num_lcores; uint16_t pid; if (!map || strlen(map) == 0) { printf("no mapping specified or string empty\n"); goto leave; } mapping = alloca(MAX_ALLOCA_SIZE); if (!mapping) { printf("unable to allocate map string\n"); goto leave; } snprintf(mapping, MAX_ALLOCA_SIZE - 1, "%s", map); /* parse map into a lcore list and port number */ num_fields = rte_strsplit(mapping, strlen(mapping), fields, RTE_DIM(fields), '.'); if (num_fields != 2) { printf("Invalid mapping format '%s'\n", map); goto leave; } f0 = fields[0]; f1 = (fields[1] == NULL) ? f0 : fields[1]; f0 = pg_strtrimset(f0, "[]"); f0 = pg_strtrimset(f0, "{}"); f1 = pg_strtrimset(f1, "[]"); f1 = pg_strtrimset(f1, "{}"); pid = strtol(f1, NULL, 10); if (pid >= RTE_MAX_ETHPORTS) { printf("Invalid port number '%s'\n", f1); goto leave; } l2p->ports[pid].pid = pid; l2p->num_ports++; num_lcores = rte_strsplit(f0, strlen(f0), lcores, RTE_DIM(lcores), ':'); if (num_lcores <= 0 || num_lcores > 2) { printf("Invalid mapping format '%s'\n", fields[0]); goto leave; } c0 = lcores[0]; c1 = (lcores[1] == NULL) ? c0 : lcores[1]; c0 = pg_strtrimset(c0, "[]"); c0 = pg_strtrimset(c0, "{}"); c1 = pg_strtrimset(c1, "[]"); c1 = pg_strtrimset(c1, "{}"); if (num_lcores == 1) { num_cores = parse_cores(pid, c0, LCORE_MODE_BOTH); if (num_cores <= 0) { printf("Invalid mapping format '%s'\n", c0); goto leave; } } else { num_cores = parse_cores(pid, c0, LCORE_MODE_RX); if (num_cores <= 0) { printf("Invalid mapping format '%s'\n", c0); goto leave; } num_cores = parse_cores(pid, c1, LCORE_MODE_TX); if (num_cores <= 0) { printf("Invalid mapping format '%s'\n", c1); goto leave; } } return 0; leave: return -1; } /** * * pg_parse_matrix - Parse the command line argument for port configuration * * DESCRIPTION * Parse the command line argument for port configuration. * * BNF: (or kind of BNF) * := """ { "," } """ * := "." * := "[" ":" "]" * := * := { "/" ( | ) } * := { "/" ( | ) } * := { "/" ( | ) } * := "-" { "/" } * := + * := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 * * BTW: A single lcore can only handle a single port/queue, which means * you can not have a single core processing more then one network device or port. * * 1.0, 2.1, 3.2 - core 1 handles port 0 rx/tx, * core 2 handles port 1 rx/tx * [0-1].0, [2/4-5].1, ... - cores 0-1 handle port 0 rx/tx, * cores 2,4,5 handle port 1 rx/tx * [1:2].0, [4:6].1, ... - core 1 handles port 0 rx, * core 2 handles port 0 tx, * [1:2-3].0, [4:5-6].1, ... - core 1 handles port 1 rx, cores 2,3 handle port 0 tx * core 4 handles port 1 rx & core 5,6 handles port 1 tx * [1-2:3].0, [4-5:6].1, ... - core 1,2 handles port 0 rx, core 3 handles port 0 tx * core 4,5 handles port 1 rx & core 6 handles port 1 tx * [1-2:3-5].0, [4-5:6/8].1, ... - core 1,2 handles port 0 rx, core 3,4,5 handles port 0 tx * core 4,5 handles port 1 rx & core 6,8 handles port 1 tx * BTW: you can use "{}" instead of "[]" as it does not matter to the syntax. * * RETURNS: N/A * * SEE ALSO: */ int l2p_parse_mappings(void) { l2p_t *l2p = l2p_get(); for (int i = 0; i < l2p->num_mappings; i++) if (parse_mapping(l2p->mappings[i])) return -1; pktgen_log_info("%*sTotal memory used = %'8" PRIu64 " KB", 54, " ", (pktgen.total_mem_used + 1023) / 1024); return 0; } int l2p_parse_mapping_add(const char *str) { l2p_t *l2p = l2p_get(); if (!str || l2p->num_mappings > RTE_MAX_ETHPORTS) return -1; l2p->mappings[l2p->num_mappings++] = strdup(str); return 0; } ================================================ FILE: app/l2p.h ================================================ /*- * Copyright(c) <2012-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2014 by Keith Wiles @ intel.com */ #ifndef __L2P_H #define __L2P_H /** * @file * * Lcore-to-Port (L2P) mapping for Pktgen. * * Manages the assignment of logical cores to Ethernet port/queue pairs, * the per-port mempool ownership, and provides accessors for obtaining * port_info_t pointers, queue counts, and PCAP state from any lcore or * port index. */ #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define MAX_MATRIX_ENTRIES 128 /**< Maximum number of lcore/port mapping entries */ #define MAX_STRING 256 /**< Maximum length of a mapping string */ #define MAX_MAP_PORTS (RTE_MAX_ETHPORTS + 1) /**< Sentinel-inclusive port array size */ #define MAX_MAP_LCORES (RTE_MAX_LCORE + 1) /**< Sentinel-inclusive lcore array size */ /** Lcore operating mode: unknown, RX-only, TX-only, or both. */ enum { LCORE_MODE_UNKNOWN = 0, LCORE_MODE_RX = 1, LCORE_MODE_TX = 2, LCORE_MODE_BOTH = 3 }; #define MAX_MAPPINGS 32 /**< Maximum number of port/queue mappings */ #define MAX_ALLOCA_SIZE 1024 /**< Maximum size of a stack allocation */ #define MEMPOOL_CACHE_SIZE (RTE_MEMPOOL_CACHE_MAX_SIZE / 2) /**< Per-lcore mempool cache size */ struct port_info_s; /** * Per-port L2P state: mempools, queue counts, and PCAP context. */ typedef struct l2p_port_s { struct port_info_s *pinfo; /**< Port information pointer */ uint16_t pid; /**< Port ID attached to lcore */ uint16_t num_rx_qids; /**< Number of Rx queues */ uint16_t num_tx_qids; /**< Number of Tx queues */ struct rte_mempool *rx_mp[MAX_QUEUES_PER_PORT]; /**< Rx pktmbuf pool per queue */ struct rte_mempool *tx_mp[MAX_QUEUES_PER_PORT]; /**< Tx pktmbuf pool per queue */ struct rte_mempool *sp_mp[MAX_QUEUES_PER_PORT]; /**< Special TX pktmbuf pool per queue */ pcap_info_t *pcap_info; /**< PCAP packet structure */ } l2p_port_t; /** * Per-lcore L2P state: mode, queue IDs, and a pointer to the owning port. */ typedef struct l2p_lport_s { uint16_t mode; /**< LCORE_MODE_RX, LCORE_MODE_TX, or LCORE_MODE_BOTH */ uint16_t lid; /**< Lcore ID */ uint16_t rx_qid; /**< RX queue ID assigned to this lcore */ uint16_t tx_qid; /**< TX queue ID assigned to this lcore */ l2p_port_t *port; /**< Pointer to the owning port structure */ } l2p_lport_t; /** * Top-level L2P mapping table. */ typedef struct { l2p_lport_t *lports[RTE_MAX_LCORE]; /**< Per-lcore port/queue assignment */ l2p_port_t ports[RTE_MAX_ETHPORTS]; /**< Per-port L2P state */ char *mappings[RTE_MAX_ETHPORTS]; /**< Raw mapping strings from the command line */ uint16_t num_mappings; /**< Number of mapping strings */ uint16_t num_ports; /**< Number of active ports */ } l2p_t; /** Log an error message with the enclosing function name as prefix. */ #define L2P_ERR(fmt, args...) printf("%s: " fmt "\n", __func__, ##args); /** Log an error message and return -1. */ #define L2P_ERR_RET(fmt, args...) \ do { \ L2P_ERR(fmt, ##args); \ return -1; \ } while (0) /** Log an error message and return NULL. */ #define L2P_NULL_RET(fmt, args...) \ do { \ L2P_ERR(fmt, ##args); \ return NULL; \ } while (0) /** * Return the global l2p_t singleton. * * @return * Pointer to the global l2p_t mapping table. */ l2p_t *l2p_get(void); /** * Return the l2p_port_t for a given port ID. * * @param pid * Port ID to look up. * @return * Pointer to the l2p_port_t, or NULL if @p pid is invalid. */ static __inline__ l2p_port_t * l2p_get_port(uint16_t pid) { l2p_t *l2p = l2p_get(); return ((pid < RTE_MAX_ETHPORTS) && (l2p->ports[pid].pid < RTE_MAX_ETHPORTS)) ? &l2p->ports[pid] : NULL; } /** * Return the number of active ports in the L2P mapping. * * @return * Number of configured ports. */ static __inline__ uint16_t l2p_get_port_cnt(void) { l2p_t *l2p = l2p_get(); return l2p->num_ports; } /** * Return the lcore ID associated with a port. * * Scans the lport table and returns the first lcore mapped to @p pid. * * @param pid * Port ID to search for. * @return * Lcore ID, or RTE_MAX_LCORE if none found. */ static __inline__ uint16_t l2p_get_lcore_by_pid(uint16_t pid) { l2p_t *l2p = l2p_get(); uint16_t lid = RTE_MAX_LCORE; for (uint16_t i = 0; i < RTE_MAX_LCORE; i++) { if (l2p->lports[i] && l2p->lports[i]->port->pid == pid) { lid = l2p->lports[i]->lid; break; } } return lid; } /** * Return the port ID assigned to a given lcore. * * @param lid * Lcore ID to look up. * @return * Port ID, or RTE_MAX_ETHPORTS if @p lid has no port assignment. */ static __inline__ uint16_t l2p_get_pid_by_lcore(uint16_t lid) { l2p_t *l2p = l2p_get(); uint16_t pid = RTE_MAX_ETHPORTS; if (!l2p->lports[lid] || !l2p->lports[lid]->port) return pid; if (l2p->lports[lid]->port->pid >= RTE_MAX_ETHPORTS) return pid; return l2p->lports[lid]->port->pid; } /** * Return the RX mempool for a given port and queue. * * @param pid * Port ID. * @param qid * RX queue ID. * @return * Pointer to the RX mempool, or NULL on error. */ static __inline__ struct rte_mempool * l2p_get_rx_mp(uint16_t pid, uint16_t qid) { l2p_t *l2p = l2p_get(); if (pid >= RTE_MAX_ETHPORTS) L2P_NULL_RET("Invalid port ID %u", pid); if (qid >= MAX_QUEUES_PER_PORT) L2P_NULL_RET("Invalid queue ID %u", qid); return l2p->ports[pid].rx_mp[qid]; } /** * Return the TX mempool for a given port and queue. * * @param pid * Port ID. * @param qid * TX queue ID. * @return * Pointer to the TX mempool, or NULL on error. */ static __inline__ struct rte_mempool * l2p_get_tx_mp(uint16_t pid, uint16_t qid) { l2p_t *l2p = l2p_get(); if (pid >= RTE_MAX_ETHPORTS) L2P_NULL_RET("Invalid port ID %u", pid); if (qid >= MAX_QUEUES_PER_PORT) L2P_NULL_RET("Invalid queue ID %u", qid); return l2p->ports[pid].tx_mp[qid]; } /** * Return the special TX mempool for a given port and queue. * * @param pid * Port ID. * @param qid * TX queue ID. * @return * Pointer to the special TX mempool, or NULL on error. */ static __inline__ struct rte_mempool * l2p_get_sp_mp(uint16_t pid, uint16_t qid) { l2p_t *l2p = l2p_get(); if (pid >= RTE_MAX_ETHPORTS) L2P_NULL_RET("Invalid port ID %u", pid); if (qid >= MAX_QUEUES_PER_PORT) L2P_NULL_RET("Invalid queue ID %u", qid); return l2p->ports[pid].sp_mp[qid]; } /** * Return the lcore operating mode (RX, TX, or both). * * @param lid * Lcore ID to query. * @return * LCORE_MODE_* constant, or -1 on error. */ static __inline__ int l2p_get_type(uint16_t lid) { l2p_t *l2p = l2p_get(); if (lid >= RTE_MAX_LCORE) L2P_ERR_RET("Invalid lcore ID %u", lid); return l2p->lports[lid]->mode; } /** * Return the port_info_t for a given port ID. * * @param pid * Port ID to look up. * @return * Pointer to port_info_t, or NULL if @p pid is invalid. */ static __inline__ struct port_info_s * l2p_get_port_pinfo(uint16_t pid) { l2p_port_t *port = l2p_get_port(pid); return (port == NULL) ? NULL : port->pinfo; } /** * Set the port_info_t pointer for a given port ID. * * @param pid * Port ID to update. * @param pinfo * Pointer to the port_info_t to associate. * @return * 0 on success, -1 on invalid @p pid. */ static __inline__ int l2p_set_port_pinfo(uint16_t pid, struct port_info_s *pinfo) { l2p_port_t *port = l2p_get_port(pid); if (port == NULL) L2P_ERR_RET("Invalid port ID %u", pid); port->pinfo = pinfo; return 0; } /** * Return the port_info_t for the port assigned to a given lcore. * * @param lid * Lcore ID to look up. * @return * Pointer to port_info_t, or NULL if @p lid is invalid. */ static __inline__ struct port_info_s * l2p_get_pinfo_by_lcore(uint16_t lid) { l2p_t *l2p = l2p_get(); l2p_port_t *port; if (lid >= RTE_MAX_LCORE || l2p->lports[lid]->lid >= RTE_MAX_LCORE || (port = l2p->lports[lid]->port) == NULL) L2P_NULL_RET("Invalid lcore ID %u", lid); return port->pinfo; } /** * Set the port_info_t pointer for the port assigned to a given lcore. * * @param lid * Lcore ID to update. * @param pinfo * Pointer to the port_info_t to associate. * @return * 0 on success, -1 on invalid @p lid. */ static __inline__ int l2p_set_pinfo_by_lcore(uint16_t lid, struct port_info_s *pinfo) { l2p_t *l2p = l2p_get(); l2p_port_t *port; if (lid >= RTE_MAX_LCORE || l2p->lports[lid]->lid >= RTE_MAX_LCORE || (port = l2p->lports[lid]->port) == NULL) L2P_ERR_RET("Invalid lcore ID %u", lid); port->pinfo = pinfo; return 0; } /** * Return the RX and TX queue counts for a port. * * @param pid * Port ID to query. * @param rx_qid * Output: number of RX queues (may be NULL). * @param tx_qid * Output: number of TX queues (may be NULL). * @return * 0 on success, -1 on invalid @p pid. */ static __inline__ int l2p_get_qcnt(uint16_t pid, uint16_t *rx_qid, uint16_t *tx_qid) { l2p_t *l2p = l2p_get(); if (pid < RTE_MAX_ETHPORTS && l2p->ports[pid].pid < RTE_MAX_ETHPORTS) { if (rx_qid) *rx_qid = l2p->ports[pid].num_rx_qids; if (tx_qid) *tx_qid = l2p->ports[pid].num_tx_qids; return 0; } return -1; } /** * Return the number of RX queues for a port. * * @param pid * Port ID to query. * @return * RX queue count, or 0 on invalid @p pid. */ static __inline__ int l2p_get_rxcnt(uint16_t pid) { l2p_t *l2p = l2p_get(); if (pid < RTE_MAX_ETHPORTS && l2p->ports[pid].pid < RTE_MAX_ETHPORTS) return l2p->ports[pid].num_rx_qids; return 0; } /** * Return the number of TX queues for a port. * * @param pid * Port ID to query. * @return * TX queue count, or 0 on invalid @p pid. */ static __inline__ int l2p_get_txcnt(uint16_t pid) { l2p_t *l2p = l2p_get(); if (pid < RTE_MAX_ETHPORTS && l2p->ports[pid].pid < RTE_MAX_ETHPORTS) return l2p->ports[pid].num_tx_qids; return 0; } /** * Return the RX queue ID assigned to a given lcore. * * @param lid * Lcore ID to query. * @return * RX queue ID, or 0 on invalid @p lid. */ static __inline__ int l2p_get_rxqid(uint16_t lid) { l2p_t *l2p = l2p_get(); if (lid < RTE_MAX_LCORE && l2p->lports[lid] != NULL && l2p->lports[lid]->port != NULL) return l2p->lports[lid]->rx_qid; return 0; } /** * Return the TX queue ID assigned to a given lcore. * * @param lid * Lcore ID to query. * @return * TX queue ID, or 0 on invalid @p lid. */ static __inline__ int l2p_get_txqid(uint16_t lid) { l2p_t *l2p = l2p_get(); if (lid < RTE_MAX_LCORE && l2p->lports[lid] != NULL && l2p->lports[lid]->port != NULL) return l2p->lports[lid]->tx_qid; return 0; } /** * Return the RX and TX queue IDs for a port (via the first mapped lcore). * * @param pid * Port ID to query. * @param rx_qid * Output: RX queue ID (may be NULL). * @param tx_qid * Output: TX queue ID (may be NULL). * @return * 0 on success, -1 on invalid @p pid. */ static __inline__ int l2p_get_qids(uint16_t pid, uint16_t *rx_qid, uint16_t *tx_qid) { l2p_t *l2p = l2p_get(); if (pid < RTE_MAX_ETHPORTS && l2p->ports[pid].pid < RTE_MAX_ETHPORTS) { if (rx_qid) *rx_qid = l2p_get_rxqid(pid); if (tx_qid) *tx_qid = l2p_get_txqid(pid); return 0; } return -1; } /** * Return the PCAP info structure for a port. * * @param pid * Port ID to look up. * @return * Pointer to pcap_info_t, or NULL on invalid @p pid. */ static __inline__ pcap_info_t * l2p_get_pcap(uint16_t pid) { l2p_t *l2p = l2p_get(); if (pid < RTE_MAX_ETHPORTS && l2p->ports[pid].pid < RTE_MAX_ETHPORTS) return l2p->ports[pid].pcap_info; L2P_NULL_RET("Invalid port ID %u", pid); } /** * Set the PCAP info pointer for a port. * * @param pid * Port ID to update. * @param pcap_info * PCAP info structure to associate with the port. * @return * 0 on success, -1 on invalid @p pid. */ static __inline__ int l2p_set_pcap_info(uint16_t pid, pcap_info_t *pcap_info) { l2p_t *l2p = l2p_get(); if (pid >= RTE_MAX_ETHPORTS || l2p->ports[pid].pid >= RTE_MAX_ETHPORTS) L2P_ERR_RET("Invalid port ID %u", pid); l2p->ports[pid].pcap_info = pcap_info; return 0; } /** * Return the PCAP mempool for a port. * * @param pid * Port ID to look up. * @return * Pointer to the PCAP mempool, or NULL on error. */ static __inline__ struct rte_mempool * l2p_get_pcap_mp(uint16_t pid) { l2p_t *l2p = l2p_get(); if (pid >= RTE_MAX_ETHPORTS || l2p->ports[pid].pid >= RTE_MAX_ETHPORTS || l2p->ports[pid].pcap_info == NULL) L2P_NULL_RET("Invalid port ID %u", pid); return l2p->ports[pid].pcap_info->mp; } /** * Set the PCAP mempool for a port. * * @param pid * Port ID to update. * @param mp * Mempool to associate with the port's PCAP state. * @return * 0 on success, -1 on invalid @p pid. */ static __inline__ int l2p_set_pcap_mp(uint16_t pid, struct rte_mempool *mp) { l2p_t *l2p = l2p_get(); if (pid >= RTE_MAX_ETHPORTS || l2p->ports[pid].pid >= RTE_MAX_ETHPORTS) L2P_ERR_RET("Invalid port ID %u", pid); l2p->ports[pid].pcap_info->mp = mp; return 0; } /** * Allocate and initialise the global l2p_t mapping table. */ void l2p_create(void); /** * Parse all registered mapping strings and populate the l2p_t table. * * @return * 0 on success, negative on parse error. */ int l2p_parse_mappings(void); /** * Register a mapping string for later parsing. * * @param str * Mapping string in the form expected by the -m argument parser. * @return * 0 on success, negative on error. */ int l2p_parse_mapping_add(const char *str); /** * Parse a hex portmask string into a bitmask of port indices. * * @param portmask * Null-terminated hex string (e.g. "0x3" selects ports 0 and 1). * @return * Bitmask of selected port IDs. */ uint32_t l2p_parse_portmask(const char *portmask); #ifdef __cplusplus } #endif #endif /* __L2P_H */ ================================================ FILE: app/lpktgenlib.c ================================================ /*- * Copyright(c) <2011-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2011 by Keith Wiles @ intel.com */ #define lpktgenlib_c #define LUA_LIB #define lua_c #include #include #include "lpktgenlib.h" #include #include #include #include #include #include "pktgen-cmds.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef __INTEL_COMPILER #pragma GCC diagnostic ignored "-Wcast-qual" #endif extern pktgen_t pktgen; void pktgen_quit(void); static int pktgen_exit(lua_State *L __rte_unused) { pktgen_quit(); return 0; } static inline double cycles_to_us(uint64_t cycles) { return (cycles == 0) ? 0.0 : ((1.0 / pktgen.hz) * (double)cycles) * Million; } static inline portlist_t pktgen_get_portlist(lua_State *L, int index) { portlist_t portlist; if (lua_isstring(L, index)) { if (portlist_parse(luaL_checkstring(L, index), pktgen.nb_ports, &portlist) < 0) portlist = INVALID_PORTLIST; } else if (lua_isnumber(L, index)) portlist = (uint64_t)lua_tonumber(L, index); else if (lua_isinteger(L, index)) portlist = (uint64_t)lua_tointeger(L, index); else { printf("%s: unknown Lua variable type not a string, number or int\n", __func__); portlist = INVALID_PORTLIST; } return portlist; } /** * * setf_integer - Helper routine to set Lua variables. * * DESCRIPTION * Helper routine to a set Lua variables. * * RETURNS: N/A * * SEE ALSO: */ static __inline__ void setf_integer(lua_State *L, const char *name, lua_Integer value) { lua_pushinteger(L, value); lua_setfield(L, -2, name); } /** * * setf_number - Helper routine to set Lua variables. * * DESCRIPTION * Helper routine to a set Lua variables. * * RETURNS: N/A * * SEE ALSO: */ static __inline__ void setf_number(lua_State *L, const char *name, lua_Number value) { lua_pushnumber(L, value); lua_setfield(L, -2, name); } /** * * setf_string - Helper routine to set Lua variables. * * DESCRIPTION * Helper routine to a set Lua variables. * * RETURNS: N/A * * SEE ALSO: */ static __inline__ void setf_string(lua_State *L, const char *name, const char *value) { lua_pushstring(L, value); lua_setfield(L, -2, name); } static __inline__ void setf_eth_stats(lua_State *L, const struct rte_eth_stats *stats) { setf_integer(L, "ipackets", stats->ipackets); setf_integer(L, "opackets", stats->opackets); setf_integer(L, "ibytes", stats->ibytes); setf_integer(L, "obytes", stats->obytes); setf_integer(L, "ierrors", stats->ierrors); setf_integer(L, "oerrors", stats->oerrors); setf_integer(L, "rx_nombuf", stats->rx_nombuf); setf_integer(L, "imissed", stats->imissed); } static __inline__ void setf_qstats(lua_State *L, const qstats_t *qs) { setf_integer(L, "ipackets", qs->q_ipackets); setf_integer(L, "opackets", qs->q_opackets); setf_integer(L, "errors", qs->q_errors); } static void push_port_stats_t(lua_State *L, const port_stats_t *ps, uint16_t rxq_cnt) { /* Assumes a Lua table is on the top of the stack. */ lua_newtable(L); /* curr */ setf_eth_stats(L, &ps->curr); lua_setfield(L, -2, "curr"); lua_newtable(L); /* sizes */ setf_integer(L, "_64", ps->sizes._64); setf_integer(L, "_65_127", ps->sizes._65_127); setf_integer(L, "_128_255", ps->sizes._128_255); setf_integer(L, "_256_511", ps->sizes._256_511); setf_integer(L, "_512_1023", ps->sizes._512_1023); setf_integer(L, "_1024_1522", ps->sizes._1024_1522); setf_integer(L, "broadcast", ps->sizes.broadcast); setf_integer(L, "multicast", ps->sizes.multicast); setf_integer(L, "jumbo", ps->sizes.jumbo); setf_integer(L, "runt", ps->sizes.runt); setf_integer(L, "unknown", ps->sizes.unknown); lua_setfield(L, -2, "sizes"); lua_newtable(L); /* qstats */ if (rxq_cnt > MAX_QUEUES_PER_PORT) rxq_cnt = MAX_QUEUES_PER_PORT; for (uint16_t qid = 0; qid < rxq_cnt; qid++) { lua_pushinteger(L, qid); lua_newtable(L); setf_qstats(L, &ps->qstats[qid]); lua_rawset(L, -3); } lua_setfield(L, -2, "qstats"); } static __inline__ void getf_etheraddr(lua_State *L, const char *field, struct rte_ether_addr *value) { lua_getfield(L, 3, field); if (lua_isstring(L, -1)) if (pg_ether_aton(luaL_checkstring(L, -1), value) == NULL) lua_putstring("failed to convert MAC string %s\n", luaL_checkstring(L, -1)); lua_pop(L, 1); } static __inline__ void getf_ipaddr(lua_State *L, const char *field, void *value, uint32_t flags) { lua_getfield(L, 3, field); if (lua_isstring(L, -1)) { _atoip((char *)(uintptr_t)luaL_checkstring(L, -1), flags, value, sizeof(struct pg_ipaddr)); } lua_pop(L, 1); } static __inline__ uint32_t getf_integer(lua_State *L, const char *field) { uint32_t value = 0; lua_getfield(L, 3, field); if (lua_isinteger(L, -1)) value = luaL_checkinteger(L, -1); lua_pop(L, 1); return value; } static __inline__ char * getf_string(lua_State *L, const char *field) { char *value = NULL; lua_getfield(L, 3, field); if (lua_isstring(L, -1)) value = (char *)luaL_checkstring(L, -1); lua_pop(L, 1); return value; } /** * * pktgen_set - Set a number of Pktgen values. * * DESCRIPTION * Set a number of Pktgen values for a given port list. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_set(lua_State *L) { uint32_t value; portlist_t portlist; char *what; switch (lua_gettop(L)) { default: return luaL_error(L, "set, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); what = (char *)luaL_checkstring(L, 2); value = luaL_checknumber(L, 3); foreach_port( portlist, _do(if (!strcasecmp(what, "count")) single_set_tx_count(pinfo, value); else if (!strcasecmp(what, "size")) single_set_pkt_size(pinfo, value); else if (!strcasecmp(what, "rate")) single_set_tx_rate(pinfo, luaL_checkstring(L, 3)); else if (!strcasecmp(what, "burst")) single_set_tx_burst(pinfo, value); else if (!strcasecmp(what, "txburst")) single_set_tx_burst(pinfo, value); else if (!strcasecmp(what, "rxburst")) single_set_rx_burst(pinfo, value); else if (!strcasecmp(what, "cycles")) debug_set_tx_cycles(pinfo, value); else if (!strcasecmp(what, "sport")) single_set_port_value(pinfo, what[0], value); else if (!strcasecmp(what, "dport")) single_set_port_value(pinfo, what[0], value); else if (!strcasecmp(what, "ttl")) single_set_ttl_value(pinfo, value); else if (!strcasecmp(what, "seq_cnt")) pktgen_set_port_seqCnt(pinfo, value); else if (!strcasecmp(what, "seqCnt")) pktgen_set_port_seqCnt(pinfo, value); else if (!strcasecmp(what, "prime")) pktgen_set_port_prime(pinfo, value); else if (!strcasecmp(what, "dump")) debug_set_port_dump(pinfo, value); else return luaL_error(L, "set does not support %s", what);)); pktgen_update_display(); return 0; } /** * * set_seq - Set the sequence data for a given port. * * DESCRIPTION * Set the sequence data for a given port and sequence number. * * RETURNS: N/A * * SEE ALSO: */ static int set_seq(lua_State *L, uint32_t seqnum) { portlist_t portlist; uint32_t pktsize, sport, dport, gtpu_teid; uint16_t vlanid; uint8_t cos, tos; struct rte_ether_addr daddr; struct rte_ether_addr saddr; struct pg_ipaddr ip_daddr; struct pg_ipaddr ip_saddr; char *proto, *ip; portlist = pktgen_get_portlist(L, 2); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); if (pg_ether_aton(luaL_checkstring(L, 3), &daddr) == NULL) { lua_putstring("invalid destination MAC string %s\n", luaL_checkstring(L, 3)); return -1; } if (pg_ether_aton(luaL_checkstring(L, 4), &saddr) == NULL) { lua_putstring("invalid source MAC string %s\n", luaL_checkstring(L, 4)); return -1; } sport = luaL_checkinteger(L, 7); dport = luaL_checkinteger(L, 8); /* Determine if we are IPv4 or IPv6 packets */ ip = (char *)luaL_checkstring(L, 9); _atoip(luaL_checkstring(L, 5), 0, &ip_daddr, sizeof(struct pg_ipaddr)); _atoip(luaL_checkstring(L, 6), PG_IPADDR_NETWORK, &ip_saddr, sizeof(struct pg_ipaddr)); proto = (char *)luaL_checkstring(L, 10); vlanid = luaL_checkinteger(L, 11); pktsize = luaL_checkinteger(L, 12); if (lua_gettop(L) == 13) gtpu_teid = luaL_checkinteger(L, 13); else gtpu_teid = 0; cos = 0; tos = 0; if (lua_gettop(L) > 13) { cos = luaL_checkinteger(L, 14); tos = luaL_checkinteger(L, 15); } if ((proto[0] == 'i') && (ip[3] == '6')) { lua_putstring("Must use IPv4 with ICMP type packets\n"); return -1; } foreach_port(portlist, pktgen_set_seq(pinfo, seqnum, &daddr, &saddr, &ip_daddr, &ip_saddr, sport, dport, ip[3], proto[0], vlanid, pktsize, gtpu_teid); pktgen_set_cos_tos_seq(pinfo, seqnum, cos, tos)); pktgen_update_display(); return 0; } /** * * pktgen_seq - Set the sequence data for a given port. * * DESCRIPTION * Set the sequence data for a given port and sequence number. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_seq(lua_State *L) { uint32_t seqnum; switch (lua_gettop(L)) { default: return luaL_error(L, "seq, wrong number of arguments"); case 12: case 13: break; } seqnum = luaL_checkinteger(L, 1); if (seqnum >= NUM_SEQ_PKTS) return -1; return set_seq(L, seqnum); } /** * * set_seqTable - Set the sequence data for a given port. * * DESCRIPTION * Set the sequence data for a given port and sequence number. * * RETURNS: N/A * * SEE ALSO: */ static int set_seqTable(lua_State *L, uint32_t seqnum) { portlist_t portlist; uint32_t pktSize, sport, dport, gtpu_teid; uint16_t vlanid; uint8_t cos, tos; struct rte_ether_addr daddr; struct rte_ether_addr saddr; struct pg_ipaddr ip_daddr; struct pg_ipaddr ip_saddr; char *ipProto, *ethType, *tcp_flags; portlist = pktgen_get_portlist(L, 2); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); getf_etheraddr(L, "eth_dst_addr", &daddr); getf_etheraddr(L, "eth_src_addr", &saddr); getf_ipaddr(L, "ip_dst_addr", &ip_daddr, PG_IPADDR_V4); getf_ipaddr(L, "ip_src_addr", &ip_saddr, PG_IPADDR_NETWORK | PG_IPADDR_V4); sport = getf_integer(L, "sport"); dport = getf_integer(L, "dport"); ipProto = getf_string(L, "ipProto"); ethType = getf_string(L, "ethType"); vlanid = getf_integer(L, "vlanid"); pktSize = getf_integer(L, "pktSize"); cos = getf_integer(L, "cos"); tos = getf_integer(L, "tos"); tcp_flags = getf_string(L, "tcp_flags"); gtpu_teid = getf_integer(L, "gtpu_teid"); if ((ipProto[0] == 'i') && (ethType[3] == '6')) { lua_putstring("Must use IPv4 with ICMP type packets\n"); return -1; } foreach_port(portlist, pktgen_set_seq(pinfo, seqnum, &daddr, &saddr, &ip_daddr, &ip_saddr, sport, dport, ethType[3], ipProto[0], vlanid, pktSize, gtpu_teid); pktgen_set_cos_tos_seq(pinfo, seqnum, cos, tos); seq_set_tcp_flags(pinfo, seqnum, tcp_flags)); pktgen_update_display(); return 0; } /** * * pktgen_seqTable - Set the sequence data for a given port. * * DESCRIPTION * Set the sequence data for a given port and sequence number. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_seqTable(lua_State *L) { uint32_t seqnum; switch (lua_gettop(L)) { default: return luaL_error(L, "seqTable, wrong number of arguments"); case 3: break; } seqnum = luaL_checkinteger(L, 1); if (seqnum >= NUM_SEQ_PKTS) return -1; return set_seqTable(L, seqnum); } /** * * pktgen_seq_tcp_flag - Set the TCP Flags for a given port and sequence number. * * DESCRIPTION * Set the sequence data for a given port and sequence number. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_seq_tcp_flags(lua_State *L) { portlist_t portlist; uint32_t seqnum; const char *flag_str; switch (lua_gettop(L)) { default: return luaL_error(L, "seq_tcp_flags, wrong number of arguments"); case 3: break; } seqnum = luaL_checkinteger(L, 1); if (seqnum >= NUM_SEQ_PKTS) return -1; portlist = pktgen_get_portlist(L, 2); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); flag_str = luaL_checkstring(L, 3); foreach_port(portlist, seq_set_tcp_flags(pinfo, seqnum, flag_str)); return 0; } /** * * pktgen_ports_per_page - Set the number of ports per page. * * DESCRIPTION * Set the number of ports per page. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_ports_per_page(lua_State *L) { switch (lua_gettop(L)) { default: return luaL_error(L, "ports_per_page, wrong number of arguments"); case 1: break; } pktgen_set_page_size(luaL_checkinteger(L, 1)); return 0; } /** * * pktgen_icmp - Enable or Disable ICMP echo processing. * * DESCRIPTION * Enable or disable ICMP echo process for a given port list. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_icmp(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "icmp, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_icmp_echo(pinfo, estate((char *)luaL_checkstring(L, 2)))); return 0; } /** * * pktgen_sendARP - Send ARP type packets from a given port list. * * DESCRIPTION * Send APR request and gratuitous packets for a given port list. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_sendARP(lua_State *L) { portlist_t portlist; char *what; switch (lua_gettop(L)) { default: return luaL_error(L, "sendARP, wrong number of arguments"); case 2: break; } what = (char *)luaL_checkstring(L, 2); portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pktgen_send_arp_requests(pinfo, (what[0] == 'g') ? GRATUITOUS_ARP : 0)); return 0; } /** * * pktgen_set_mac - Set the MAC address for a set of ports. * * DESCRIPTION * Set the MAC address for a set of ports. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_set_mac(lua_State *L) { portlist_t portlist; struct rte_ether_addr mac; switch (lua_gettop(L)) { default: return luaL_error(L, "set_mac, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); if (pg_ether_aton(luaL_checkstring(L, 3), &mac) == NULL) { lua_putstring("invalid MAC string (%s)\n ", luaL_checkstring(L, 3)); return luaL_error(L, "invalid MAC string"); } foreach_port(portlist, single_set_mac(pinfo, luaL_checkstring(L, 2), &mac)); pktgen_update_display(); return 0; } /** * * pktgen_macFromArp - Enable or Disable getting MAC address from ARP packets. * * DESCRIPTION * Enable or disable getting MAC address from an ARP request. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_macFromArp(lua_State *L) { char *state; uint32_t onOff; switch (lua_gettop(L)) { default: return luaL_error(L, "mac_from_arp, wrong number of arguments"); case 1: break; } state = (char *)luaL_checkstring(L, 1); onOff = estate(state); enable_mac_from_arp(onOff); return 0; } /** * * pktgen_prototype - Set the packet protocol type. * * DESCRIPTION * Set the packet protocol type. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_prototype(lua_State *L) { char *type; portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "set_proto, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); type = (char *)luaL_checkstring(L, 2); foreach_port(portlist, single_set_proto(pinfo, type)); return 0; } /** * * pktgen_set_ip_addr - Set the ip address value for src and dst. * * DESCRIPTION * Set the IP address for src and dst. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_set_ip_addr(lua_State *L) { portlist_t portlist; struct pg_ipaddr ipaddr; int flags; char *type; int ip_ver; switch (lua_gettop(L)) { default: return luaL_error(L, "set_ipaddr, wrong number of arguments"); case 3: break; } type = (char *)luaL_checkstring(L, 2); portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); if (type[0] == 's') flags = PG_IPADDR_NETWORK; ip_ver = _atoip(luaL_checkstring(L, 3), flags, &ipaddr, sizeof(struct pg_ipaddr)); foreach_port(portlist, single_set_ipaddr(pinfo, type[0], &ipaddr, ip_ver)); pktgen_update_display(); return 0; } /** * * pktgen_set_type - Set the type of packet IPv4/v6 * * DESCRIPTION * Set the port packet types to IPv4 or v6. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_set_type(lua_State *L) { char *type; portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "set_type, wrong number of arguments"); case 2: break; } type = (char *)luaL_checkstring(L, 2); portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, single_set_pkt_type(pinfo, type)); pktgen_update_display(); return 0; } /** * * pktgen_set_tcp_flags - Set the TCP flags * * DESCRIPTION * Set the port packet types to IPv4 or v6. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_set_tcp_flags(lua_State *L) { char *type; portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "set_type, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); type = (char *)luaL_checkstring(L, 2); foreach_port(portlist, single_set_tcp_flags(pinfo, type)); pktgen_update_display(); return 0; } /** * * pktgen_send_ping4 - Send ping packets for IPv4 * * DESCRIPTION * Send a ping packet for IPv4. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_send_ping4(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "ping4, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pktgen_ping4(pinfo)); return 0; } #ifdef INCLUDE_PING6 /** * * pktgen_send_ping6 - Send IPv6 ICMP echo requests. * * DESCRIPTION * Send IPv6 ICMP echo requests. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_send_ping6(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "ping6, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pktgen_ping6(pinfo)); return 0; } #endif /** * * pktgen_pcap - Enable or disable PCAP support sending. * * DESCRIPTION * Enable or disable PCAP sending. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_pcap(lua_State *L) { portlist_t portlist; char *what; switch (lua_gettop(L)) { default: return luaL_error(L, "pcap, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); what = (char *)luaL_checkstring(L, 2); foreach_port(portlist, enable_pcap(pinfo, estate(what))); return 0; } /** * * pktgen_start - Start ports sending packets. * * DESCRIPTION * Start ports sending packets. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_start(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "start, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pktgen_start_transmitting(pinfo)); return 0; } /** * * pktgen_stop - Stop ports from sending packets * * DESCRIPTION * Stop port from sending packets. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_stop(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "stop, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pktgen_stop_transmitting(pinfo)); return 0; } /** * * pktgen_scrn - Enable or Disable the screen updates. * * DESCRIPTION * Enable or disable screen updates. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_scrn(lua_State *L) { switch (lua_gettop(L)) { default: return luaL_error(L, "screen, wrong number of arguments"); case 1: break; } pktgen_screen(estate((const char *)luaL_checkstring(L, 1))); return 0; } /** * * pktgen_prime - Send a set of packet to prime the forwarding tables. * * DESCRIPTION * Send a small set of packet to prime the forwarding table on a port. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_prime(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "prime, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pktgen_prime_ports(pinfo)); return 0; } static __inline__ void __delay(int32_t t) { int32_t n; while (t > 0) { n = (t > 10) ? 10 : t; rte_delay_us_sleep(n * 1000); t -= n; } } /** * * pktgen_delay - Delay for a given number of milliseconds. * * DESCRIPTION * Delay a script for a given number of milliseconds. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_delay(lua_State *L) { switch (lua_gettop(L)) { default: return luaL_error(L, "delay, wrong number of arguments"); case 1: break; } __delay(luaL_checkinteger(L, 1)); return 0; } /** * * pktgen_pause - Delay for a given number of milliseconds and display a message * * DESCRIPTION * Delay a script for a given number of milliseconds and display a message * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_pause(lua_State *L) { char *str; int v; switch (lua_gettop(L)) { default: return luaL_error(L, "pause, wrong number of arguments"); case 2: break; } str = (char *)luaL_checkstring(L, 1); if (strlen(str) > 0) lua_putstring(str); v = luaL_checkinteger(L, 2); __delay(v); return 0; } /** * * pktgen_continue - Display a message and wait for a single keyboard input. * * DESCRIPTION * Display a message and wait for a keyboard input. * * RETURNS: the single keyboard character typed as a string. * * SEE ALSO: */ static int pktgen_continue(lua_State *L) { char buf[4], *str; int n; switch (lua_gettop(L)) { default: return luaL_error(L, "continue, wrong number of arguments"); case 1: break; } str = (char *)luaL_checkstring(L, 1); if (strlen(str) > 0) lua_putstring(str); buf[0] = '\0'; n = fread(buf, 1, 1, (FILE *)lua_get_stdin(pktgen.ld)); if (n > 0) buf[n] = '\0'; lua_pushstring(L, buf); return 1; } /** * * pktgen_input - Display a message and wait for keyboard input. * * DESCRIPTION * Display a message and wait for a keyboard input. * * RETURNS: keyboard string typed at display * * SEE ALSO: */ static int pktgen_input(lua_State *L) { char buf[256], c, *str; uint32_t n, idx; switch (lua_gettop(L)) { default: return luaL_error(L, "input, wrong number of arguments"); case 1: break; } str = (char *)luaL_checkstring(L, 1); if (strlen(str) > 0) lua_putstring(str); idx = 0; buf[idx] = '\0'; while (idx < (sizeof(buf) - 2)) { n = fread(&c, 1, 1, (FILE *)lua_get_stdin(pktgen.ld)); if ((n <= 0) || (c == '\r') || (c == '\n')) break; buf[idx++] = c; } buf[idx] = '\0'; lua_pushstring(L, buf); return 1; } /** * * pktgen_sleep - Sleep for a given number of seconds. * * DESCRIPTION * Delay a script for a given number of seconds. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_sleep(lua_State *L) { switch (lua_gettop(L)) { default: return luaL_error(L, "sleep, wrong number of arguments"); case 1: break; } __delay(luaL_checkinteger(L, 1) * 1000); return 0; } /** * * pktgen_load - Load and execute a script. * * DESCRIPTION * Load and execute a script * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_load(lua_State *L) { char *path; switch (lua_gettop(L)) { default: return luaL_error(L, "load, wrong number of arguments"); case 1: break; } path = (char *)luaL_checkstring(L, 1); if (cli_execute_cmdfile(path)) return luaL_error(L, "load command failed for %s\n", path); return 0; } /** * * pktgen_config_save - Save to a configuration file. * * DESCRIPTION * Save configuration to a file. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_config_save(lua_State *L) { char *path; switch (lua_gettop(L)) { default: return luaL_error(L, "save, wrong number of arguments"); case 1: break; } path = (char *)luaL_checkstring(L, 1); if (pktgen_save(path)) return luaL_error(L, "save command failed for %s\n", path); return 0; } /** * * pktgen_clear - Clear all port statistics * * DESCRIPTION * Clear all port statistics to zero for a given port * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_clear(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "clear, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pktgen_clear_stats(pinfo)); pktgen_update_display(); return 0; } /** * * pktgen_clear_all - Clear all port statistics * * DESCRIPTION * Clear all port statistics to zero for a given port * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_clear_all(lua_State *L __rte_unused) { forall_ports(pktgen_clear_stats(pinfo)); pktgen_update_display(); return 0; } /** * * pktgen_cls_screen - Clear and redraw the screen * * DESCRIPTION * Clear and redraw the screen * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_cls_screen(lua_State *L __rte_unused) { pktgen_clear_display(); return 0; } /** * * pktgen_update - Update the screen information * * DESCRIPTION * Update the screen information * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_update_screen(lua_State *L __rte_unused) { pktgen_update_display(); return 0; } /** * * pktgen_reset_config - Reset pktgen to all default values. * * DESCRIPTION * Reset pktgen to all default values. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_reset_config(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "reset, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pktgen_reset(pinfo)); return 0; } /** * * pktgen_restart - Reset ports * * DESCRIPTION * Reset ports * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_restart(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "reset, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pktgen_port_restart(pinfo)); return 0; } /** * * range_dst_mac - Set a destination MAC address * * DESCRIPTION * Set a destination MAC address. * * RETURNS: N/A * * SEE ALSO: */ static int range_dst_mac(lua_State *L) { portlist_t portlist; struct rte_ether_addr mac; switch (lua_gettop(L)) { default: return luaL_error(L, "dst_mac, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); if (pg_ether_aton(luaL_checkstring(L, 3), &mac) == NULL) { lua_putstring("invalid destination MAC string %s\n", luaL_checkstring(L, 3)); return -1; } foreach_port(portlist, range_set_dest_mac(pinfo, luaL_checkstring(L, 2), &mac)); pktgen_update_display(); return 0; } /** * * range_src_mac - Set the source MAC address in the range data. * * DESCRIPTION * Set the source MAC address for a given set of ports in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_src_mac(lua_State *L) { portlist_t portlist; struct rte_ether_addr mac; switch (lua_gettop(L)) { default: return luaL_error(L, "src_mac, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); if (pg_ether_aton(luaL_checkstring(L, 3), &mac) == NULL) { lua_putstring("invalid source MAC string %s\n", luaL_checkstring(L, 3)); return -1; } foreach_port(portlist, range_set_src_mac(pinfo, luaL_checkstring(L, 2), &mac)); pktgen_update_display(); return 0; } /** * * range_set_type - Set the type of range packet IPv4/v6 * * DESCRIPTION * Set the range packet types to IPv4 or v6. * * RETURNS: N/A * * SEE ALSO: */ static int range_set_type(lua_State *L) { char *type; portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "pkt_type, wrong number of arguments"); case 2: break; } type = (char *)luaL_checkstring(L, 2); portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, range_set_pkt_type(pinfo, type)); pktgen_update_display(); return 0; } /** * * range_dst_ip - Set the IP address in the range data. * * DESCRIPTION * Set the IP address in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_dst_ip(lua_State *L) { portlist_t portlist; struct pg_ipaddr ipaddr; char *type; switch (lua_gettop(L)) { default: return luaL_error(L, "dst_ip, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); _atoip(luaL_checkstring(L, 3), 0, &ipaddr, sizeof(struct pg_ipaddr)); type = (char *)luaL_checkstring(L, 2); foreach_port(portlist, range_set_dst_ip(pinfo, type, &ipaddr)); pktgen_update_display(); return 0; } /** * * range_src_ip - Set the source IP address in the range data. * * DESCRIPTION * Set the source IP address in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_src_ip(lua_State *L) { portlist_t portlist; struct pg_ipaddr ipaddr; char *type; switch (lua_gettop(L)) { default: return luaL_error(L, "src_ip, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); _atoip(luaL_checkstring(L, 3), 0, &ipaddr, sizeof(ipaddr)); type = (char *)luaL_checkstring(L, 2); foreach_port(portlist, range_set_src_ip(pinfo, type, &ipaddr)); pktgen_update_display(); return 0; } /** * * range_dst_port - Set the port type in the range data. * * DESCRIPTION * Set the port type in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_dst_port(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "dst_port, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, range_set_dst_port(pinfo, (char *)luaL_checkstring(L, 2), luaL_checkinteger(L, 3))); pktgen_update_display(); return 0; } /** * * range_ip_proto - Set the ip proto value in the range data. * * DESCRIPTION * Set the ip proto value in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_ip_proto(lua_State *L) { portlist_t portlist; const char *ip; switch (lua_gettop(L)) { default: return luaL_error(L, "ip_proto, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); ip = luaL_checkstring(L, 2); foreach_port(portlist, range_set_proto(pinfo, ip)); pktgen_update_display(); return 0; } /** * * range_src_port - Set the source port value in the range data. * * DESCRIPTION * Set the source port value in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_src_port(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "src_port, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, range_set_src_port(pinfo, (char *)luaL_checkstring(L, 2), luaL_checkinteger(L, 3))); pktgen_update_display(); return 0; } /** * * range_ttl - Set the ttl value in the range data. * * DESCRIPTION * Set the ttl value in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_ttl(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "set ttl, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, range_set_ttl(pinfo, (char *)luaL_checkstring(L, 2), luaL_checkinteger(L, 3))); pktgen_update_display(); return 0; } /** * * range_hop_limits - Set the hop_limits value in the range data. * * DESCRIPTION * Set the Hop Limits value in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_hop_limits(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "set hop_limits, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, range_set_hop_limits(pinfo, (char *)luaL_checkstring(L, 2), luaL_checkinteger(L, 3))); pktgen_update_display(); return 0; } /** * * pktgen_gtpu_teid - Set the GTPU-TEID value in the range data. * * DESCRIPTION * Set the source port value in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_gtpu_teid(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "GTP-U TEID, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, range_set_gtpu_teid(pinfo, (char *)luaL_checkstring(L, 2), luaL_checkinteger(L, 3))); pktgen_update_display(); return 0; } /** * * range_vlan_id - Set the VLAN id in the range data. * * DESCRIPTION * Set the VLAN id in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_vlan_id(lua_State *L) { portlist_t portlist; uint32_t vlan_id; switch (lua_gettop(L)) { default: return luaL_error(L, "vlan_id, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); vlan_id = luaL_checkinteger(L, 3); foreach_port(portlist, range_set_vlan_id(pinfo, (char *)luaL_checkstring(L, 2), vlan_id)); pktgen_update_display(); return 0; } /** * * range_cos - Set the CoS in the range data. * * DESCRIPTION * Set the CoS in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_cos(lua_State *L) { portlist_t portlist; uint32_t cos; switch (lua_gettop(L)) { default: return luaL_error(L, "cos, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); cos = luaL_checkinteger(L, 3); foreach_port(portlist, range_set_cos_id(pinfo, (char *)luaL_checkstring(L, 2), cos)); pktgen_update_display(); return 0; } /** * * range_tos - Set the ToS in the range data. * * DESCRIPTION * Set the ToS in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_tos(lua_State *L) { portlist_t portlist; uint32_t tos; switch (lua_gettop(L)) { default: return luaL_error(L, "tos, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); tos = luaL_checkinteger(L, 3); foreach_port(portlist, range_set_tos_id(pinfo, (char *)luaL_checkstring(L, 2), tos)); pktgen_update_display(); return 0; } /** * * range_traffic_class - Set the traffic class in the range data. * * DESCRIPTION * Set the Traffic Class in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_traffic_class(lua_State *L) { portlist_t portlist; uint32_t traffic_class; switch (lua_gettop(L)) { default: return luaL_error(L, "traffic_class, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); traffic_class = luaL_checkinteger(L, 3); foreach_port(portlist, range_set_traffic_class(pinfo, (char *)luaL_checkstring(L, 2), traffic_class)); pktgen_update_display(); return 0; } /** * * single_vlan_id - Set the VLAN id for a single port * * DESCRIPTION * Set the VLAN id for a single port. * * RETURNS: N/A * * SEE ALSO: */ static int single_vlan_id(lua_State *L) { portlist_t portlist; uint32_t vlanid; switch (lua_gettop(L)) { default: return luaL_error(L, "vlanid, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); vlanid = luaL_checkinteger(L, 2); if ((vlanid < MIN_VLAN_ID) || (vlanid > MAX_VLAN_ID)) vlanid = 1; foreach_port(portlist, single_set_vlan_id(pinfo, vlanid)); pktgen_update_display(); return 0; } /** * * single_cos - Set the 802.1p prio for a single port * * DESCRIPTION * Set the 802.1p cos for a single port. * * RETURNS: N/A * * SEE ALSO: */ static int single_cos(lua_State *L) { portlist_t portlist; uint8_t cos; switch (lua_gettop(L)) { default: return luaL_error(L, "cos, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); cos = luaL_checkinteger(L, 3); if (cos > MAX_COS) cos = 0; foreach_port(portlist, single_set_cos(pinfo, cos)); pktgen_update_display(); return 0; } /** * * single_tos - Set the TOS for a single port * * DESCRIPTION * Set the TOS for a single port. * * RETURNS: N/A * * SEE ALSO: */ static int single_tos(lua_State *L) { portlist_t portlist; uint8_t tos; switch (lua_gettop(L)) { default: return luaL_error(L, "tos, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); tos = luaL_checkinteger(L, 2); foreach_port(portlist, single_set_tos(pinfo, tos)); pktgen_update_display(); return 0; } /** * * single_vxlan_id - Set the VxLAN for a single port * * DESCRIPTION * Set the VxLAN for a single port. * * RETURNS: N/A * * SEE ALSO: */ static int single_vxlan_id(lua_State *L) { portlist_t portlist; uint8_t flags, group_id; uint32_t vxlan_id; switch (lua_gettop(L)) { default: return luaL_error(L, "tos, wrong number of arguments"); case 4: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); flags = luaL_checkinteger(L, 2); group_id = luaL_checkinteger(L, 3); vxlan_id = luaL_checkinteger(L, 4); foreach_port(portlist, single_set_vxlan(pinfo, flags, group_id, vxlan_id)); pktgen_update_display(); return 0; } /** * * single_vlan - Enable or Disable vlan header * * DESCRIPTION * Enable or disable insertion of VLAN header. * * RETURNS: N/A * * SEE ALSO: */ static int single_vlan(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "process, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_vlan(pinfo, estate(luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * single_vxlan - Enable or Disable vxlan header * * DESCRIPTION * Enable or disable insertion of VxLAN header. * * RETURNS: N/A * * SEE ALSO: */ static int single_vxlan(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "process, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_vxlan(pinfo, estate(luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * range_mpls_entry - Set the MPLS entry in the range data. * * DESCRIPTION * Set the VLAN id in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_mpls_entry(lua_State *L) { portlist_t portlist; uint32_t mpls_entry; switch (lua_gettop(L)) { default: return luaL_error(L, "mpls_entry, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); mpls_entry = strtoul(luaL_checkstring(L, 2), NULL, 16); foreach_port(portlist, range_set_mpls_entry(pinfo, mpls_entry)); pktgen_update_display(); return 0; } /** * * pktgen_mpls - Enable or Disable MPLS header * * DESCRIPTION * Enable or disable insertion of MPLS header. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_mpls(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "mpls, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_mpls(pinfo, estate(luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * range_qinqids - Set the Q-in-Q ID's in the range data. * * DESCRIPTION * Set the Q-in-Q ID's in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_qinqids(lua_State *L) { portlist_t portlist; uint32_t qinq_id1, qinq_id2; switch (lua_gettop(L)) { default: return luaL_error(L, "qinqids, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); qinq_id1 = luaL_checkinteger(L, 2); if ((qinq_id1 < MIN_VLAN_ID) || (qinq_id1 > MAX_VLAN_ID)) qinq_id1 = 1; qinq_id2 = luaL_checkinteger(L, 3); if ((qinq_id2 < MIN_VLAN_ID) || (qinq_id2 > MAX_VLAN_ID)) qinq_id2 = 1; foreach_port(portlist, single_set_qinqids(pinfo, qinq_id1, qinq_id2)); pktgen_update_display(); return 0; } /** * * pktgen_qinq - Enable or Disable Q-in-Q header * * DESCRIPTION * Enable or disable insertion of Q-in-Q header. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_qinq(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "qinq, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_qinq(pinfo, estate(luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * range_gre_key - Set the GRE key in the range data. * * DESCRIPTION * Set the GRE key in the range data. * * RETURNS: N/A * * SEE ALSO: */ static int range_gre_key(lua_State *L) { portlist_t portlist; uint32_t gre_key; switch (lua_gettop(L)) { default: return luaL_error(L, "gre_key, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); gre_key = luaL_checkinteger(L, 2); foreach_port(portlist, range_set_gre_key(pinfo, gre_key)); pktgen_update_display(); return 0; } /** * * pktgen_gre - Enable or Disable GRE with IPv4 payload * * DESCRIPTION * Enable or disable GRE with IPv4 payload. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_gre(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "gre, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_gre(pinfo, estate(luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * pktgen_gre_eth - Enable or Disable GRE with Ethernet payload * * DESCRIPTION * Enable or disable GRE with Ethernet payload * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_gre_eth(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "gre_eth, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_gre_eth(pinfo, estate(luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * pktgen_rnd_s_ip - Enable or disable randomizing the source IP address * * DESCRIPTION * Enable or disable randomizing the source IP address * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_rnd_s_ip(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "rnd_s_ip, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_rnd_s_ip(pinfo, estate(luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * pktgen_rnd_s_pt - Enable or disable randomizing the source port * * DESCRIPTION * Enable or disable randomizing the source port * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_rnd_s_pt(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "rnd_s_pt, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_rnd_s_pt(pinfo, estate(luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * range_pkt_size - Set the port range size. * * DESCRIPTION * Set the port range size. * * RETURNS: N/A * * SEE ALSO: */ static int range_pkt_size(lua_State *L) { portlist_t portlist; uint32_t size; char *type; switch (lua_gettop(L)) { default: return luaL_error(L, "pkt_size, wrong number of arguments"); case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); type = (char *)luaL_checkstring(L, 2); size = luaL_checkinteger(L, 3); foreach_port(portlist, range_set_pkt_size(pinfo, type, size)); pktgen_update_display(); return 0; } /** * * range - Enable or disable the range data sending. * * DESCRIPTION * Enable or disable the range data sending. * * RETURNS: N/A * * SEE ALSO: */ static int range(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "range, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_range(pinfo, estate((const char *)luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * pktgen_latency - Enable or disable the latency testing. * * DESCRIPTION * Enable or disable the latency testing. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_latency(lua_State *L) { portlist_t portlist; char *what = NULL; switch (lua_gettop(L)) { default: return luaL_error(L, "latency, wrong number of arguments"); case 2: case 3: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); what = (char *)luaL_checkstring(L, 2); if (!what) return luaL_error(L, "latency, missing argument or not a string"); if (lua_gettop(L) == 3) { if (strncasecmp(lua_tostring(L, 2), "rate", 4) == 0) foreach_port(portlist, latency_set_rate(pinfo, (uint32_t)luaL_checkinteger(L, 3))); else if (strncasecmp(lua_tostring(L, 2), "entropy", 7) == 0) foreach_port(portlist, latency_set_entropy(pinfo, (uint16_t)luaL_checkinteger(L, 3))); else return luaL_error(L, "latency, invalid arguments must be rate or entropy"); } else if (lua_gettop(L) == 2) foreach_port(portlist, enable_latency(pinfo, estate((const char *)luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * pktgen_jitter - Set Jitter threshold * * DESCRIPTION * Set Jitter threshold in micro-seconds * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_jitter(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "jitter, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, single_set_jitter(pinfo, luaL_checkinteger(L, 2))); pktgen_update_display(); return 0; } /** * * pktgen_pattern - Set the pattern type. * * DESCRIPTION * Set the pattern type. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_pattern(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "pattern, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pattern_set_type(pinfo, (char *)luaL_checkstring(L, 2))); pktgen_update_display(); return 0; } /** * * pktgen_pattern - Set the pattern type. * * DESCRIPTION * Set the pattern type. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_user_pattern(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "user.pattern, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pattern_set_user_pattern(pinfo, (char *)luaL_checkstring(L, 2))); pktgen_update_display(); return 0; } /** * * pktgen_page - Set the page type to be displayed. * * DESCRIPTION * Set the page type to be displayed. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_page(lua_State *L) { switch (lua_gettop(L)) { default: return luaL_error(L, "page, wrong number of arguments"); case 1: break; } pktgen_set_page((char *)luaL_checkstring(L, 1)); return 0; } /** * * pktgen_port - Set the port type number * * DESCRIPTION * Set the port type IPv4 or IPv6 * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_port(lua_State *L) { switch (lua_gettop(L)) { default: return luaL_error(L, "port, wrong number of arguments"); case 1: break; } pktgen_set_port_number((uint16_t)luaL_checkinteger(L, 1)); return 0; } /** * * pktgen_process - Enable or Disable input packet processing. * * DESCRIPTION * Enable or disable input packet processing. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_process(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "process, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_process(pinfo, estate((const char *)luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * pktgen_capture - Enable or Disable capture packet processing. * * DESCRIPTION * Enable or disable capture packet processing. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_capture(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "capture, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_capture(pinfo, estate((const char *)luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) /** * * pktgen_bonding - Enable or Disable bonding to send zero packets * * DESCRIPTION * Enable or disable bonding packet processing. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_bonding(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "bonding, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, enable_bonding(pinfo, estate((const char *)luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } #endif /** * * pktgen_latsampler_params - Set latency sampler params. * * DESCRIPTION * Set latency sampler params. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_latsampler_params(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "latsampler_params, wrong number of arguments"); case 5: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, single_set_latsampler_params(pinfo, (char *)luaL_checkstring(L, 2), (uint32_t)luaL_checkinteger(L, 3), (uint32_t)luaL_checkinteger(L, 4), (char *)luaL_checkstring(L, 5))); pktgen_update_display(); return 0; } /** * * pktgen_latsampler - Enable or Disable latency sampler. * * DESCRIPTION * Enable or disable latency sampler. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_latsampler(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "latsampler, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, pktgen_start_stop_latency_sampler(pinfo, estate((char *)luaL_checkstring(L, 2)))); pktgen_update_display(); return 0; } /** * * pktgen_blink - Enable or disable port Led blinking. * * DESCRIPTION * Enable or disable port led blinking. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_blink(lua_State *L) { portlist_t portlist; switch (lua_gettop(L)) { default: return luaL_error(L, "blink, wrong number of arguments"); case 2: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); foreach_port(portlist, debug_blink(pinfo, estate((const char *)luaL_checkstring(L, 2)))); if (pktgen.blinklist) pktgen.flags |= BLINK_PORTS_FLAG; else pktgen.flags &= ~BLINK_PORTS_FLAG; pktgen_update_display(); return 0; } /** * * isSending - Get the current state of the transmitter on a port. * * DESCRIPTION * Get the current state of the transmitter on a port. * * RETURNS: N/A * * SEE ALSO: */ static void isSending(lua_State *L, port_info_t *pinfo) { lua_pushinteger(L, pinfo->pid); /* Push the table index */ lua_pushstring(L, pktgen_port_transmitting(pinfo->pid) ? "y" : "n"); /* Now set the table as an array with pid as the index. */ lua_rawset(L, -3); } /** * * pktgen_isSending - Get the current state of the transmitter on a port. * * DESCRIPTION * Get the current state of the transmitter on a port. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_isSending(lua_State *L) { portlist_t portlist; uint32_t n; switch (lua_gettop(L)) { default: return luaL_error(L, "isSending, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); lua_newtable(L); n = 0; foreach_port(portlist, _do(isSending(L, pinfo); n++)); setf_integer(L, "n", n); return 1; } /** * * link_state - Get the current link state of a port. * * DESCRIPTION * Get the current link state of a port. * * RETURNS: N/A * * SEE ALSO: */ static void link_state(lua_State *L, port_info_t *pinfo) { char buff[32]; lua_pushinteger(L, pinfo->pid); /* Push the table index */ lua_pushstring(L, pktgen_link_state(pinfo->pid, buff, sizeof(buff))); /* Now set the table as an array with pid as the index. */ lua_rawset(L, -3); } /** * * pktgen_linkState - Get the current link state of a port. * * DESCRIPTION * Get the current link state of a port. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_linkState(lua_State *L) { portlist_t portlist; uint32_t n; switch (lua_gettop(L)) { default: return luaL_error(L, "linkState, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); lua_newtable(L); n = 0; foreach_port(portlist, _do(link_state(L, pinfo); n++)); setf_integer(L, "n", n); return 1; } /** * * port_stats - Return the other port stats for a given port. * * DESCRIPTION * Return the packet stats for a given port. * * RETURNS: N/A * * SEE ALSO: */ static void port_stats(lua_State *L, port_info_t *pinfo) { port_stats_t ps = {0}; uint16_t rxq_cnt; pktgen_port_stats(pinfo->pid, &ps); lua_pushinteger(L, pinfo->pid); /* Push the table index */ lua_newtable(L); /* Full port_stats_t structure as nested table. */ rxq_cnt = l2p_get_rxcnt(pinfo->pid); push_port_stats_t(L, &ps, rxq_cnt); /* Now set the table as an array with pid as the index. */ lua_rawset(L, -3); } /** * * pktgen_portStats - Return the other port stats for a given ports. * * DESCRIPTION * Return the other port stats for a given ports. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_portStats(lua_State *L) { portlist_t portlist; uint32_t n; switch (lua_gettop(L)) { default: return luaL_error(L, "portStats, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); lua_newtable(L); n = 0; foreach_port(portlist, _do(port_stats(L, pinfo); n++)); setf_integer(L, "n", n); return 1; } /** * * port_info - Return the other port information for a given port. * * DESCRIPTION * Return the other port information for a given port. * * RETURNS: N/A * * SEE ALSO: */ static void port_info(lua_State *L, port_info_t *pinfo) { struct rte_eth_dev_info dev = {0}; pkt_seq_t *pkt; char buff[32] = {0}; pkt = &pinfo->seq_pkt[SINGLE_PKT]; /*------------------------------------*/ lua_pushinteger(L, pinfo->pid); /* Push the table index */ lua_newtable(L); /* Create the structure table for a packet */ /*------------------------------------*/ lua_pushstring(L, "total_stats"); lua_newtable(L); setf_integer(L, "max_ipackets", pktgen.max_total_ipackets); setf_integer(L, "max_opackets", pktgen.max_total_opackets); setf_integer(L, "cumm_rate_ipackets", pktgen.cumm_rate_totals.ipackets); setf_integer(L, "cumm_rate_opackets", pktgen.cumm_rate_totals.opackets); setf_integer(L, "cumm_rate_itotals", iBitsTotal(pktgen.cumm_rate_totals)); setf_integer(L, "cumm_rate_ototals", oBitsTotal(pktgen.cumm_rate_totals)); lua_rawset(L, -3); /*------------------------------------*/ lua_pushstring(L, "info"); lua_newtable(L); setf_string(L, "pattern_type", (pinfo->fill_pattern_type == ABC_FILL_PATTERN) ? "abcd..." : (pinfo->fill_pattern_type == NO_FILL_PATTERN) ? "None" : (pinfo->fill_pattern_type == ZERO_FILL_PATTERN) ? "Zero" : pinfo->user_pattern); if (rte_atomic64_read(&pinfo->transmit_count) == 0) setf_string(L, "tx_count", "Forever"); else setf_integer(L, "tx_count", rte_atomic64_read(&pinfo->transmit_count)); setf_integer(L, "tx_rate", pinfo->tx_rate); setf_integer(L, "pkt_size", pkt->pkt_size + RTE_ETHER_CRC_LEN); setf_integer(L, "tx_burst", pinfo->tx_burst); setf_integer(L, "rx_burst", pinfo->rx_burst); setf_string(L, "eth_type", (pkt->ethType == RTE_ETHER_TYPE_IPV4) ? "IPv4" : (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? "IPv6" : (pkt->ethType == RTE_ETHER_TYPE_ARP) ? "ARP" : "Other"); setf_string(L, "proto_type", (pkt->ipProto == PG_IPPROTO_TCP) ? "TCP" : (pkt->ipProto == PG_IPPROTO_ICMP) ? "ICMP" : (rte_atomic64_read(&pinfo->port_flags) & SEND_VXLAN_PACKETS) ? "VXLAN" : "UDP"); setf_integer(L, "vlanid", pkt->vlanid); /*------------------------------------*/ lua_pushstring(L, "l2_l3_info"); lua_newtable(L); setf_integer(L, "src_port", pkt->sport); setf_integer(L, "dst_port", pkt->dport); setf_integer(L, "ttl", pkt->ttl); inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_dst_addr.addr.ipv4.s_addr), 0xFFFFFFFF); setf_string(L, "dst_ip", buff); inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_src_addr.addr.ipv4.s_addr), pkt->ip_mask); setf_string(L, "src_ip", buff); inet_mtoa(buff, sizeof(buff), &pkt->eth_dst_addr); setf_string(L, "dst_mac", buff); inet_mtoa(buff, sizeof(buff), &pkt->eth_src_addr); setf_string(L, "src_mac", buff); lua_rawset(L, -3); if (rte_eth_dev_info_get(pinfo->pid, &dev) < 0) rte_exit(EXIT_FAILURE, "Cannot get device info for port %u\n", pinfo->pid); const struct rte_bus *bus = NULL; if (dev.device) bus = rte_bus_find_by_device(dev.device); if (bus && !strcmp(rte_bus_name(bus), "pci")) { char name[RTE_ETH_NAME_MAX_LEN]; char vend[8], device[8]; vend[0] = device[0] = '\0'; sscanf(rte_dev_bus_info(dev.device), "vendor_id=%4s, device_id=%4s", vend, device); rte_eth_dev_get_name_by_port(pinfo->pid, name); snprintf(buff, sizeof(buff), "%d/%s:%s/%s", rte_dev_numa_node(dev.device), vend, device, rte_dev_name(dev.device)); } else snprintf(buff, sizeof(buff), "%04x:%04x/%02x:%02d.%d", 0, 0, 0, 0, 0); setf_string(L, "pci_vendor", buff); /*------------------------------------*/ lua_pushstring(L, "tx_debug"); lua_newtable(L); setf_integer(L, "tx_pps", pinfo->tx_pps); setf_integer(L, "tx_cycles", pinfo->tx_cycles); lua_rawset(L, -3); /*------------------------------------*/ lua_pushstring(L, "802.1p"); lua_newtable(L); setf_integer(L, "cos", pkt->cos); setf_integer(L, "dscp", pkt->tos >> 2); setf_integer(L, "ipp", pkt->tos >> 5); lua_rawset(L, -3); /*------------------------------------*/ lua_pushstring(L, "VxLAN"); lua_newtable(L); setf_integer(L, "vni_flags", pkt->vni_flags); setf_integer(L, "group_id", pkt->group_id); setf_integer(L, "vlan_id", pkt->vlanid); pktgen_link_state(pinfo->pid, buff, sizeof(buff)); setf_string(L, "link_state", buff); lua_rawset(L, -3); lua_rawset(L, -3); /* Now set the table as an array with pid as the index. */ lua_rawset(L, -3); } /** * * pktgen_portInfo - Return the other port Info for a given ports. * * DESCRIPTION * Return the other port Info for a given ports. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_portInfo(lua_State *L) { portlist_t portlist; uint32_t n; switch (lua_gettop(L)) { default: return luaL_error(L, "portInfo, wrong number of arguments"); case 1: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); lua_newtable(L); n = 0; foreach_port(portlist, _do(port_info(L, pinfo); n++)); setf_integer(L, "n", n); return 1; } static void _pktgen_push_line(void *arg, const char **h) { lua_State *L = arg; int j; for (j = 0; h[j] != NULL; j++) { if (strcmp(h[j], CLI_HELP_PAUSE)) { lua_pushstring(L, h[j]); lua_concat(L, 2); } } } /** * * pktgen_help - Display the current help information. * * DESCRIPTION * Display the current help information. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_help(lua_State *L) { lua_concat(L, 0); cli_help_foreach(_pktgen_push_line, L); return 1; } /** * * pktgen_portCount - Return number of ports used * * DESCRIPTION * Return the number of ports used * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_portCount(lua_State *L) { lua_pushinteger(L, pktgen.nb_ports); return 1; } /** * * pktgen_totalPorts - Return the total number of ports * * DESCRIPTION * Return the total number of ports seen by DPDK * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_totalPorts(lua_State *L) { lua_pushinteger(L, pktgen.nb_ports); return 1; } /** * * pktgen_rnd - Setup random bit patterns * * DESCRIPTION * Setup the random bit pattern support. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_rnd(lua_State *L) { portlist_t portlist; char mask[33] = {0}; const char *msk; int i, mask_idx = 0; char curr_bit; switch (lua_gettop(L)) { default: return luaL_error(L, "rnd, wrong number of arguments"); case 4: break; } portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); msk = luaL_checkstring(L, 4); if (strcmp(msk, "off")) /* Filter invalid characters from provided mask. This way the user can * more easily enter long bitmasks, using for example '_' as a separator * every 8 bits. */ for (i = 0; (mask_idx < 32) && ((curr_bit = msk[i]) != '\0'); i++) if ((curr_bit == '0') || (curr_bit == '1') || (curr_bit == '.') || (curr_bit == 'X') || (curr_bit == 'x')) mask[mask_idx++] = curr_bit; foreach_port(portlist, enable_random(pinfo, pktgen_set_random_bitfield( pinfo->rnd_bitfields, luaL_checkinteger(L, 2), luaL_checkinteger(L, 3), mask) ? ENABLE_STATE : DISABLE_STATE)); return 0; } static void add_rnd_pattern(lua_State *L, port_info_t *pinfo) { uint32_t i, curr_bit, idx; char mask[36]; /* 4*8 bits, 3 delimiter spaces, \0 */ bf_spec_t *curr_spec; rnd_bits_t *rnd_bits = pinfo->rnd_bitfields; lua_pushinteger(L, pinfo->pid); /* Push the port number as the table index */ lua_newtable(L); /* Create the structure table for a packet */ for (idx = 0; idx < MAX_RND_BITFIELDS; idx++) { curr_spec = &rnd_bits->specs[idx]; memset(mask, 0, sizeof(mask)); memset(mask, ' ', sizeof(mask) - 1); /* Compose human readable bitmask representation */ for (i = 0; i < MAX_BITFIELD_SIZE; ++i) { curr_bit = (uint32_t)1 << (MAX_BITFIELD_SIZE - i - 1); /* + i >> 3 for space delimiter after every 8 bits. * Need to check rndMask before andMask: for random bits, the * andMask is also 0. */ mask[i + (i >> 3)] = ((ntohl(curr_spec->rndMask) & curr_bit) != 0) ? 'X' : ((ntohl(curr_spec->andMask) & curr_bit) == 0) ? '0' : ((ntohl(curr_spec->orMask) & curr_bit) != 0) ? '1' : '.'; } lua_pushinteger(L, idx); /* Push the RND bit index */ lua_newtable(L); /* Create the structure table for a packet */ setf_integer(L, "offset", curr_spec->offset); setf_string(L, "mask", mask); setf_string(L, "active", (rnd_bits->active_specs & (1 << idx)) ? "Yes" : "No"); lua_rawset(L, -3); } lua_rawset(L, -3); } /** * * pktgen_rnd_list - Return the random bit patterns in a table * * DESCRIPTION * Return a table of the random bit patterns. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_rnd_list(lua_State *L) { portlist_t portlist; int n; switch (lua_gettop(L)) { default: return luaL_error(L, "rnd_list, wrong number of arguments"); case 1: case 0: break; } if (lua_gettop(L) == 1) { portlist = pktgen_get_portlist(L, 1); if (portlist == INVALID_PORTLIST) return luaL_error(L, "invalid portlist"); } else portlist = -1; lua_newtable(L); n = 0; foreach_port(portlist, _do(add_rnd_pattern(L, pinfo); n++)); setf_integer(L, "n", n); return 1; } /** * * pktgen_run - Run a Lua or command script on the local disk or in a string. * * DESCRIPTION * Run a script file on the local disk, can be Lua or command lines. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_run(lua_State *L) { switch (lua_gettop(L)) { default: return luaL_error(L, "run( ['cmd'|'lua'], ), arguments wrong."); case 3: break; } /* A cmd executes a file on the disk and can be a lua Script of command line file. */ if (strcasecmp("cmd", luaL_checkstring(L, 1)) == 0) cli_execute_cmdfile(luaL_checkstring(L, 2)); else if (strcasecmp("lua", luaL_checkstring(L, 1)) == 0) /* Only a Lua script in memory. */ lua_execute_string(pktgen.ld, (char *)luaL_checkstring(L, 2)); else return luaL_error(L, "run( ['cmd'|'lua'], ), arguments wrong."); return 0; } /** * * pktgen_clock_gettime - Enable or Disable clock_gettime support. * * DESCRIPTION * Enable or disable clock_gettime support. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_clock_gettime(lua_State *L) { switch (lua_gettop(L)) { default: return luaL_error(L, "clock_gettime, wrong number of arguments"); case 1: break; } enable_clock_gettime(estate((char *)luaL_checkstring(L, 1))); pktgen_update_display(); return 0; } static const char *lua_help_info[] = { "Pktgen Lua functions and values using pktgen.XXX to access\n", "set - Set a number of options\n", "set_mac - Set the MAC address for a port\n", "set_ipaddr - Set the src and dst IP addresses\n", "mac_from_arp - Configure MAC from ARP packet\n", "set_proto - Set the prototype value\n", "set_type - Set the type value\n", "\n", "seq - Set the sequence data for a port\n", "seqTable - Set the sequence data for a port using tables\n", "ports_per_page - Set the number of ports per page\n", "icmp_echo - Enable/disable ICMP echo support\n", "send_arp - Send a ARP request or GRATUITOUS_ARP\n", "pcap - Load a PCAP file\n", "ping4 - Send a Ping IPv4 packet (ICMP echo)\n", #ifdef INCLUDE_PING6 "ping6 - Send a Ping IPv6 packet (ICMP echo)\n", #endif "start - Start a set of ports sending packets\n", "stop - Stop a set of ports sending packets\n", "screen - Turn off and on the screen updates\n", "prime - Send a small number of packets to prime the routes\n", "delay - Delay a given number of milliseconds\n", "pause - Delay for a given number of milliseconds and display message\n", "sleep - Delay a given number of seconds\n", "load - load and run a command file or Lua file.\n", "save - Save the configuration of Pktgen to a file.\n", "clear - Clear stats for the given ports\n", "clr - Clear all stats on all ports\n", "cls - Redraw the screen\n", "clock_gettime - Enable or Disable using clock_gettime() data\n", "\n", "update - Update the screen information\n", "reset - Reset the configuration to all ports\n", "vlan - Enable or disable VLAN header\n", "mpls - Enable or disable MPLS header\n", "qinq - Enable or disable Q-in-Q header\n", "gre - Enable or disable GRE with IPv4 payload\n", "gre_eth - Enable or disable GRE with Ethernet payload\n", "rnd_s_ip - Enable or disable randomizing the source IP address\n", "rnd_s_pt - Enable or disable randomizing the source port\n", "rnd - Enable or disable random bit patterns for a given portlist\n", "rnd_list - List of current random bit patterns\n", "\n", "Range commands\n", "dst_mac - Set the destination MAC address for a port\n", "src_mac - Set the src MAC address for a port\n", "src_ip - Set the source IP address and netmask value\n", "dst_ip - Set the destination IP address\n", "src_port - Set the IP source port number\n", "dst_port - Set the IP destination port number\n", "ttl - Set the Time to Live value\n", "hop_limits - Set the Hop Limit value\n", "vlan_id - Set the vlan id value\n", "mpls_entry - Set the MPLS entry\n", "qinqids - Set the Q-in-Q ID's\n", "gre_key - Set the range GRE key\n", "pkt_size - the packet size for a range port\n", "range - Enable or disable sending range data on a port.\n", "rxtap - Enable or disable RX Tap packet processing on a port\n", "txtap - Enable or disable TX Tap packet processing on a port\n", "latsampler_params - set latency sampler params\n", "latsampler - enable or disable latency sampler\n", "pattern - Set pattern type\n", "userPattern - Set the user pattern string\n", "jitter - Set the jitter threshold\n", "gtpu_teid - Set GTP-U TEID\n", "\n", "page - Select a page to display, seq, range, pcap and a number from 0-N\n", "port - select a different port number used for sequence and range pages.\n", "process - Enable or disable input packet processing on a port\n", "capture - Enable or disable capture packet processing on a port\n", "bonding - Enable or disable bonding support for sending zero packets\n", "blink - Blink an led on a port\n", "help - Return the help text\n", "Lua.help - Lua command help text\n", "\n", "isSending - Test to see if a port is sending packets\n", "linkState - Return the current link state of a port\n", "\n", "portStats - Return the current port stats (port_stats_t snapshot)\n", "portInfo - Return the current port configuration/info (no stats)\n", "portCount - Number of port being used\n", "totalPorts - Total number of ports seen by DPDK\n", "\n", "run - Load a Lua string or command file and execute it.\n", "continue - Display a message and wait for keyboard key and return\n", "input - Wait for a keyboard input line and return line.\n", "\n", "Pktgen.info.XXXX values below\n", "\n", "Lua_Version - Lua version string\n", "Lua_Release - Lua release string\n", "Lua_Copyright - Lua Copyright string\n", "Lua_Authors - Lua Authors string\n", "\n", "Pktgen_Version - Pktgen version string\n", "Pktgen_Copyright - Pktgen copyright string\n", "Pktgen_Authors - Pktgen Authors string\n", "DPDK_Version - DPDK version string\n", "DPDK_Copyright - DPDK copyright string", "\n", "startSeqIdx - Start of Sequence index value\n", "singlePktIdx - Single packet index value\n", "rangePktIdx - Start of the Range packet index\n", "pingPktIdx - Ping packet index value\n", "startExtraTxIdx- Start of Extra TX index value", "\n", "numSeqPkts - Max number of sequence packets\n", "numExtraTxPkts - Number of Extra TX packet buffers\n", "numTotalPkts - Number of total packet buffers\n", "\n", "minPktSize - Min packet size plus FCS\n", "maxPktSize - Max packet size plus FCS\n", "minVlanID - Min VLAN ID value\n", "maxVlanID - Max VLAN ID value\n", "vlanTagSize - VLAN Tag size\n", "minCos - Min 802.1p value\n", "maxCos - Max 802.1p value\n", "minTOS - Min TOS value\n", "maxTOS - Max TOS value\n", "mbufCacheSize - mbuf cache size value]n", "\n", "defaultPktBurst- Default Tx burst packet count\n", "defaultPktTxBurst- Default Tx burst packet count\n", "defaultPktRxBurst- Default Rx burst packet count\n", "defaultBuffSize- Default buffer size value\n", "maxMbufsPerPort- Max mbufs per port value\n", "maxPrimeCount - Max prime count\n", "portCount - Number of ports used\n", "totalPorts - Total ports found\n", NULL}; /** * * pktgen_lua_help - Display the current Lua help information. * * DESCRIPTION * Display the current Lua help information. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_lua_help(lua_State *L) { int i; lua_concat(L, 0); for (i = 1; lua_help_info[i] != NULL; i++) { lua_pushstring(L, lua_help_info[i]); lua_concat(L, 2); } return 1; } static const luaL_Reg pktgenlib_range[] = { /* Range commands */ {"dst_mac", range_dst_mac}, /* Set the destination MAC address for a port */ {"src_mac", range_src_mac}, /* Set the src MAC address for a port */ {"set_type", range_set_type}, /* Set the packet type to IPv4 or IPv6 */ {"src_ip", range_src_ip}, /* Set the source IP address and netmask value */ {"dst_ip", range_dst_ip}, /* Set the destination IP address */ {"ip_proto", range_ip_proto}, /* Set the IP Protocol type */ {"src_port", range_src_port}, /* Set the IP source port number */ {"dst_port", range_dst_port}, /* Set the IP destination port number */ {"ttl", range_ttl}, /* Set the IP TTL value */ {"hop_limits", range_hop_limits}, /* Set the IPv6 Hop Limits value */ {"vlan_id", range_vlan_id}, /* Set the vlan id value */ {"mpls_entry", range_mpls_entry}, /* Set the MPLS entry value */ {"qinqids", range_qinqids}, /* Set the Q-in-Q ID values */ {"gre_key", range_gre_key}, /* Set the GRE key */ {"pkt_size", range_pkt_size}, /* the packet size for a range port */ {"cos", range_cos}, /* Set the COS value */ {"tos", range_tos}, /* Set the COS value */ {"traffic_class", range_traffic_class}, /* Set the IPv6 Traffic Class */ {NULL, NULL}}; static const luaL_Reg pktgenlib[] = { {"quit", pktgen_exit}, {"set", pktgen_set}, /* Set a number of options */ {"start", pktgen_start}, /* Start a set of ports sending packets */ {"stop", pktgen_stop}, /* Stop a set of ports sending packets */ /* Set the single packet value on main screen */ {"set_mac", pktgen_set_mac}, /* Set the MAC address for a port */ {"set_ipaddr", pktgen_set_ip_addr}, /* Set the src and dst IP addresses */ {"mac_from_arp", pktgen_macFromArp}, /* Configure MAC from ARP packet */ {"set_proto", pktgen_prototype}, /* Set the prototype value */ {"set_type", pktgen_set_type}, /* Set the type value */ {"set_tcp_flags", pktgen_set_tcp_flags}, /* Set TCP flags for a given port */ {"ping4", pktgen_send_ping4}, /* Send a Ping IPv4 packet (ICMP echo) */ #ifdef INCLUDE_PING6 {"ping6", pktgen_send_ping6}, /* Send a Ping IPv6 packet (ICMP echo) */ #endif {"pcap", pktgen_pcap}, /* Load a PCAP file */ {"icmp_echo", pktgen_icmp}, /* Enable/disable ICMP echo support */ {"send_arp", pktgen_sendARP}, /* Send a ARP request or GRATUITOUS_ARP */ /* Setup sequence packets */ {"seq", pktgen_seq}, /* Set the sequence data for a port */ {"seqTable", pktgen_seqTable}, /* Set the sequence data for a port using tables */ {"seq_tcp_flags", pktgen_seq_tcp_flags}, /* Set tcp flags */ {"screen", pktgen_scrn}, /* Turn off and on the screen updates */ {"prime", pktgen_prime}, /* Send a small number of packets to prime the routes */ {"delay", pktgen_delay}, /* Delay a given number of milliseconds */ {"pause", pktgen_pause}, /* Delay for a given number of milliseconds and display message */ {"sleep", pktgen_sleep}, /* Delay a given number of seconds */ {"load", pktgen_load}, /* load and run a command file or Lua file. */ {"save", pktgen_config_save}, /* Save the configuration of Pktgen to a file. */ {"clear", pktgen_clear}, /* Clear stats for the given ports */ {"clr", pktgen_clear_all}, /* Clear all stats on all ports */ {"cls", pktgen_cls_screen}, /* Redraw the screen */ {"update", pktgen_update_screen}, /* Update the screen information */ {"reset", pktgen_reset_config}, /* Reset the configuration to all ports */ {"port_restart", pktgen_restart}, /* Reset ports */ {"portCount", pktgen_portCount}, /* Used port count value */ {"totalPorts", pktgen_totalPorts}, /* Total ports seen by DPDK */ {"vlan", single_vlan}, /* Enable or disable VLAN header */ {"vlanid", single_vlan_id}, /* Set the vlan ID for a given portlist */ {"cos", single_cos}, /* Set the prio for a given portlist */ {"tos", single_tos}, /* Set the tos for a given portlist */ {"vxlan", single_vxlan}, /* Enable or disable VxLAN */ {"vxlan_id", single_vxlan_id}, /* Set the VxLAN values */ {"mpls", pktgen_mpls}, /* Enable or disable MPLS header */ {"qinq", pktgen_qinq}, /* Enable or disable Q-in-Q header */ {"gre", pktgen_gre}, /* Enable or disable GRE with IPv4 payload */ {"gre_eth", pktgen_gre_eth}, /* Enable or disable GRE with Ethernet payload */ {"rnd_s_ip", pktgen_rnd_s_ip}, /* Enable or disable randomizing the source IP address */ {"rnd_s_pt", pktgen_rnd_s_pt}, /* Enable or disable randomizing the source port */ {"set_range", range}, /* Enable or disable sending range data on a port. */ {"ports_per_page", pktgen_ports_per_page}, /* Set the number of ports per page */ {"page", pktgen_page}, /* Select a page to display, seq, range, pcap and a number from 0-N */ {"port", pktgen_port}, /* select a different port number used for sequence and range pages. */ {"process", pktgen_process}, /* Enable or disable input packet processing on a port */ {"capture", pktgen_capture}, /* Enable or disable capture on a port */ #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) {"bonding", pktgen_bonding}, /* Enable or disable bonding on a port */ #endif {"blink", pktgen_blink}, /* Blink an led on a port */ {"help", pktgen_help}, /* Return the help text */ {"Lua.help", pktgen_lua_help}, /* Lua command help text */ {"isSending", pktgen_isSending}, /* Test to see if a port is sending packets */ {"linkState", pktgen_linkState}, /* Return the current link state of a port */ {"portStats", pktgen_portStats}, /* return the current port stats */ {"portInfo", pktgen_portInfo}, /* return the current port info */ {"run", pktgen_run}, /* Load a Lua string or command file and execute it. */ {"continue", pktgen_continue}, /* Display a message and wait for keyboard key and return */ {"input", pktgen_input}, /* Wait for a keyboard input line and return line. */ {"pattern", pktgen_pattern}, /* Set pattern type */ {"userPattern", pktgen_user_pattern}, /* Set the user pattern string */ {"latency", pktgen_latency}, /* Enable or disable latency testing */ {"jitter", pktgen_jitter}, /* Set the jitter threshold */ {"gtpu_teid", range_gtpu_teid}, /* set GTP-U TEID. */ {"rnd", pktgen_rnd}, /* Set up the rnd function on a portlist */ {"rnd_list", pktgen_rnd_list}, /* Return a table of rnd bit patterns per port */ {"latsampler_params", pktgen_latsampler_params}, /* set latency sampler params */ {"latsampler", pktgen_latsampler}, /* enable or disable latency sampler */ {"clock_gettime", pktgen_clock_gettime}, /* Enable/disable clock_gettime support */ {NULL, NULL}}; /* }====================================================== */ /** * * luaopen_pktgen - Initialize the Lua support for pktgen. * * DESCRIPTION * Initialize the Lua library for Pktgen. * * RETURNS: N/A * * SEE ALSO: */ LUALIB_API int luaopen_pktgen(lua_State *L) { luaL_newlib(L, pktgenlib); lua_pushstring(L, "info"); /* Push the table index name */ lua_newtable(L); /* Create the structure table for information */ setf_string(L, "Lua_Version", (char *)LUA_VERSION); setf_string(L, "Lua_Release", (char *)LUA_RELEASE); setf_string(L, "Lua_Copyright", (char *)LUA_COPYRIGHT); setf_string(L, "Lua_Authors", (char *)LUA_AUTHORS); setf_string(L, "Pktgen_Version", (char *)PKTGEN_VERSION); setf_string(L, "Pktgen_Copyright", (char *)copyright_msg()); setf_string(L, "Pktgen_Authors", (char *)"Keith Wiles @ Intel Corp"); setf_string(L, "DPDK_Version", (char *)rte_version()); /* Now set the table for the info values. */ lua_rawset(L, -3); lua_pushstring(L, "range"); /* Push the table index name */ lua_newtable(L); /* Create the structure table for information */ luaL_setfuncs(L, pktgenlib_range, 0); lua_rawset(L, -3); return 1; } /** * * pktgen_lua_openlib - Open the Pktgen Lua library. * * DESCRIPTION * Open and initialize the Pktgen Lua Library. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_lua_openlib(lua_State *L) { lua_gc(L, LUA_GCSTOP, 0); /* stop collector during initialization */ luaL_requiref(L, LUA_PKTGENLIBNAME, luaopen_pktgen, 1); lua_pop(L, 1); lua_gc(L, LUA_GCRESTART, 0); } ================================================ FILE: app/lpktgenlib.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2011 by Keith Wiles @ intel.com */ #ifndef LPKTGENLIB_H_ #define LPKTGENLIB_H_ /** * @file * * Lua bindings for the Pktgen application API. * * Registers the "pktgen" Lua library so that Lua scripts can control * port configuration, traffic generation, and statistics collection. * Declarations are conditionally compiled under LUA_ENABLED. */ #ifdef __cplusplus extern "C" { #endif #ifdef LUA_ENABLED #define lua_c #include #include #define LUA_PKTGENLIBNAME "pktgen" /**< Lua library name for the pktgen bindings */ #define PKTGEN_SHORTCUTS "Pktgen" /**< Lua shortcut table name */ /** * Open the Pktgen Lua library and register all pktgen API functions. * * Called automatically by the Lua runtime when the library is required. * * @param L * Lua state to register the library into. * @return * Number of values pushed onto the Lua stack (1 — the library table). */ LUALIB_API int luaopen_pktgen(lua_State *L); /** * Register all Pktgen Lua libraries (including shortcut aliases) into @p L. * * @param L * Lua state to register the libraries into. */ void pktgen_lua_openlib(lua_State *L); #endif #ifdef __cplusplus } #endif #endif /* LPKTGENLIB_H_ */ ================================================ FILE: app/meson.build ================================================ sources = files( 'cli-functions.c', 'l2p.c', 'pktgen-arp.c', 'pktgen-capture.c', 'pktgen-cmds.c', 'pktgen-cpu.c', 'pktgen-display.c', 'pktgen-dump.c', 'pktgen-ether.c', 'pktgen-gre.c', 'pktgen-gtpu.c', 'pktgen-ipv4.c', 'pktgen-ipv6.c', 'pktgen-latency.c', 'pktgen-log.c', 'pktgen-main.c', 'pktgen-pcap.c', 'pktgen-port-cfg.c', 'pktgen-random.c', 'pktgen-range.c', 'pktgen-seq.c', 'pktgen-stats.c', 'pktgen-sys.c', 'pktgen-tcp.c', 'pktgen-udp.c', 'pktgen-vlan.c', 'pktgen.c', 'xorshift64star.c', ) if get_option('enable_lua') sources += files('lpktgenlib.c') endif cflags = ['-D__PROJECT_VERSION="' + meson.project_version() + '"'] deps = [dpdk, common, utils, vec, plugin, cli, lua, hmap] if fgen_dep.found() deps += [fgen_dep] endif deps += [cc.find_library('rte_net_i40e', dirs: [dpdk_libs_path], required: false)] deps += [cc.find_library('rte_net_ixgbe', dirs: [dpdk_libs_path], required: false)] deps += [cc.find_library('rte_net_ice', dirs: [dpdk_libs_path], required: false)] deps += [cc.find_library('rte_bus_vdev', dirs: [dpdk_libs_path], required: false)] deps += [dependency('threads')] deps += [dependency('numa', required: true)] deps += [dependency('pcap', required: true)] deps += [cc.find_library('dl', required: false)] deps += [cc.find_library('m', required: false)] deps += [cc.find_library('bsd', required: true)] pktgen = executable('pktgen', sources, c_args: cflags, install: true, dependencies: [deps, lua_dep, dpdk_bond]) ================================================ FILE: app/pktgen-arp.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include #include "pktgen-arp.h" #include "pktgen.h" #include "pktgen-cmds.h" #include "pktgen-log.h" #include "pktgen-txbuff.h" void arp_pkt_dump(struct rte_mbuf *m) { struct rte_ether_hdr *eth; struct rte_arp_hdr *arp; char dst[64], src[64]; char sip[64], tip[64]; eth = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); arp = rte_pktmbuf_mtod_offset(m, struct rte_arp_hdr *, sizeof(struct rte_ether_hdr)); printf("\nARP Packet Dump\n"); rte_ether_format_addr(dst, sizeof(dst), ð->dst_addr); rte_ether_format_addr(src, sizeof(src), ð->src_addr); printf(" Ethernet Header DST: %s, SRC: %s, Type: %04x\n", dst, src, ntohs(eth->ether_type)); printf(" ARP Header Type: %04x, Proto: %04x, hlen: %d, plen: %d, opcode: %d\n", ntohs(arp->arp_hardware), ntohs(arp->arp_protocol), arp->arp_hlen, arp->arp_plen, ntohs(arp->arp_opcode)); rte_ether_format_addr(dst, sizeof(dst), &arp->arp_data.arp_sha); rte_ether_format_addr(src, sizeof(src), &arp->arp_data.arp_tha); inet_ntop(AF_INET, &arp->arp_data.arp_sip, sip, sizeof(sip)); inet_ntop(AF_INET, &arp->arp_data.arp_tip, tip, sizeof(tip)); printf(" ARP Data Sender: %s-%s, Target: %s-%s\n", dst, sip, src, tip); } /** * * pktgen_send_arp - Send an ARP request packet. * * DESCRIPTION * Create and send an ARP request packet. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_send_arp(uint32_t pid, uint32_t type, uint8_t seq_idx) { port_info_t *pinfo = l2p_get_port_pinfo(pid); l2p_port_t *port; pkt_seq_t *pkt; struct rte_mbuf *m; struct rte_ether_hdr *eth; struct rte_arp_hdr *arp; uint32_t addr; pkt = &pinfo->seq_pkt[seq_idx]; port = l2p_get_port(pid); { const uint16_t tx_qid = l2p_get_txqid(rte_lcore_id()); if (rte_mempool_get(port->sp_mp[tx_qid], (void **)&m)) { pktgen_log_warning("No packet buffers found"); return; } } eth = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); arp = (struct rte_arp_hdr *)ð[1]; /* src and dest addr */ memset(ð->dst_addr, 0xFF, 6); rte_ether_addr_copy(&pkt->eth_src_addr, ð->src_addr); eth->ether_type = htons(RTE_ETHER_TYPE_ARP); memset(arp, 0, sizeof(struct rte_arp_hdr)); rte_memcpy(&arp->arp_data.arp_sha, &pkt->eth_src_addr, 6); addr = htonl(pkt->ip_src_addr.addr.ipv4.s_addr); inetAddrCopy(&arp->arp_data.arp_sip, &addr); if (likely(type == GRATUITOUS_ARP)) { rte_memcpy(&arp->arp_data.arp_tha, &pkt->eth_src_addr, 6); addr = htonl(pkt->ip_src_addr.addr.ipv4.s_addr); inetAddrCopy(&arp->arp_data.arp_tip, &addr); } else { memset(&arp->arp_data.arp_tha, 0, 6); addr = htonl(pkt->ip_dst_addr.addr.ipv4.s_addr); inetAddrCopy(&arp->arp_data.arp_tip, &addr); } /* Fill in the rest of the ARP packet header */ arp->arp_hardware = htons(ETH_HW_TYPE); arp->arp_protocol = htons(RTE_ETHER_TYPE_IPV4); arp->arp_hlen = 6; arp->arp_plen = 4; arp->arp_opcode = htons(ARP_REQUEST); m->pkt_len = 60; m->data_len = 60; tx_send_packets(pinfo, l2p_get_txqid(rte_lcore_id()), &m, 1); } /** * * pktgen_process_arp - Handle a ARP request input packet and send a response. * * DESCRIPTION * Handle a ARP request input packet and send a response if required. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_process_arp(struct rte_mbuf *m, uint32_t pid, uint32_t qid, uint32_t vlan) { port_info_t *pinfo = l2p_get_port_pinfo(pid); pkt_seq_t *pkt = NULL; struct rte_ether_hdr *eth = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); struct rte_arp_hdr *arp = (struct rte_arp_hdr *)ð[1]; /* Adjust for a vlan header if present */ if (vlan) arp = (struct rte_arp_hdr *)((char *)arp + sizeof(struct rte_vlan_hdr)); /* Process all ARP requests if they are for us. */ if (arp->arp_opcode == htons(ARP_REQUEST)) { int idx; if (arp->arp_data.arp_tip == arp->arp_data.arp_sip) { /* GARP Packet */ idx = pktgen_find_matching_ipdst(pinfo, arp->arp_data.arp_sip); /* Found a matching packet, replace the dst address */ if (idx >= 0) { rte_ether_addr_copy(&arp->arp_data.arp_sha, &pkt->eth_dst_addr); pktgen_clear_display(); } return; } idx = pktgen_find_matching_ipsrc(pinfo, arp->arp_data.arp_tip); /* ARP request not for this interface. */ if (likely(idx >= 0)) { struct rte_mbuf *m1; l2p_port_t *port; pkt = &pinfo->seq_pkt[idx]; port = l2p_get_port(pid); m1 = rte_pktmbuf_copy(m, port->sp_mp[qid], 0, UINT32_MAX); if (unlikely(m1 == NULL)) return; eth = rte_pktmbuf_mtod(m1, struct rte_ether_hdr *); arp = (struct rte_arp_hdr *)ð[1]; /* Grab the source MAC address as the destination address for the port. */ if (unlikely(pktgen.flags & MAC_FROM_ARP_FLAG)) { rte_ether_addr_copy(&arp->arp_data.arp_sha, &pkt->eth_dst_addr); for (uint32_t i = 0; i < pinfo->seqCnt; i++) pktgen_packet_ctor(pinfo, i, -1); } /* Swap the two MAC addresses */ ethAddrSwap(&arp->arp_data.arp_sha, &arp->arp_data.arp_tha); /* Swap the two IP addresses */ inetAddrSwap(&arp->arp_data.arp_tip, &arp->arp_data.arp_sip); /* Set the packet to ARP reply */ arp->arp_opcode = htons(ARP_REPLY); /* Swap the MAC addresses */ ethAddrSwap(ð->dst_addr, ð->src_addr); /* Copy in the MAC address for the reply. */ rte_ether_addr_copy(&pkt->eth_src_addr, &arp->arp_data.arp_sha); rte_ether_addr_copy(&pkt->eth_src_addr, ð->src_addr); m1->ol_flags = 0; tx_send_packets(pinfo, qid, &m1, 1); return; } } else if (arp->arp_opcode == htons(ARP_REPLY)) { int idx; idx = pktgen_find_matching_ipsrc(pinfo, arp->arp_data.arp_tip); /* ARP request not for this interface. */ if (likely(idx >= 0)) { pkt = &pinfo->seq_pkt[idx]; /* Grab the real destination MAC address */ if (pkt->ip_dst_addr.addr.ipv4.s_addr == ntohl(arp->arp_data.arp_sip)) rte_ether_addr_copy(&arp->arp_data.arp_sha, &pkt->eth_dst_addr); pktgen.flags |= PRINT_LABELS_FLAG; } } } ================================================ FILE: app/pktgen-arp.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_ARP_H_ #define _PKTGEN_ARP_H_ /** * @file * * ARP packet transmission, processing, and debug dump for Pktgen. */ #include #include #ifdef __cplusplus extern "C" { #endif /** * Transmit an ARP request or reply on port @p pid. * * @param pid * Port index to send the ARP packet on. * @param type * ARP operation type (e.g. RTE_ARP_OP_REQUEST or RTE_ARP_OP_REPLY). * @param seq_idx * Packet sequence slot index used as the ARP template. */ void pktgen_send_arp(uint32_t pid, uint32_t type, uint8_t seq_idx); /** * Process a received ARP packet and send a reply if warranted. * * @param m * Received mbuf containing the ARP packet. * @param pid * Port index on which the packet arrived. * @param qid * Queue index on which the packet arrived. * @param vlan * VLAN ID extracted from the outer header (0 if untagged). */ void pktgen_process_arp(struct rte_mbuf *m, uint32_t pid, uint32_t qid, uint32_t vlan); /** * Hex-dump the ARP fields of mbuf @p m to stdout for debugging. * * @param m * Mbuf containing the ARP packet to dump. */ void arp_pkt_dump(struct rte_mbuf *m); #ifdef __cplusplus } #endif #endif /* _PKTGEN_ARP_H_ */ ================================================ FILE: app/pktgen-capture.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include "pktgen-capture.h" #include #include #include #include #include #ifdef LUA_ENABLED #include #endif #include "pktgen-cmds.h" #include "pktgen-log.h" #include "pktgen-display.h" #define CAPTURE_BUFF_SIZE (4 * (1024 * 1024)) /** * * pktgen_packet_capture_init - Initialize memory and data structures for packet * capture. * * DESCRIPTION * Initialization of memory zones and data structures for packet capture * storage. * * PARAMETERS: * capture: capture_t struct that will keep a pointer to the allocated memzone * socket_id: Socket where the memzone will be reserved * * RETURNS: N/A * * SEE ALSO: */ void pktgen_packet_capture_init(uint16_t sid) { char memzone_name[RTE_MEMZONE_NAMESIZE]; capture_t *cap = &pktgen.capture[sid]; snprintf(memzone_name, sizeof(memzone_name), "Capture_MZ_%d", sid); cap->mz = rte_memzone_reserve(memzone_name, CAPTURE_BUFF_SIZE, sid, RTE_MEMZONE_2MB | RTE_MEMZONE_SIZE_HINT_ONLY); if (cap->mz == NULL) printf("*** Unable to create capture memzone for socket ID %d\n", sid); else { cap->port = RTE_MAX_ETHPORTS; cap->used = 0; cap->nb_pkts = 0; cap->tail = (cap_hdr_t *)cap->mz->addr; cap->end = (cap_hdr_t *)((char *)cap->mz->addr + (cap->mz->len - sizeof(cap_hdr_t))); memset(cap->tail, 0, sizeof(cap_hdr_t)); memset(cap->end, 0, sizeof(cap_hdr_t)); } } /** * * pktgen_set_capture - Enable or disable packet capturing * * DESCRIPTION * Set up packet capturing for the given ports and make sure only 1 port per * socket is in capture mode. * * PARAMETERS: * info: port to capture from * onOff: enable or disable capturing? * * RETURNS: N/A * * SEE ALSO: */ void pktgen_set_capture(port_info_t *pinfo, uint32_t onOff) { capture_t *cap; if (onOff == ENABLE_STATE) { /* Enabling an already enabled port is a no-op */ if (rte_atomic64_read(&pinfo->port_flags) & CAPTURE_PKTS) return; /* Find an lcore that can capture packets for the requested port */ uint16_t lid; if ((lid = l2p_get_lcore_by_pid(pinfo->pid)) >= RTE_MAX_LCORE) { pktgen_log_warning("Port %d has no rx lcore: capture is not possible", pinfo->pid); return; } /* Get socket of the selected lcore and check if capturing is possible */ uint16_t sid = coreinfo_get(lid)->socket_id; cap = &pktgen.capture[sid]; if (cap->mz == NULL) { pktgen_log_warning( "No memory allocated for capturing on socket %d for port %d, are hugepages" " allocated on this socket?", sid, pinfo->pid); return; } /* Everything checks out: enable packet capture */ cap->used = 0; cap->port = pinfo->pid; cap->nb_pkts = 0; cap->tail = cap->mz->addr; /* Write end-of-data sentinel to start of capture memory. This */ /* effectively clears previously captured data. */ memset(cap->tail, 0, sizeof(cap_hdr_t)); memset(cap->end, 0, sizeof(cap_hdr_t)); pktgen_set_port_flags(pinfo, CAPTURE_PKTS); pktgen_log_info("Capturing on port %d, lcore %d, socket %d; buffer size: %.2f MB ", pinfo->pid, lid, sid, (double)cap->mz->len / (1024 * 1024)); pktgen_log_info(" (~%.2f seconds for 64 byte packets at line rate)", (double)cap->mz->len / (66 /* 64 bytes payload + 2 bytes for payload size */ * ((double)pinfo->link.link_speed * Million / 8) /* Xbit -> Xbyte */ / 84) /* 64 bytes payload + 20 byte etherrnet * frame overhead: 84 bytes per packet */ ); } else { if (!(rte_atomic64_read(&pinfo->port_flags) & CAPTURE_PKTS)) return; int sid; for (sid = 0; sid < RTE_MAX_NUMA_NODES; ++sid) { cap = &pktgen.capture[sid]; if (cap->mz && (cap->port == pinfo->pid)) break; } /* This should never happen. */ if (sid == RTE_MAX_NUMA_NODES) { pktgen_log_error("Could not find socket for port %d", pinfo->pid); return; } /* If there is previously captured data in the buffer, write it to disk. */ if (cap->nb_pkts > 0) { pcap_t *pcap; pcap_dumper_t *pcap_dumper; struct pcap_pkthdr pcap_hdr; cap_hdr_t *hdr; time_t t; char filename[64]; char str_time[64]; size_t mem_dumped = 0; unsigned int pct = 0; char status[1024]; printf("Used %'lu, count %'u\n", cap->used, cap->nb_pkts); sprintf(status, "\r Dumping ~%.2fMB of captured data to disk: 0%%", (double)cap->used / (1024 * 1024)); scrn_printf(0, 0, "\n%s", status); pcap = pcap_open_dead(DLT_EN10MB, 65535); t = time(NULL); strftime(str_time, sizeof(str_time), "%Y%m%d-%H%M%S", localtime(&t)); snprintf(filename, sizeof(filename), "pktgen-%s-%d.pcap", str_time, cap->port); pcap_dumper = pcap_dump_open(pcap, filename); hdr = cap->mz->addr; for (uint32_t i = 0; i < cap->nb_pkts; i++) { if (hdr->pkt_len == 0) { printf("\n>>> Hit packet length zero at %'u of %'u\n", i, cap->nb_pkts); break; } struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); pcap_hdr.len = hdr->pkt_len; pcap_hdr.caplen = hdr->data_len; pcap_hdr.ts.tv_sec = ts.tv_sec; pcap_hdr.ts.tv_usec = ts.tv_nsec / 1000; pcap_dump((u_char *)pcap_dumper, &pcap_hdr, (const u_char *)hdr->pkt); hdr = (cap_hdr_t *)(hdr->pkt + hdr->data_len); mem_dumped = hdr->pkt - (unsigned char *)cap->mz->addr; /* The amount of data to dump to disk, is potentially very large */ /* (a few gigabytes), so print a percentage counter. */ if (pct < ((mem_dumped * 100) / cap->used)) { pct = (mem_dumped * 100) / cap->used; if (pct % 10 == 0) strncatf(status, "%d%%", pct); else if (pct % 2 == 0) strncatf(status, "."); scrn_printf(0, 0, "%s", status); } } scrn_printf(0, 0, "\r"); scrn_printf(0, 0, "\n"); /* Clean of the screen a bit */ pcap_dump_close(pcap_dumper); pcap_close(pcap); chmod(filename, 0666); } cap->used = 0; cap->tail = (cap_hdr_t *)cap->mz->addr; cap->port = RTE_MAX_ETHPORTS; pktgen_clr_port_flags(pinfo, CAPTURE_PKTS); } } /** * * pktgen_packet_capture_bulk - Capture packets to memory. * * DESCRIPTION * Capture packet contents to memory, so they can be written to disk later. * * A captured packet is stored as follows: * - uint16_t: non-truncated packet length * - uint16_t: size of actual packet contents that are stored * - unsigned char[]: packet contents (number of bytes stored equals previous * uint16_t) * * RETURNS: N/A * * SEE ALSO: */ void pktgen_packet_capture_bulk(struct rte_mbuf **pkts, uint32_t nb_dump, capture_t *cap) { uint32_t dlen, plen, i; struct rte_mbuf *pkt; /* Don't capture if buffer is full */ if (cap->tail >= cap->end) return; for (i = 0; i < nb_dump; i++) { pkt = pkts[i]; /* If the packet is segmented by DPDK, only the contents of the first * segment are captured. Capturing all segments uses too much CPU * cycles, which causes packets to be dropped. * Hence, data_len is used instead of pkt_len. */ dlen = pkt->data_len; plen = pkt->pkt_len; if (plen == 0) plen = 128; if (dlen == 0) dlen = 128; /* If packet to capture is larger than available buffer size, stop capturing. * The packet data is prepended by the untruncated packet length and * the amount of captured data (which can be less than the packet size * if DPDK has stored the packet contents in segmented mbufs). */ if ((cap_hdr_t *)(cap->tail->pkt + dlen) > cap->end) break; /* Write untruncated data length and size of the actually captured data. */ cap->tail->pkt_len = plen; cap->tail->data_len = dlen; cap->tail->tstamp = pktgen_get_time(); rte_memcpy(cap->tail->pkt, rte_pktmbuf_mtod(pkt, uint8_t *), dlen); cap->tail = (cap_hdr_t *)(cap->tail->pkt + dlen); cap->nb_pkts++; } /* Write end-of-data sentinel */ cap->tail->pkt_len = 0; cap->used = (unsigned char *)cap->tail - (unsigned char *)cap->mz->addr; } ================================================ FILE: app/pktgen-capture.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_CAPTURE_H_ #define _PKTGEN_CAPTURE_H_ /** * @file * * In-memory packet capture for Pktgen. * * Provides structures and functions for capturing received packets into a * DPDK memzone with per-packet timestamps and length metadata. */ #include #include #include #include #include "pktgen-port-cfg.h" #ifdef __cplusplus extern "C" { #endif /** Per-packet capture record header, immediately followed by packet data. */ typedef struct cap_hdr_s { uint64_t tstamp; /**< Capture timestamp in TSC cycles */ uint16_t pkt_len; /**< Original packet length in bytes */ uint16_t data_len; /**< Number of packet bytes stored after this header */ uint8_t pkt[0]; /**< Inline packet data (flexible array) */ } cap_hdr_t; /** Capture buffer state for one NUMA socket. */ typedef struct capture_s { const struct rte_memzone *mz; /**< Memory region to store packets */ cap_hdr_t *tail; /**< Current tail pointer in the pkt buffer */ cap_hdr_t *end; /**< Points to just before the end[-1] of the buffer */ size_t used; /**< Memory used by captured packets */ uint32_t nb_pkts; /**< Number of packets in capture pool */ uint16_t port; /**< port for this memzone */ } capture_t; /** * Allocate and initialise the capture memzone for a NUMA socket. * * @param socket_id * NUMA socket ID on which to allocate the capture buffer. */ void pktgen_packet_capture_init(uint16_t socket_id); /** * Enable or disable packet capture on a port. * * @param pinfo Per-port state. * @param onOff ENABLE_STATE to start capturing, DISABLE_STATE to stop. */ void pktgen_set_capture(port_info_t *pinfo, uint32_t onOff); /** * Capture a burst of received packets into the per-socket capture buffer. * * @param pkts Array of mbufs to capture. * @param nb_dump Number of mbufs in @p pkts. * @param capture Capture state for the NUMA socket. */ void pktgen_packet_capture_bulk(struct rte_mbuf **pkts, uint32_t nb_dump, capture_t *capture); #ifdef __cplusplus } #endif #endif /* _PKTGEN_CAPTURE_H_ */ ================================================ FILE: app/pktgen-cmds.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include #include "pktgen.h" #include "pktgen-cmds.h" #include "pktgen-display.h" #include #include #include #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) #include #include #endif static char hash_line[] = "#######################################################################"; #define _cp(s) (strcmp(str, s) == 0) static char * convert_bitfield(bf_spec_t *bf) { uint32_t mask; char *p; uint32_t i; static char rnd_bitmask[33]; memset(rnd_bitmask, 0, sizeof(rnd_bitmask)); memset(rnd_bitmask, '.', sizeof(rnd_bitmask) - 1); p = rnd_bitmask; for (i = 0; i < MAX_BITFIELD_SIZE; i++) { mask = (uint32_t)(1 << (MAX_BITFIELD_SIZE - i - 1)); /* Need to check rndMask before andMask: for random bits, the * andMask is also 0. */ p[i] = ((ntohl(bf->rndMask) & mask) != 0) ? 'X' : ((ntohl(bf->andMask) & mask) == 0) ? '0' : ((ntohl(bf->orMask) & mask) != 0) ? '1' : '.'; } return rnd_bitmask; } /** * pktgen_script_save - Save a configuration as a startup script * * DESCRIPTION * Save a configuration as a startup script * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_script_save(char *path) { port_info_t *pinfo; pkt_seq_t *pkt; range_info_t *range; latency_t *lat; uint32_t flags; char buff[64]; FILE *fd; int i, j; uint64_t lcore; struct rte_ether_addr eaddr; char buf[128]; fd = fopen(path, "w"); if (fd == NULL) return -1; for (i = 0, lcore = 0; i < RTE_MAX_LCORE; i++) if (rte_lcore_is_enabled(i)) lcore |= (1 << i); fprintf(fd, "#\n# %s\n", pktgen_version()); fprintf(fd, "# %s, %s %s\n\n", copyright_msg(), powered_by(), rte_version()); fprintf(fd, "# Command line arguments: (DPDK args are defaults)\n"); fprintf(fd, "# %s -c %" PRIx64 " -n 3 -m 512 --proc-type %s -- ", pktgen.argv[0], lcore, (rte_eal_process_type() == RTE_PROC_PRIMARY) ? "primary" : "secondary"); for (i = 1; i < pktgen.argc; i++) fprintf(fd, "%s ", pktgen.argv[i]); fprintf(fd, "\n\n%s\n", hash_line); fprintf(fd, "# Pktgen Configuration script information:\n"); fprintf(fd, "# Flags %08x\n", pktgen.flags); fprintf(fd, "# Number of ports: %d\n", pktgen.nb_ports); fprintf(fd, "# Number ports per page: %d\n", pktgen.nb_ports_per_page); fprintf(fd, "# Number descriptors: RX %d TX: %d\n", pktgen.nb_rxd, pktgen.nb_txd); fprintf(fd, "# Promiscuous mode is %s\n\n", (pktgen.flags & PROMISCUOUS_ON_FLAG) ? "Enabled" : "Disabled"); fprintf(fd, "\n# Global configuration:\n"); uint16_t rows, cols; pktgen_display_get_geometry(&rows, &cols); fprintf(fd, "# geometry %dx%d\n", cols, rows); fprintf(fd, "%s mac_from_arp\n\n", (pktgen.flags & MAC_FROM_ARP_FLAG) ? "enable" : "disable"); for (i = 0; i < pktgen.nb_ports; i++) { pinfo = l2p_get_port_pinfo(i); pkt = &pinfo->seq_pkt[SINGLE_PKT]; range = &pinfo->range; if (pinfo->tx_burst == 0) continue; lat = &pinfo->latency; fprintf(fd, "######################### Port %2d ##################################\n", i); if (rte_atomic64_read(&pinfo->transmit_count) == 0) strcpy(buff, "Forever"); else snprintf(buff, sizeof(buff), "%" PRIu64, rte_atomic64_read(&pinfo->transmit_count)); fprintf(fd, "#\n"); flags = rte_atomic64_read(&pinfo->port_flags); fprintf(fd, "# Port: %2d, Burst (Rx/Tx):%3d/%3d, Rate:%g%%, Flags:%16x, TX Count:%s\n", pinfo->pid, pinfo->rx_burst, pinfo->tx_burst, pinfo->tx_rate, flags, buff); fprintf(fd, "# Sequence count:%d, Prime:%d VLAN ID:%04x, ", pinfo->seqCnt, pinfo->prime_cnt, pinfo->vlanid); pktgen_link_state(pinfo->pid, buff, sizeof(buff)); fprintf(fd, "Link: %s\n", buff); fprintf(fd, "#\n# Set up the primary port information:\n"); fprintf(fd, "set %d count %" PRIu64 "\n", pinfo->pid, rte_atomic64_read(&pinfo->transmit_count)); fprintf(fd, "set %d size %d\n", pinfo->pid, pkt->pkt_size + RTE_ETHER_CRC_LEN); fprintf(fd, "set %d rate %g\n", pinfo->pid, pinfo->tx_rate); fprintf(fd, "set %d rxburst %d\n", pinfo->pid, pinfo->rx_burst); fprintf(fd, "set %d txburst %d\n", pinfo->pid, pinfo->tx_burst); fprintf(fd, "set %d sport %d\n", pinfo->pid, pkt->sport); fprintf(fd, "set %d dport %d\n", pinfo->pid, pkt->dport); fprintf(fd, "set %d prime %d\n", pinfo->pid, pinfo->prime_cnt); fprintf(fd, "set %d type %s\n", i, (pkt->ethType == RTE_ETHER_TYPE_IPV4) ? "ipv4" : (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? "ipv6" : (pkt->ethType == RTE_ETHER_TYPE_VLAN) ? "vlan" : (pkt->ethType == RTE_ETHER_TYPE_ARP) ? "arp" : "unknown"); fprintf(fd, "set %d proto %s\n", i, (pkt->ipProto == PG_IPPROTO_TCP) ? "tcp" : (pkt->ipProto == PG_IPPROTO_ICMP) ? "icmp" : "udp"); fprintf(fd, "set %d dst ip %s\n", i, (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? inet_ntop6(buff, sizeof(buff), pkt->ip_dst_addr.addr.ipv6.a, PG_PREFIXMAX) : inet_ntop4(buff, sizeof(buff), ntohl(pkt->ip_dst_addr.addr.ipv4.s_addr), 0xFFFFFFFF)); fprintf(fd, "set %d src ip %s\n", i, (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? inet_ntop6(buff, sizeof(buff), pkt->ip_src_addr.addr.ipv6.a, pkt->ip_src_addr.prefixlen) : inet_ntop4(buff, sizeof(buff), ntohl(pkt->ip_src_addr.addr.ipv4.s_addr), pkt->ip_mask)); tcp_str_from_flags(pkt->tcp_flags, buf, sizeof(buf)); fprintf(fd, "set %d tcp flags %s\n", i, buf); fprintf(fd, "set %d tcp seq %u\n", i, pkt->tcp_seq); fprintf(fd, "set %d tcp ack %u\n", i, pkt->tcp_ack); fprintf(fd, "set %d dst mac %s\n", pinfo->pid, inet_mtoa(buff, sizeof(buff), &pkt->eth_dst_addr)); fprintf(fd, "set %d src mac %s\n", pinfo->pid, inet_mtoa(buff, sizeof(buff), &pkt->eth_src_addr)); fprintf(fd, "set %d vlan %d\n\n", i, pkt->vlanid); fprintf(fd, "set %d pattern %s\n", i, (pinfo->fill_pattern_type == ABC_FILL_PATTERN) ? "abc" : (pinfo->fill_pattern_type == NO_FILL_PATTERN) ? "none" : (pinfo->fill_pattern_type == ZERO_FILL_PATTERN) ? "zero" : "user"); if ((pinfo->fill_pattern_type == USER_FILL_PATTERN) && strlen(pinfo->user_pattern)) { char buff[64]; memset(buff, 0, sizeof(buff)); snprintf(buff, sizeof(buff), "%s", pinfo->user_pattern); fprintf(fd, "set %d user pattern %s\n", i, buff); } fprintf(fd, "\n"); fprintf(fd, "set %d jitter %" PRIu64 "\n", i, lat->jitter_threshold_us); fprintf(fd, "%sable %d mpls\n", (flags & SEND_MPLS_LABEL) ? "en" : "dis", i); sprintf(buff, "0x%x", pkt->mpls_entry); fprintf(fd, "range %d mpls entry %s\n", i, buff); fprintf(fd, "%sable %d qinq\n", (flags & SEND_Q_IN_Q_IDS) ? "en" : "dis", i); fprintf(fd, "set %d qinqids %d %d\n", i, pkt->qinq_outerid, pkt->qinq_innerid); fprintf(fd, "%sable %d gre\n", (flags & SEND_GRE_IPv4_HEADER) ? "en" : "dis", i); fprintf(fd, "%sable %d gre_eth\n", (flags & SEND_GRE_ETHER_HEADER) ? "en" : "dis", i); fprintf(fd, "%sable %d vxlan\n", (flags & SEND_VXLAN_PACKETS) ? "en" : "dis", i); fprintf(fd, "set %d vxlan 0x%x %d %d\n", i, pkt->vni_flags, pkt->group_id, pkt->vxlan_id); fprintf(fd, "#\n# Port flag values:\n"); fprintf(fd, "%sable %d icmp\n", (flags & ICMP_ECHO_ENABLE_FLAG) ? "en" : "dis", i); fprintf(fd, "%sable %d pcap\n", (flags & SEND_PCAP_PKTS) ? "en" : "dis", i); fprintf(fd, "%sable %d range\n", (flags & SEND_RANGE_PKTS) ? "en" : "dis", i); fprintf(fd, "%sable %d latency\n", (flags & SEND_LATENCY_PKTS) ? "en" : "dis", i); fprintf(fd, "%sable %d process\n", (flags & PROCESS_INPUT_PKTS) ? "en" : "dis", i); fprintf(fd, "%sable %d capture\n", (flags & CAPTURE_PKTS) ? "en" : "dis", i); fprintf(fd, "%sable %d vlan\n", (flags & SEND_VLAN_ID) ? "en" : "dis", i); fprintf(fd, "#\n# Range packet information:\n"); fprintf(fd, "range %d src mac start %s\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac, &eaddr))); fprintf(fd, "range %d src mac min %s\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac_min, &eaddr))); fprintf(fd, "range %d src mac max %s\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac_max, &eaddr))); fprintf(fd, "range %d src mac inc %s\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac_inc, &eaddr))); fprintf(fd, "\n"); fprintf(fd, "range %d dst mac start %s\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac, &eaddr))); fprintf(fd, "range %d dst mac min %s\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac_min, &eaddr))); fprintf(fd, "range %d dst mac max %s\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac_max, &eaddr))); fprintf(fd, "range %d dst mac inc %s\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac_inc, &eaddr))); fprintf(fd, "\n"); fprintf(fd, "range %d src ip start %s\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->src_ip), 0xFFFFFFFF)); fprintf(fd, "range %d src ip min %s\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->src_ip_min), 0xFFFFFFFF)); fprintf(fd, "range %d src ip max %s\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->src_ip_max), 0xFFFFFFFF)); fprintf(fd, "range %d src ip inc %s\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->src_ip_inc), 0xFFFFFFFF)); fprintf(fd, "\n"); fprintf(fd, "range %d dst ip start %s\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->dst_ip), 0xFFFFFFFF)); fprintf(fd, "range %d dst ip min %s\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->dst_ip_min), 0xFFFFFFFF)); fprintf(fd, "range %d dst ip max %s\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->dst_ip_max), 0xFFFFFFFF)); fprintf(fd, "range %d dst ip inc %s\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->dst_ip_inc), 0xFFFFFFFF)); fprintf(fd, "\n"); fprintf(fd, "range %d proto %s\n", i, (range->ip_proto == PG_IPPROTO_UDP) ? "udp" : (range->ip_proto == PG_IPPROTO_ICMP) ? "icmp" : "tcp"); fprintf(fd, "\n"); fprintf(fd, "range %d src port start %d\n", i, range->src_port); fprintf(fd, "range %d src port min %d\n", i, range->src_port_min); fprintf(fd, "range %d src port max %d\n", i, range->src_port_max); fprintf(fd, "range %d src port inc %d\n", i, range->src_port_inc); fprintf(fd, "\n"); fprintf(fd, "range %d dst port start %d\n", i, range->dst_port); fprintf(fd, "range %d dst port min %d\n", i, range->dst_port_min); fprintf(fd, "range %d dst port max %d\n", i, range->dst_port_max); fprintf(fd, "range %d dst port inc %d\n", i, range->dst_port_inc); fprintf(fd, "\n"); tcp_str_from_flags(range->tcp_flags, buf, sizeof(buf)); fprintf(fd, "range %d tcp flags %s\n", i, buf); fprintf(fd, "\n"); fprintf(fd, "range %d tcp seq start %u\n", i, range->tcp_seq); fprintf(fd, "range %d tcp seq min %u\n", i, range->tcp_seq_min); fprintf(fd, "range %d tcp seq max %u\n", i, range->tcp_seq_max); fprintf(fd, "range %d tcp seq inc %u\n", i, range->tcp_seq_inc); fprintf(fd, "\n"); fprintf(fd, "range %d tcp ack start %u\n", i, range->tcp_ack); fprintf(fd, "range %d tcp ack min %u\n", i, range->tcp_ack_min); fprintf(fd, "range %d tcp ack max %u\n", i, range->tcp_ack_max); fprintf(fd, "range %d tcp ack inc %u\n", i, range->tcp_ack_inc); fprintf(fd, "\n"); fprintf(fd, "range %d ttl start %d\n", i, range->ttl); fprintf(fd, "range %d ttl min %d\n", i, range->ttl_min); fprintf(fd, "range %d ttl max %d\n", i, range->ttl_max); fprintf(fd, "range %d ttl inc %d\n", i, range->ttl_inc); fprintf(fd, "\n"); fprintf(fd, "range %d vlan start %d\n", i, range->vlan_id); fprintf(fd, "range %d vlan min %d\n", i, range->vlan_id_min); fprintf(fd, "range %d vlan max %d\n", i, range->vlan_id_max); fprintf(fd, "range %d vlan inc %d\n", i, range->vlan_id_inc); fprintf(fd, "\n"); fprintf(fd, "range %d cos start %d\n", i, range->cos); fprintf(fd, "range %d cos min %d\n", i, range->cos_min); fprintf(fd, "range %d cos max %d\n", i, range->cos_max); fprintf(fd, "range %d cos inc %d\n", i, range->cos_inc); fprintf(fd, "\n"); fprintf(fd, "range %d tos start %d\n", i, range->tos); fprintf(fd, "range %d tos min %d\n", i, range->tos_min); fprintf(fd, "range %d tos max %d\n", i, range->tos_max); fprintf(fd, "range %d tos inc %d\n", i, range->tos_inc); fprintf(fd, "range %d gre key %d\n", i, pkt->gre_key); fprintf(fd, "\n"); fprintf(fd, "range %d size start %d\n", i, range->pkt_size + RTE_ETHER_CRC_LEN); fprintf(fd, "range %d size min %d\n", i, range->pkt_size_min + RTE_ETHER_CRC_LEN); fprintf(fd, "range %d size max %d\n", i, range->pkt_size_max + RTE_ETHER_CRC_LEN); fprintf(fd, "range %d size inc %d\n\n", i, range->pkt_size_inc); fprintf(fd, "#\n# Set up the sequence data for the port.\n"); fprintf(fd, "set %d seq_cnt %d\n", pinfo->pid, pinfo->seqCnt); for (j = 0; j < pinfo->seqCnt; j++) { pkt = &pinfo->seq_pkt[j]; fprintf(fd, "seq %d %d %s ", j, i, inet_mtoa(buff, sizeof(buff), &pkt->eth_dst_addr)); fprintf(fd, "%s ", inet_mtoa(buff, sizeof(buff), &pkt->eth_src_addr)); fprintf(fd, "%s ", (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? inet_ntop6(buff, sizeof(buff), pkt->ip_dst_addr.addr.ipv6.a, PG_PREFIXMAX) : inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_dst_addr.addr.ipv4.s_addr), 0xFFFFFFFF)); fprintf(fd, "%s ", (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? inet_ntop6(buff, sizeof(buff), pkt->ip_src_addr.addr.ipv6.a, pkt->ip_src_addr.prefixlen) : inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_src_addr.addr.ipv4.s_addr), pkt->ip_mask)); fprintf(fd, "%d %d %s %s %d %d %d\n", pkt->sport, pkt->dport, (pkt->ethType == RTE_ETHER_TYPE_IPV4) ? "ipv4" : (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? "ipv6" : (pkt->ethType == RTE_ETHER_TYPE_VLAN) ? "vlan" : "Other", (pkt->ipProto == PG_IPPROTO_TCP) ? "tcp" : (pkt->ipProto == PG_IPPROTO_ICMP) ? "icmp" : "udp", pkt->vlanid, pkt->pkt_size + RTE_ETHER_CRC_LEN, pkt->gtpu_teid); } pcap_info_t *pcap = l2p_get_pcap(pinfo->pid); if (pcap) { fprintf(fd, "#\n# PCAP port %d\n", i); fprintf(fd, "# Packet count: %d, max size: %d\n", pcap->pkt_count, pcap->max_pkt_size); fprintf(fd, "# Filename : %s\n", pcap->filename); } fprintf(fd, "\n"); if (pinfo->rnd_bitfields && pinfo->rnd_bitfields->active_specs) { uint32_t active = pinfo->rnd_bitfields->active_specs; bf_spec_t *bf; fprintf(fd, "\n-- Rnd bitfields\n"); for (j = 0; j < MAX_RND_BITFIELDS; j++) { if ((active & (1 << j)) == 0) continue; bf = &pinfo->rnd_bitfields->specs[j]; fprintf(fd, "set %d rnd %d %d %s\n", i, j, bf->offset, convert_bitfield(bf)); } fprintf(fd, "\n"); } } fprintf(fd, "################################ Done #################################\n"); fchmod(fileno(fd), 0666); fclose(fd); return 0; } /** * * pktgen_lua_save - Save a configuration as a Lua script * * DESCRIPTION * Save a configuration as a Lua script * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_lua_save(char *path) { port_info_t *pinfo; pkt_seq_t *pkt; range_info_t *range; latency_t *lat; uint32_t flags; char buff[64]; FILE *fd; int i, j; uint64_t lcore; struct rte_ether_addr eaddr; fd = fopen(path, "w"); if (fd == NULL) return -1; for (i = 0, lcore = 0; i < RTE_MAX_LCORE; i++) if (rte_lcore_is_enabled(i)) lcore |= (1 << i); fprintf(fd, "--\n-- %s\n", pktgen_version()); fprintf(fd, "-- %s, %s %s\n\n", copyright_msg(), powered_by(), rte_version()); fprintf(fd, "package.path = package.path ..\";?.lua;test/?.lua;app/?.lua;\"\n"); fprintf(fd, "require \"Pktgen\"\n\n"); fprintf(fd, "-- Command line arguments: (DPDK args are defaults)\n"); fprintf(fd, "-- %s -c %" PRIx64 " -n 3 -m 512 --proc-type %s -- ", pktgen.argv[0], lcore, (rte_eal_process_type() == RTE_PROC_PRIMARY) ? "primary" : "secondary"); for (i = 1; i < pktgen.argc; i++) fprintf(fd, "%s ", pktgen.argv[i]); fprintf(fd, "\n\n-- %s\n", hash_line); fprintf(fd, "-- Pktgen Configuration script information:\n"); fprintf(fd, "-- Flags %08x\n", pktgen.flags); fprintf(fd, "-- Number of ports: %d\n", pktgen.nb_ports); fprintf(fd, "-- Number ports per page: %d\n", pktgen.nb_ports_per_page); fprintf(fd, "-- Number descriptors: RX %d TX: %d\n", pktgen.nb_rxd, pktgen.nb_txd); fprintf(fd, "-- Promiscuous mode is %s\n\n", (pktgen.flags & PROMISCUOUS_ON_FLAG) ? "Enabled" : "Disabled"); fprintf(fd, "\n--%s\n", hash_line); fprintf(fd, "-- Global configuration:\n"); uint16_t rows, cols; pktgen_display_get_geometry(&rows, &cols); fprintf(fd, "-- geometry %dx%d\n", cols, rows); fprintf(fd, "pktgen.mac_from_arp(\"%s\");\n\n", (pktgen.flags & MAC_FROM_ARP_FLAG) ? "enable" : "disable"); for (i = 0; i < pktgen.nb_ports; i++) { pinfo = l2p_get_port_pinfo(i); pkt = &pinfo->seq_pkt[SINGLE_PKT]; range = &pinfo->range; if (pinfo->tx_burst == 0) continue; lat = &pinfo->latency; fprintf(fd, "-- ######################### Port %2d ##################################\n", i); if (rte_atomic64_read(&pinfo->transmit_count) == 0) strcpy(buff, "Forever"); else snprintf(buff, sizeof(buff), "%" PRIu64, rte_atomic64_read(&pinfo->transmit_count)); fprintf(fd, "-- \n"); flags = rte_atomic64_read(&pinfo->port_flags); fprintf(fd, "-- Port: %2d, Burst (Rx/Tx):%3d/%3d, Rate:%g%%, Flags:%16x, TX Count:%s\n", pinfo->pid, pinfo->rx_burst, pinfo->tx_burst, pinfo->tx_rate, flags, buff); fprintf(fd, "-- Sequence Count:%d, Prime:%d VLAN ID:%04x, ", pinfo->seqCnt, pinfo->prime_cnt, pinfo->vlanid); pktgen_link_state(pinfo->pid, buff, sizeof(buff)); fprintf(fd, "Link: %s\n", buff); fprintf(fd, "--\n-- Set up the primary port information:\n"); fprintf(fd, "pktgen.set('%d', 'count', %" PRIu64 ");\n", pinfo->pid, rte_atomic64_read(&pinfo->transmit_count)); fprintf(fd, "pktgen.set('%d', 'size', %d);\n", pinfo->pid, pkt->pkt_size + RTE_ETHER_CRC_LEN); fprintf(fd, "pktgen.set('%d', 'rate', %g);\n", pinfo->pid, pinfo->tx_rate); fprintf(fd, "pktgen.set('%d', 'txburst', %d);\n", pinfo->pid, pinfo->tx_burst); fprintf(fd, "pktgen.set('%d', 'rxburst', %d);\n", pinfo->pid, pinfo->rx_burst); fprintf(fd, "pktgen.set('%d', 'sport', %d);\n", pinfo->pid, pkt->sport); fprintf(fd, "pktgen.set('%d', 'dport', %d);\n", pinfo->pid, pkt->dport); fprintf(fd, "pktgen.set('%d', 'prime', %d);\n", pinfo->pid, pinfo->prime_cnt); fprintf(fd, "pktgen.set_type('%d', '%s');\n", i, (pkt->ethType == RTE_ETHER_TYPE_IPV4) ? "ipv4" : (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? "ipv6" : (pkt->ethType == RTE_ETHER_TYPE_VLAN) ? "vlan" : (pkt->ethType == RTE_ETHER_TYPE_ARP) ? "arp" : "unknown"); fprintf(fd, "pktgen.set_proto('%d', '%s');\n", i, (pkt->ipProto == PG_IPPROTO_TCP) ? "tcp" : (pkt->ipProto == PG_IPPROTO_ICMP) ? "icmp" : "udp"); fprintf(fd, "pktgen.set_ipaddr('%d', 'dst', '%s');\n", i, (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? inet_ntop6(buff, sizeof(buff), pkt->ip_dst_addr.addr.ipv6.a, PG_PREFIXMAX) : inet_ntop4(buff, sizeof(buff), ntohl(pkt->ip_dst_addr.addr.ipv4.s_addr), 0xFFFFFFFF)); fprintf(fd, "pktgen.set_ipaddr('%d', 'src','%s');\n", i, (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? inet_ntop6(buff, sizeof(buff), pkt->ip_src_addr.addr.ipv6.a, pkt->ip_src_addr.prefixlen) : inet_ntop4(buff, sizeof(buff), ntohl(pkt->ip_src_addr.addr.ipv4.s_addr), pkt->ip_mask)); fprintf(fd, "pktgen.set_mac('%d', 'dst', '%s');\n", pinfo->pid, inet_mtoa(buff, sizeof(buff), &pkt->eth_dst_addr)); fprintf(fd, "pktgen.set_mac('%d', 'src', '%s');\n", pinfo->pid, inet_mtoa(buff, sizeof(buff), &pkt->eth_src_addr)); fprintf(fd, "pktgen.vlanid('%d', %d);\n\n", i, pkt->vlanid); fprintf(fd, "pktgen.pattern('%d', '%s');\n", i, (pinfo->fill_pattern_type == ABC_FILL_PATTERN) ? "abc" : (pinfo->fill_pattern_type == NO_FILL_PATTERN) ? "none" : (pinfo->fill_pattern_type == ZERO_FILL_PATTERN) ? "zero" : "user"); if ((pinfo->fill_pattern_type == USER_FILL_PATTERN) && strlen(pinfo->user_pattern)) { char buff[64]; memset(buff, 0, sizeof(buff)); snprintf(buff, sizeof(buff), "%s", pinfo->user_pattern); fprintf(fd, "pktgen.userPattern('%d', '%s');\n", i, buff); } fprintf(fd, "\n"); fflush(fd); fprintf(fd, "pktgen.jitter('%d', %lu);\n", i, lat->jitter_threshold_us); fprintf(fd, "pktgen.mpls('%d', '%sable');\n", i, (flags & SEND_MPLS_LABEL) ? "en" : "dis"); sprintf(buff, "0x%x", pkt->mpls_entry); fprintf(fd, "pktgen.range.mpls_entry('%d', '%s');\n", i, buff); fprintf(fd, "pktgen.qinq('%d', '%sable');\n", i, (flags & SEND_Q_IN_Q_IDS) ? "en" : "dis"); fprintf(fd, "pktgen.range.qinqids('%d', %d, %d);\n", i, pkt->qinq_outerid, pkt->qinq_innerid); fprintf(fd, "pktgen.gre('%d', '%sable');\n", i, (flags & SEND_GRE_IPv4_HEADER) ? "en" : "dis"); fprintf(fd, "pktgen.gre_eth('%d', '%sable');\n", i, (flags & SEND_GRE_ETHER_HEADER) ? "en" : "dis"); fprintf(fd, "pktgen.range.gre_key('%d', %d);\n", i, pkt->gre_key); fprintf(fd, "pktgen.vxlan('%d', '%sable');\n", i, (flags & SEND_VXLAN_PACKETS) ? "en" : "dis"); fprintf(fd, "pktgen.vxlan_id('%d', '0x%x', '%d', '%d');\n", i, pkt->vni_flags, pkt->group_id, pkt->vxlan_id); fprintf(fd, "--\n-- Port flag values:\n"); fprintf(fd, "pktgen.icmp_echo('%d', '%sable');\n", i, (flags & ICMP_ECHO_ENABLE_FLAG) ? "en" : "dis"); fprintf(fd, "pktgen.pcap('%d', '%sable');\n", i, (flags & SEND_PCAP_PKTS) ? "en" : "dis"); fprintf(fd, "pktgen.set_range('%d', '%sable');\n", i, (flags & SEND_RANGE_PKTS) ? "en" : "dis"); fprintf(fd, "pktgen.latency('%d', '%sable');\n", i, (flags & SEND_LATENCY_PKTS) ? "en" : "dis"); fprintf(fd, "pktgen.process('%d', '%sable');\n", i, (flags & PROCESS_INPUT_PKTS) ? "en" : "dis"); fprintf(fd, "pktgen.capture('%d', '%sable');\n", i, (flags & CAPTURE_PKTS) ? "en" : "dis"); fprintf(fd, "pktgen.vlan('%d', '%sable');\n\n", i, (flags & SEND_VLAN_ID) ? "en" : "dis"); fflush(fd); fprintf(fd, "--\n-- Range packet information:\n"); fprintf(fd, "pktgen.range.src_mac('%d', 'start', '%s');\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac, &eaddr))); fprintf(fd, "pktgen.range.src_mac('%d', 'min', '%s');\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac_min, &eaddr))); fprintf(fd, "pktgen.range.src_mac('%d', 'max', '%s');\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac_max, &eaddr))); fprintf(fd, "pktgen.range.src_mac('%d', 'inc', '%s');\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac_inc, &eaddr))); fprintf(fd, "pktgen.range.dst_mac('%d', 'start', '%s');\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac, &eaddr))); fprintf(fd, "pktgen.range.dst_mac('%d', 'min', '%s');\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac_min, &eaddr))); fprintf(fd, "pktgen.range.dst_mac('%d', 'max', '%s');\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac_max, &eaddr))); fprintf(fd, "pktgen.range.dst_mac('%d', 'inc', '%s');\n", i, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac_inc, &eaddr))); fprintf(fd, "\n"); fprintf(fd, "pktgen.range.src_ip('%d', 'start', '%s');\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->src_ip), 0xFFFFFFFF)); fprintf(fd, "pktgen.range.src_ip('%d', 'min', '%s');\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->src_ip_min), 0xFFFFFFFF)); fprintf(fd, "pktgen.range.src_ip('%d', 'max', '%s');\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->src_ip_max), 0xFFFFFFFF)); fprintf(fd, "pktgen.range.src_ip('%d', 'inc', '%s');\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->src_ip_inc), 0xFFFFFFFF)); fprintf(fd, "\n"); fprintf(fd, "pktgen.range.dst_ip('%d', 'start', '%s');\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->dst_ip), 0xFFFFFFFF)); fprintf(fd, "pktgen.range.dst_ip('%d', 'min', '%s');\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->dst_ip_min), 0xFFFFFFFF)); fprintf(fd, "pktgen.range.dst_ip('%d', 'max', '%s');\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->dst_ip_max), 0xFFFFFFFF)); fprintf(fd, "pktgen.range.dst_ip('%d', 'inc', '%s');\n", i, inet_ntop4(buff, sizeof(buff), ntohl(range->dst_ip_inc), 0xFFFFFFFF)); fprintf(fd, "\n"); fprintf(fd, "pktgen.range.ip_proto('%d', '%s');\n", i, (range->ip_proto == PG_IPPROTO_UDP) ? "udp" : (range->ip_proto == PG_IPPROTO_ICMP) ? "icmp" : "tcp"); fprintf(fd, "\n"); fprintf(fd, "pktgen.range.src_port('%d', 'start', %d);\n", i, range->src_port); fprintf(fd, "pktgen.range.src_port('%d', 'min', %d);\n", i, range->src_port_min); fprintf(fd, "pktgen.range.src_port('%d', 'max', %d);\n", i, range->src_port_max); fprintf(fd, "pktgen.range.src_port('%d', 'inc', %d);\n", i, range->src_port_inc); fprintf(fd, "\n"); fprintf(fd, "pktgen.range.dst_port('%d', 'start', %d);\n", i, range->dst_port); fprintf(fd, "pktgen.range.dst_port('%d', 'min', %d);\n", i, range->dst_port_min); fprintf(fd, "pktgen.range.dst_port('%d', 'max', %d);\n", i, range->dst_port_max); fprintf(fd, "pktgen.range.dst_port('%d', 'inc', %d);\n", i, range->dst_port_inc); fprintf(fd, "\n"); fprintf(fd, "pktgen.range.ttl('%d', 'start', %d);\n", i, range->ttl); fprintf(fd, "pktgen.range.ttl('%d', 'min', %d);\n", i, range->ttl_min); fprintf(fd, "pktgen.range.ttl('%d', 'max', %d);\n", i, range->ttl_max); fprintf(fd, "pktgen.range.ttl('%d', 'inc', %d);\n", i, range->ttl_inc); fprintf(fd, "\n"); fprintf(fd, "pktgen.range.vlan_id('%d', 'start', %d);\n", i, range->vlan_id); fprintf(fd, "pktgen.range.vlan_id('%d', 'min', %d);\n", i, range->vlan_id_min); fprintf(fd, "pktgen.range.vlan_id('%d', 'max', %d);\n", i, range->vlan_id_max); fprintf(fd, "pktgen.range.vlan_id('%d', 'inc', %d);\n", i, range->vlan_id_inc); fprintf(fd, "\n"); fprintf(fd, "pktgen.range.cos('%d', 'start', %d);\n", i, range->cos); fprintf(fd, "pktgen.range.cos('%d', 'min', %d);\n", i, range->cos_min); fprintf(fd, "pktgen.range.cos('%d', 'max', %d);\n", i, range->cos_max); fprintf(fd, "pktgen.range.cos('%d', 'inc', %d);\n", i, range->cos_inc); fprintf(fd, "\n"); fprintf(fd, "pktgen.range.tos('%d', 'start', %d);\n", i, range->tos); fprintf(fd, "pktgen.range.tos('%d', 'min', %d);\n", i, range->tos_min); fprintf(fd, "pktgen.range.tos('%d', 'max', %d);\n", i, range->tos_max); fprintf(fd, "pktgen.range.tos('%d', 'inc', %d);\n", i, range->tos_inc); fprintf(fd, "\n"); fprintf(fd, "pktgen.range.pkt_size('%d', 'start', %d);\n", i, range->pkt_size + RTE_ETHER_CRC_LEN); fprintf(fd, "pktgen.range.pkt_size('%d', 'min', %d);\n", i, range->pkt_size_min + RTE_ETHER_CRC_LEN); fprintf(fd, "pktgen.range.pkt_size('%d', 'max', %d);\n", i, range->pkt_size_max + RTE_ETHER_CRC_LEN); fprintf(fd, "pktgen.range.pkt_size('%d', 'inc', %d);\n\n", i, range->pkt_size_inc); fprintf(fd, "--\n-- Set up the sequence data for the port.\n"); fprintf(fd, "pktgen.set('%d', 'seq_cnt', %d);\n\n", pinfo->pid, pinfo->seqCnt); fflush(fd); if (pinfo->seqCnt) { fprintf(fd, "-- (seqnum, port, dst_mac, src_mac, ip_dst, ip_src, sport, dport, " "ethType, proto, vlanid, pkt_size, gtpu_teid)\n"); for (j = 0; j < pinfo->seqCnt; j++) { pkt = &pinfo->seq_pkt[j]; fprintf(fd, "-- pktgen.seq(%d, '%d', '%s' ", j, i, inet_mtoa(buff, sizeof(buff), &pkt->eth_dst_addr)); fprintf(fd, "'%s', ", inet_mtoa(buff, sizeof(buff), &pkt->eth_src_addr)); fprintf( fd, "'%s', ", (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? inet_ntop6(buff, sizeof(buff), pkt->ip_dst_addr.addr.ipv6.a, PG_PREFIXMAX) : inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_dst_addr.addr.ipv4.s_addr), 0xFFFFFFFF)); fprintf(fd, "'%s', ", (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? inet_ntop6(buff, sizeof(buff), pkt->ip_src_addr.addr.ipv6.a, pkt->ip_src_addr.prefixlen) : inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_src_addr.addr.ipv4.s_addr), pkt->ip_mask)); fprintf(fd, "%d, %d, '%s', '%s', %d, %d, %d);\n", pkt->sport, pkt->dport, (pkt->ethType == RTE_ETHER_TYPE_IPV4) ? "ipv4" : (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? "ipv6" : (pkt->ethType == RTE_ETHER_TYPE_VLAN) ? "vlan" : "Other", (pkt->ipProto == PG_IPPROTO_TCP) ? "tcp" : (pkt->ipProto == PG_IPPROTO_ICMP) ? "icmp" : "udp", pkt->vlanid, pkt->pkt_size + RTE_ETHER_CRC_LEN, pkt->gtpu_teid); } fflush(fd); fprintf(fd, "local seq_table = {}\n"); for (j = 0; j < pinfo->seqCnt; j++) { pkt = &pinfo->seq_pkt[j]; fprintf(fd, "seq_table[%d] = {\n", j); fprintf(fd, " ['eth_dst_addr'] = '%s',\n", inet_mtoa(buff, sizeof(buff), &pkt->eth_dst_addr)); fprintf(fd, " ['eth_src_addr'] = '%s',\n", inet_mtoa(buff, sizeof(buff), &pkt->eth_src_addr)); fprintf( fd, " ['ip_dst_addr'] = '%s',\n", (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? inet_ntop6(buff, sizeof(buff), pkt->ip_dst_addr.addr.ipv6.a, PG_PREFIXMAX) : inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_dst_addr.addr.ipv4.s_addr), 0xFFFFFFFF)); fprintf(fd, " ['ip_src_addr'] = '%s',\n", (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? inet_ntop6(buff, sizeof(buff), pkt->ip_src_addr.addr.ipv6.a, pkt->ip_src_addr.prefixlen) : inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_src_addr.addr.ipv4.s_addr), 0xFFFFFFFF)); fprintf(fd, " ['sport'] = %d,\n", pkt->sport); fprintf(fd, " ['dport'] = %d,\n", pkt->dport); fprintf(fd, " ['ethType'] = '%s',\n", (pkt->ethType == RTE_ETHER_TYPE_IPV4) ? "ipv4" : (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? "ipv6" : (pkt->ethType == RTE_ETHER_TYPE_VLAN) ? "vlan" : "Other"); fprintf(fd, " ['ipProto'] = '%s',\n", (pkt->ipProto == PG_IPPROTO_TCP) ? "tcp" : (pkt->ipProto == PG_IPPROTO_ICMP) ? "icmp" : "udp"); fprintf(fd, " ['vlanid'] = %d,\n", pkt->vlanid); fprintf(fd, " ['pktSize'] = %d,\n", pkt->pkt_size); fprintf(fd, " ['gtpu_teid'] = %d\n", pkt->gtpu_teid); fprintf(fd, "}\n"); } fflush(fd); for (j = 0; j < pinfo->seqCnt; j++) fprintf(fd, "pktgen.seqTable(%d, '%d', seq_table[%d]);\n", j, i, j); } fflush(fd); pcap_info_t *pcap = l2p_get_pcap(pinfo->pid); if (pcap) { fprintf(fd, "--\n-- PCAP port %d\n", i); fprintf(fd, "-- Packet count: %d, max size: %d\n", pcap->pkt_count, pcap->max_pkt_size); fprintf(fd, "-- Filename : %s\n", pcap->filename); } fprintf(fd, "\n"); fflush(fd); if (pinfo->rnd_bitfields && pinfo->rnd_bitfields->active_specs) { uint32_t active = pinfo->rnd_bitfields->active_specs; bf_spec_t *bf; fprintf(fd, "\n-- Rnd bitfields\n"); fflush(fd); for (j = 0; j < MAX_RND_BITFIELDS; j++) { if ((active & (1 << j)) == 0) continue; bf = &pinfo->rnd_bitfields->specs[j]; fprintf(fd, "pktgen.rnd('%d', %d, %d, '%s');\n", i, j, bf->offset, convert_bitfield(bf)); } fprintf(fd, "\n"); } } fprintf(fd, "pktgen.screen('on');\n"); fprintf(fd, "pktgen.cls();\n\n"); fprintf(fd, "-- ################################ Done #################################\n"); fflush(fd); fchmod(fileno(fd), 0666); fclose(fd); return 0; } /** * * pktgen_save - Save a configuration as a startup script * * DESCRIPTION * Save a configuration as a startup script * * RETURNS: N/A * * SEE ALSO: */ int pktgen_save(char *path) { if (strcasestr(path, ".lua") != NULL) return pktgen_lua_save(path); else return pktgen_script_save(path); } /** * * pktgen_port_transmitting - Is the port transmitting packets? * * DESCRIPTION * Is the port transmitting packets. * * RETURNS: 1 for yes and 0 for no. * * SEE ALSO: */ int pktgen_port_transmitting(int port) { return pktgen_tst_port_flags(l2p_get_port_pinfo(port), SENDING_PACKETS); } /** * * pktgen_link_state - Get the ASCII string for the port state. * * DESCRIPTION * Return the port state string for a given port. * * RETURNS: String pointer to link state * * SEE ALSO: */ char * pktgen_link_state(int port, char *buff, int len) { port_info_t *pinfo = l2p_get_port_pinfo(port); if (pinfo->link.link_status == RTE_ETH_LINK_UP) snprintf(buff, len, "", (uint32_t)pinfo->link.link_speed, (pinfo->link.link_duplex == RTE_ETH_LINK_FULL_DUPLEX) ? ("FD") : ("HD")); else snprintf(buff, len, "<--Down-->"); return buff; } /** * * pktgen_transmit_count_rate - Get a string for the current transmit count and rate * * DESCRIPTION * Current value of the transmit count/%rate as a string. * * RETURNS: String pointer to transmit count/%rate. * * SEE ALSO: */ char * pktgen_transmit_count_rate(int port, char *buff, int len) { port_info_t *pinfo = l2p_get_port_pinfo(port); if (rte_atomic64_read(&pinfo->transmit_count) == 0) snprintf(buff, len, "Forever /%g%%", pinfo->tx_rate); else snprintf(buff, len, "%" PRIu64 " /%g%%", rte_atomic64_read(&pinfo->transmit_count), pinfo->tx_rate); return buff; } /** * * pktgen_port_stats - Get the port stats structure. * * DESCRIPTION * Return the packet statistics values. * * RETURNS: N/A * * SEE ALSO: */ int pktgen_port_stats(int port, port_stats_t *ps) { port_info_t *pinfo = l2p_get_port_pinfo(port); if (!ps) return -1; *ps = pinfo->stats; return 0; } /** * * pktgen_flags_string - Return the flags string for display * * DESCRIPTION * Return the current flags string for display for a port. * * RETURNS: N/A * * SEE ALSO: */ char * pktgen_flags_string(port_info_t *pinfo) { static char buff[64]; uint64_t flags = rte_atomic64_read(&pinfo->port_flags); buff[0] = '\0'; // clang-format off snprintf(buff, sizeof(buff), "%c%c%c%c%c%c%c%c%c%-6s%6s", (pktgen.flags & PROMISCUOUS_ON_FLAG) ? 'P' : '-', (flags & ICMP_ECHO_ENABLE_FLAG) ? 'E' : '-', (flags & BONDING_TX_PACKETS) ? 'B' : '-', (flags & PROCESS_INPUT_PKTS) ? 'I' : '-', (flags & SEND_LATENCY_PKTS) ? 'L' : '-', (flags & RANDOMIZE_SRC_IP) ? 'i' : '-', (flags & RANDOMIZE_SRC_PT) ? 'p' : '-', (flags & SEND_RANDOM_PKTS) ? 'R' : '-', (flags & CAPTURE_PKTS) ? 'c' : '-', (flags & SEND_VLAN_ID) ? "VLAN" : (flags & SEND_VXLAN_PACKETS) ? "VxLan" : (flags & SEND_MPLS_LABEL) ? "MPLS" : (flags & SEND_Q_IN_Q_IDS) ? "QnQ" : (flags & SEND_GRE_IPv4_HEADER) ? "GREip" : (flags & SEND_GRE_ETHER_HEADER) ? "GREet" : "", (flags & SEND_PCAP_PKTS) ? "PCAP" : (flags & SEND_SEQ_PKTS) ? "Seq" : (flags & SEND_RANGE_PKTS) ? "Range" : (flags & SEND_SINGLE_PKTS) ? "Single" : "Unkn"); // clang-format on return buff; } /** * * pktgen_update_display - Update the display data and static data. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_update_display(void) { pktgen.flags |= PRINT_LABELS_FLAG; pktgen.flags |= UPDATE_DISPLAY_FLAG; } /** * * pktgen_clear_display - clear the screen. * * DESCRIPTION * clear the screen and redisplay data. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_clear_display(void) { if (!scrn_is_paused()) { scrn_pause(); scrn_cls(); scrn_pos(this_scrn->nrows + 1, 1); pktgen_update_display(); scrn_resume(); pktgen_page_display(); } } /** * * pktgen_force_update - Force the screen to update data and static data. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_force_update(void) { pktgen.flags |= UPDATE_DISPLAY_FLAG; if (!scrn_is_paused()) pktgen_page_display(); } /** * * pktgen_set_page_size - Set the number of ports per page. * * DESCRIPTION * Set the max number of ports per page. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_set_page_size(uint32_t page_size) { if ((page_size > 0) && (page_size <= pktgen.nb_ports) && (page_size <= 6)) { pktgen.nb_ports_per_page = page_size; pktgen.ending_port = pktgen.starting_port + page_size; if (pktgen.ending_port >= (pktgen.starting_port + pktgen.nb_ports)) pktgen.ending_port = (pktgen.starting_port + pktgen.nb_ports); pktgen_clear_display(); } } /** * * pktgen_screen - Enable or Disable screen updates. * * DESCRIPTION * Enable or disable screen updates. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_screen(int state) { uint16_t rows; pktgen_display_get_geometry(&rows, NULL); if (state == DISABLE_STATE) { if (!scrn_is_paused()) { scrn_pause(); scrn_cls(); scrn_setw(1); scrn_pos(rows + 1, 1); } } else { scrn_cls(); scrn_setw(pktgen.last_row + 1); scrn_resume(); scrn_pos(rows + 1, 1); pktgen_force_update(); } } /** * * pktgen_set_port_number - Set the current port number for sequence and range pages * * DESCRIPTION * Set the current port number for sequence and range pages. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_set_port_number(uint16_t port_number) { if (port_number < pktgen.nb_ports) { pktgen.curr_port = port_number; pktgen_clear_display(); } } /** * * pktgen_set_icmp_echo - Set the ICMP echo response flag on a port * * DESCRIPTION * Enable or disable the ICMP echo response flags for the given ports. * * RETURNS: N/A * * SEE ALSO: */ void enable_icmp_echo(port_info_t *pinfo, uint32_t onOff) { if (onOff == ENABLE_STATE) pktgen_set_port_flags(pinfo, ICMP_ECHO_ENABLE_FLAG); else pktgen_clr_port_flags(pinfo, ICMP_ECHO_ENABLE_FLAG); } /** * * enable_mac_from_arp - Enable or disable getting MAC from ARP requests. * * DESCRIPTION * Enable or disable getting the MAC address from the ARP request packets. * * RETURNS: N/A * * SEE ALSO: */ void enable_mac_from_arp(uint32_t onOff) { if (onOff == ENABLE_STATE) pktgen.flags |= MAC_FROM_ARP_FLAG; else pktgen.flags &= ~MAC_FROM_ARP_FLAG; } /** * * enable_rnd_s_ip - Enable/disable randomizing the source IP address * * DESCRIPTION * Enable/disable randomizing the source IP address. * * Naively randomizes the addresses, as validating each IP could be a performance * bottleneck (given that only ~14% are invalid) and some people might want to * test invalid IPs too. * * RETURNS: N/A * * SEE ALSO: */ void enable_rnd_s_ip(port_info_t *pinfo, uint32_t onOff) { if (onOff == ENABLE_STATE) pktgen_set_port_flags(pinfo, RANDOMIZE_SRC_IP); else pktgen_clr_port_flags(pinfo, RANDOMIZE_SRC_IP); } /** * * enable_rnd_s_pt - Enable/disable randomizing the source port * * DESCRIPTION * Enable/disable randomizing the source port. * * Naively randomizes the port, as despite it probably being weird for some ports to * receive traffic, all of them are technically valid (except for port 0). * * RETURNS: N/A * * SEE ALSO: */ void enable_rnd_s_pt(port_info_t *pinfo, uint32_t onOff) { if (onOff == ENABLE_STATE) pktgen_set_port_flags(pinfo, RANDOMIZE_SRC_PT); else pktgen_clr_port_flags(pinfo, RANDOMIZE_SRC_PT); } /** * * enable_random - Enable/disable random bitfield mode * * DESCRIPTION * Enable/disable random bitfield mode * * RETURNS: N/A * * SEE ALSO: */ void enable_random(port_info_t *pinfo, uint32_t onOff) { if (onOff == ENABLE_STATE) { pktgen_clr_port_flags(pinfo, EXCLUSIVE_MODES); pktgen_set_port_flags(pinfo, SEND_RANDOM_PKTS | SEND_SINGLE_PKTS); } else pktgen_clr_port_flags(pinfo, SEND_RANDOM_PKTS); } void enable_clock_gettime(uint32_t onOff) { if (onOff == ENABLE_STATE) pktgen.flags |= CLOCK_GETTIME_FLAG; else pktgen.flags &= ~CLOCK_GETTIME_FLAG; pktgen.hz = pktgen_get_timer_hz(); pktgen.page_timeout = UPDATE_DISPLAY_TICK_RATE; pktgen.stats_timeout = pktgen.hz; } void debug_tx_rate(port_info_t *pinfo) { printf(" %d: rate %.2f, tx_cycles %'ld, tx_pps %'ld, link %s-%d-%s, hz %'ld\n", pinfo->pid, pinfo->tx_rate, pinfo->tx_cycles, pinfo->tx_pps, (pinfo->link.link_status) ? "UP" : "Down", pinfo->link.link_speed, (pinfo->link.link_duplex == RTE_ETH_LINK_FULL_DUPLEX) ? "FD" : "HD", pktgen.hz); } /* * Local wrapper function to test mp is NULL and return or continue * to call rte_mempool_dump() routine. */ static void __mempool_dump(FILE *f, struct rte_mempool *mp) { if (mp == NULL) return; rte_mempool_dump(f, mp); } /** * * debug_mempool_dump - Display the mempool information * * DESCRIPTION * Dump out the mempool information. * * RETURNS: N/A * * SEE ALSO: */ void debug_mempool_dump(port_info_t *pinfo __rte_unused, char *name __rte_unused) { int all; all = !strcmp(name, "all"); if (all || !strcmp(name, "rx")) { for (uint16_t q = 0; q < l2p_get_rxcnt(pinfo->pid); q++) { struct rte_mempool *mp = l2p_get_rx_mp(pinfo->pid, q); if (mp) fprintf(stdout, "\nPort %u RX qid %u: %s @ %p\n", pinfo->pid, q, mp->name, mp); __mempool_dump(stdout, mp); } } if (all || !strcmp(name, "tx")) { for (uint16_t q = 0; q < l2p_get_txcnt(pinfo->pid); q++) { struct rte_mempool *mp = l2p_get_tx_mp(pinfo->pid, q); if (mp) fprintf(stdout, "\nPort %u TX qid %u: %s @ %p\n", pinfo->pid, q, mp->name, mp); __mempool_dump(stdout, mp); } } if (all || !strcmp(name, "arp")) { for (uint16_t q = 0; q < l2p_get_txcnt(pinfo->pid); q++) { struct rte_mempool *mp = l2p_get_sp_mp(pinfo->pid, q); if (mp) fprintf(stdout, "\nPort %u SP qid %u: %s @ %p\n", pinfo->pid, q, mp->name, mp); __mempool_dump(stdout, mp); } } } /** * * pktgen_start_transmitting - Start a port transmitting packets. * * DESCRIPTION * Start the given ports sending packets. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_start_transmitting(port_info_t *pinfo) { if (!pktgen_tst_port_flags(pinfo, SENDING_PACKETS)) { rte_atomic64_set(&pinfo->current_tx_count, rte_atomic64_read(&pinfo->transmit_count)); if (rte_atomic64_read(&pinfo->current_tx_count) == 0) pktgen_set_port_flags(pinfo, SEND_FOREVER); pktgen_set_port_flags(pinfo, SETUP_TRANSMIT_PKTS); pktgen_setup_packets(pinfo->pid); // will clear the SETUP_TRANSMIT_PKTS flag pktgen_set_port_flags(pinfo, SENDING_PACKETS); } } /** * * pktgen_stop_transmitting - Stop port transmitting packets. * * DESCRIPTION * Stop the given ports from sending traffic. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_stop_transmitting(port_info_t *pinfo) { if (pktgen_tst_port_flags(pinfo, SENDING_PACKETS)) pktgen_clr_port_flags(pinfo, (SENDING_PACKETS | SEND_FOREVER)); } static void pktgen_set_receive_state(port_info_t *pinfo, int state) { if (state) pktgen_set_port_flags(pinfo, STOP_RECEIVING_PACKETS); else pktgen_clr_port_flags(pinfo, STOP_RECEIVING_PACKETS); } /** * * pktgen_start_stop_latency_sampler - Starts or stops latency sampler. * * DESCRIPTION * Starts or stops latency sampler. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_start_stop_latency_sampler(port_info_t *pinfo, uint32_t state) { if (state == ENABLE_STATE) pktgen_start_latency_sampler(pinfo); else if (state == DISABLE_STATE) pktgen_stop_latency_sampler(pinfo); } /** * * start_latency_sampler - Starts latency sampler. * * DESCRIPTION * Starts latency sampler. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_start_latency_sampler(port_info_t *pinfo) { uint16_t q, rxq; /* Start sampler */ if (pktgen_tst_port_flags(pinfo, SAMPLING_LATENCIES)) { pktgen_log_info("Latency sampler is already running, stop it first!"); return; } if (pinfo->latsamp_rate == 0 || pinfo->latsamp_type == LATSAMPLER_UNSPEC || pinfo->latsamp_num_samples == 0) { pktgen_log_error("Set proper sampling type, number, rate and outfile!"); return; } rxq = l2p_get_rxcnt(pinfo->pid); if (rxq == 0 || rxq > MAX_QUEUES_PER_PORT) { pktgen_log_error("no rx queues or rx queues over limit (%d) to sample on this port!", MAX_QUEUES_PER_PORT); return; } for (q = 0; q < rxq; q++) { pinfo->latsamp_stats[q].next = 0; pinfo->latsamp_stats[q].idx = 0; pinfo->latsamp_stats[q].num_samples = pinfo->latsamp_num_samples / rxq; pktgen_log_info("Assigning %d sample latencies to queue %d", pinfo->latsamp_num_samples / rxq, q); } if (pinfo->seq_pkt[LATENCY_PKT].pkt_size < (RTE_ETHER_MIN_LEN - RTE_ETHER_CRC_LEN) + sizeof(tstamp_t)) pinfo->seq_pkt[LATENCY_PKT].pkt_size += sizeof(tstamp_t); pinfo->seq_pkt[LATENCY_PKT].ipProto = PG_IPPROTO_UDP; pktgen_packet_ctor(pinfo, LATENCY_PKT, -1); /* Start sampling */ pktgen_set_port_flags(pinfo, SAMPLING_LATENCIES); } /** * * stop_latency_sampler - Stops latency sampler * * DESCRIPTION * Stops latency sampler * * RETURNS: N/A * * SEE ALSO: */ void pktgen_stop_latency_sampler(port_info_t *pinfo) { FILE *outfile; uint32_t i, count; uint16_t q, rxq = l2p_get_rxcnt(pinfo->pid); if (!pktgen_tst_port_flags(pinfo, SAMPLING_LATENCIES)) { pktgen_log_info("Latency sampler is not running, nothing to do!"); return; } /* Stop sampling */ pktgen_clr_port_flags(pinfo, SAMPLING_LATENCIES); /* Dump stats to file */ outfile = fopen(pinfo->latsamp_outfile, "w"); if (outfile == NULL) pktgen_log_error("Cannot open the latency outfile!"); else { pktgen_log_info("Writing to file %s", pinfo->latsamp_outfile); fprintf(outfile, "Latency\n"); for (q = 0, count = 0; q < rxq; q++) { pktgen_log_info("Writing sample latencies of queue %d", q); for (i = 0; i < pinfo->latsamp_stats[q].idx; i++) { fprintf(outfile, "%" PRIu64 "\n", pinfo->latsamp_stats[q].data[i]); count++; } } fclose(outfile); pktgen_log_warning("Wrote %d sample latencies to file %s", count, pinfo->latsamp_outfile); } /* Reset stats data */ for (q = 0; q < rxq; q++) { pinfo->latsamp_stats[q].next = 0; pinfo->latsamp_stats[q].idx = 0; pinfo->latsamp_stats[q].num_samples = 0; } pinfo->seq_pkt[LATENCY_PKT].ipProto = PG_IPPROTO_UDP; pktgen_packet_ctor(pinfo, LATENCY_PKT, -1); } /** * * pktgen_prime_ports - Send a small number of packets to setup forwarding tables * * DESCRIPTION * Send a small number of packets from a port to setup the forwarding tables in * the device under test. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_prime_ports(port_info_t *pinfo) { rte_atomic64_set(&pinfo->current_tx_count, pinfo->prime_cnt); pktgen_set_port_flags(pinfo, SENDING_PACKETS); rte_delay_us_sleep(300 * 1000); } /** * * single_set_proto - Set up the protocol type for a port/packet. * * DESCRIPTION * Setup all single packets with a protocol types with the port list. * * RETURNS: N/A * * SEE ALSO: */ void single_set_proto(port_info_t *pinfo, char *type) { pinfo->seq_pkt[SINGLE_PKT].ipProto = (type[0] == 'u') ? PG_IPPROTO_UDP : (type[0] == 'i') ? PG_IPPROTO_ICMP : (type[0] == 't') ? PG_IPPROTO_TCP : PG_IPPROTO_TCP; /* ICMP only works on IPv4 packets. */ if (type[0] == 'i') pinfo->seq_pkt[SINGLE_PKT].ethType = RTE_ETHER_TYPE_IPV4; pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * range_set_proto - Set up the protocol type for a port/packet. * * DESCRIPTION * Setup all range packets with a protocol types with the port list. * * RETURNS: N/A * * SEE ALSO: */ void range_set_proto(port_info_t *pinfo, const char *type) { pinfo->seq_pkt[RANGE_PKT].ipProto = (type[0] == 'u') ? PG_IPPROTO_UDP : (type[0] == 'i') ? PG_IPPROTO_ICMP : (type[0] == 't') ? PG_IPPROTO_TCP : PG_IPPROTO_TCP; pinfo->range.ip_proto = pinfo->seq_pkt[RANGE_PKT].ipProto; /* ICMP only works on IPv4 packets. */ if (type[0] == 'i') pinfo->seq_pkt[RANGE_PKT].ethType = RTE_ETHER_TYPE_IPV4; } /** * * enable_pcap - Enable or disable PCAP sending of packets. * * DESCRIPTION * Enable or disable PCAP packet sending. * * RETURNS: N/A * * SEE ALSO: */ void enable_pcap(port_info_t *pinfo, uint32_t state) { pcap_info_t *pcap = l2p_get_pcap(pinfo->pid); if ((pcap != NULL) && (pcap->pkt_count != 0)) { if (state == ENABLE_STATE) { pktgen_clr_port_flags(pinfo, EXCLUSIVE_MODES); pktgen_set_port_flags(pinfo, SEND_PCAP_PKTS); } else { pktgen_clr_port_flags(pinfo, EXCLUSIVE_MODES); pktgen_set_port_flags(pinfo, SEND_SINGLE_PKTS); } pinfo->tx_cycles = 0; } } /** * * pcap_filter - Compile a PCAP filter for a portlist * * DESCRIPTION * Compile a pcap filter for a portlist * * RETURNS: N/A * * SEE ALSO: */ void pcap_filter(port_info_t *pinfo, char *str) { pcap_t *pc = pcap_open_dead(DLT_EN10MB, 65535); pcap_info_t *pcap = l2p_get_pcap(pinfo->pid); pcap->pcap_result = pcap_compile(pc, &pcap->pcap_program, str, 1, PCAP_NETMASK_UNKNOWN); pcap_close(pc); } /** * * debug_blink - Enable or disable a port from blinking. * * DESCRIPTION * Enable or disable the given ports from blinking. * * RETURNS: N/A * * SEE ALSO: */ void debug_blink(port_info_t *pinfo, uint32_t state) { if (state == ENABLE_STATE) pktgen.blinklist |= (1 << pinfo->pid); else { pktgen.blinklist &= ~(1 << pinfo->pid); rte_eth_led_on(pinfo->pid); } } /** * * enable_process - Enable or disable input packet processing. * * DESCRIPTION * Enable or disable input packet processing of ICMP, ARP, ... * * RETURNS: N/A * * SEE ALSO: */ void enable_process(port_info_t *pinfo, int state) { if (state == ENABLE_STATE) pktgen_set_port_flags(pinfo, PROCESS_INPUT_PKTS); else pktgen_clr_port_flags(pinfo, PROCESS_INPUT_PKTS); } /** * * enable_capture - Enable or disable capture packet processing. * * DESCRIPTION * Enable or disable capture packet processing of ICMP, ARP, ... * * RETURNS: N/A * * SEE ALSO: */ void enable_capture(port_info_t *pinfo, uint32_t state) { pktgen_set_capture(pinfo, state); } #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) /** * * enable_bonding - Enable or disable bonding TX zero packet processing. * * DESCRIPTION * Enable or disable calling TX with zero packets for bonding PMD * * RETURNS: N/A * * SEE ALSO: */ void enable_bonding(port_info_t *pinfo, uint32_t state) { struct rte_eth_bond_8023ad_conf conf; uint16_t workers[RTE_MAX_ETHPORTS]; uint16_t active_workers[RTE_MAX_ETHPORTS]; int i, num_workers, num_active_workers; if (rte_eth_bond_8023ad_conf_get(pinfo->pid, &conf) < 0) { printf("Port %d is not a bonding port\n", pinfo->pid); return; } num_workers = rte_eth_bond_members_get(pinfo->pid, workers, RTE_MAX_ETHPORTS); if (num_workers < 0) { printf("Failed to get worker list for port = %d\n", pinfo->pid); return; } num_active_workers = rte_eth_bond_active_members_get(pinfo->pid, active_workers, RTE_MAX_ETHPORTS); if (num_active_workers < 0) { printf("Failed to get active worker list for port = %d\n", pinfo->pid); return; } printf("Port %d:\n", pinfo->pid); for (i = 0; i < num_workers; i++) { if (state == ENABLE_STATE) { pktgen_set_port_flags(pinfo, BONDING_TX_PACKETS); rte_eth_bond_8023ad_ext_distrib(pinfo->pid, workers[i], 1); printf(" Enable worker %u 802.3ad distributing\n", workers[i]); rte_eth_bond_8023ad_ext_collect(pinfo->pid, workers[i], 1); printf(" Enable worker %u 802.3ad collecting\n", workers[i]); } else { pktgen_clr_port_flags(pinfo, BONDING_TX_PACKETS); rte_eth_bond_8023ad_ext_distrib(pinfo->pid, workers[i], 0); printf(" Disable worker %u 802.3ad distributing\n", workers[i]); rte_eth_bond_8023ad_ext_collect(pinfo->pid, workers[i], 1); printf(" Enable worker %u 802.3ad collecting\n", workers[i]); } } } static void show_states(uint8_t state) { const char *states[] = {"LACP_Active", "LACP_Short_timeout", "Aggregation", "Synchronization", "Collecting", "Distributing", "Defaulted", "Expired", NULL}; int j; for (j = 0; states[j]; j++) { if (state & (1 << j)) printf("%s ", states[j]); } } void show_bonding_mode(port_info_t *pinfo) { int bonding_mode, agg_mode; uint16_t workers[RTE_MAX_ETHPORTS]; int num_workers, num_active_workers; int primary_id; int i; uint16_t port_id = pinfo->pid; /* Display the bonding mode.*/ bonding_mode = rte_eth_bond_mode_get(port_id); if (bonding_mode < 0) { printf("Failed to get bonding mode for port = %d\n", port_id); return; } else printf("\tBonding mode: %d, ", bonding_mode); if (bonding_mode == BONDING_MODE_BALANCE) { int balance_xmit_policy; balance_xmit_policy = rte_eth_bond_xmit_policy_get(port_id); if (balance_xmit_policy < 0) { printf("\nFailed to get balance xmit policy for port = %d\n", port_id); return; } else { printf("Balance Xmit Policy: "); switch (balance_xmit_policy) { case BALANCE_XMIT_POLICY_LAYER2: printf("BALANCE_XMIT_POLICY_LAYER2"); break; case BALANCE_XMIT_POLICY_LAYER23: printf("BALANCE_XMIT_POLICY_LAYER23"); break; case BALANCE_XMIT_POLICY_LAYER34: printf("BALANCE_XMIT_POLICY_LAYER34"); break; } printf(", "); } } if (bonding_mode == BONDING_MODE_8023AD) { agg_mode = rte_eth_bond_8023ad_agg_selection_get(port_id); printf("IEEE802.3AD Aggregator Mode: "); switch (agg_mode) { case AGG_BANDWIDTH: printf("bandwidth"); break; case AGG_STABLE: printf("stable"); break; case AGG_COUNT: printf("count"); break; } printf("\n"); } num_workers = rte_eth_bond_members_get(port_id, workers, RTE_MAX_ETHPORTS); if (num_workers < 0) { printf("\tFailed to get worker list for port = %d\n", port_id); return; } if (num_workers > 0) { printf("\tSlaves (%d): [", num_workers); for (i = 0; i < num_workers - 1; i++) printf("%d ", workers[i]); printf("%d]\n", workers[num_workers - 1]); } else { printf("\tSlaves: []\n"); } num_active_workers = rte_eth_bond_active_members_get(port_id, workers, RTE_MAX_ETHPORTS); if (num_active_workers < 0) { printf("\tFailed to get active worker list for port = %d\n", port_id); return; } if (num_active_workers > 0) { printf("\tActive Slaves (%d): [", num_active_workers); for (i = 0; i < num_active_workers - 1; i++) printf("%d ", workers[i]); printf("%d]\n", workers[num_active_workers - 1]); } else { printf("\tActive Slaves: []\n"); } for (i = 0; i < num_active_workers; i++) { struct rte_eth_bond_8023ad_member_info conf; printf("\t\tSlave %u\n", workers[i]); rte_eth_bond_8023ad_member_info(pinfo->pid, workers[i], &conf); printf("\t\t %sSelected\n\t\t Actor States ( ", conf.selected ? "" : "Not "); show_states(conf.actor_state); printf(")\n\t\t Partner States( "); show_states(conf.partner_state); printf(")\n\t\t AGG Port %u\n", conf.agg_port_id); } primary_id = rte_eth_bond_primary_get(port_id); if (primary_id < 0) { printf("\tFailed to get primary worker for port = %d\n", port_id); return; } else printf("\tPrimary: [%d]\n", primary_id); } #endif /** * * range_set_pkt_type - Set the packet type value for range packets. * * DESCRIPTION * Set the packet type value for the given port list. * * RETURNS: N/A * * SEE ALSO: */ void range_set_pkt_type(port_info_t *pinfo, const char *type) { pinfo->seq_pkt[RANGE_PKT].ethType = (type[0] == 'a') ? RTE_ETHER_TYPE_ARP : (type[3] == '4') ? RTE_ETHER_TYPE_IPV4 : (type[3] == '6') ? RTE_ETHER_TYPE_IPV6 : RTE_ETHER_TYPE_IPV4; if (pinfo->seq_pkt[RANGE_PKT].ethType == RTE_ETHER_TYPE_IPV6) { if (pinfo->range.pkt_size < MIN_v6_PKT_SIZE) pinfo->range.pkt_size = MIN_v6_PKT_SIZE; if (pinfo->range.pkt_size_min < MIN_v6_PKT_SIZE) pinfo->range.pkt_size_min = MIN_v6_PKT_SIZE; if (pinfo->range.pkt_size_max < MIN_v6_PKT_SIZE) pinfo->range.pkt_size_max = MIN_v6_PKT_SIZE; } } /** * * single_set_pkt_type - Set the packet type value. * * DESCRIPTION * Set the packet type value for the given port list. * * RETURNS: N/A * * SEE ALSO: */ void single_set_pkt_type(port_info_t *pinfo, const char *type) { pkt_seq_t *pkt = &pinfo->seq_pkt[SINGLE_PKT]; uint16_t ethtype = pkt->ethType; /* handle ['arp' | 'a'] or ['ipv4' | 'ipv6'] or ['ip4' | 'ip6'] */ pkt->ethType = (type[0] == 'a') ? RTE_ETHER_TYPE_ARP : (type[3] == '4') ? RTE_ETHER_TYPE_IPV4 : (type[3] == '6') ? RTE_ETHER_TYPE_IPV6 : (type[2] == '4') ? RTE_ETHER_TYPE_IPV4 : (type[2] == '6') ? RTE_ETHER_TYPE_IPV6 : RTE_ETHER_TYPE_IPV4; if ((ethtype == RTE_ETHER_TYPE_IPV6) && (pkt->ethType == RTE_ETHER_TYPE_IPV4)) { if (pkt->pkt_size >= (MIN_v6_PKT_SIZE - RTE_ETHER_CRC_LEN)) pkt->pkt_size = RTE_ETHER_MIN_LEN + (pkt->pkt_size - (MIN_v6_PKT_SIZE - RTE_ETHER_CRC_LEN)); } if ((ethtype == RTE_ETHER_TYPE_IPV4) && (pkt->ethType == RTE_ETHER_TYPE_IPV6)) { if (pkt->pkt_size < (MIN_v6_PKT_SIZE - RTE_ETHER_CRC_LEN)) pkt->pkt_size = MIN_v6_PKT_SIZE + (pkt->pkt_size - (RTE_ETHER_MIN_LEN - RTE_ETHER_CRC_LEN)); } pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * enable_vxlan - Set the port to send a VxLAN ID * * DESCRIPTION * Set the given port list to send VxLAN ID packets. * * RETURNS: N/A * * SEE ALSO: */ void enable_vxlan(port_info_t *pinfo, uint32_t onOff) { if (onOff == ENABLE_STATE) { pktgen_clr_port_flags(pinfo, EXCLUSIVE_PKT_MODES); pktgen_set_port_flags(pinfo, SEND_VXLAN_PACKETS); } else pktgen_clr_port_flags(pinfo, SEND_VXLAN_PACKETS); pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * enable_vlan - Set the port to send a VLAN ID * * DESCRIPTION * Set the given port list to send VLAN ID packets. * * RETURNS: N/A * * SEE ALSO: */ void enable_vlan(port_info_t *pinfo, uint32_t onOff) { if (onOff == ENABLE_STATE) { pktgen_clr_port_flags(pinfo, EXCLUSIVE_PKT_MODES); pktgen_set_port_flags(pinfo, SEND_VLAN_ID | SEND_SINGLE_PKTS); } else pktgen_clr_port_flags(pinfo, SEND_VLAN_ID); } /** * * single_set_vlan_id - Set the port VLAN ID value * * DESCRIPTION * Set the given port list with the given VLAN ID. * * RETURNS: N/A * * SEE ALSO: */ void single_set_vlan_id(port_info_t *pinfo, uint16_t vlanid) { pinfo->vlanid = vlanid; pinfo->seq_pkt[SINGLE_PKT].vlanid = pinfo->vlanid; pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * single_set_prio - Set the port 802.1p cos value * * DESCRIPTION * Set the given port list with the given 802.1p cos * * RETURNS: N/A * * SEE ALSO: */ void single_set_cos(port_info_t *pinfo, uint8_t cos) { pinfo->cos = cos; pinfo->seq_pkt[SINGLE_PKT].cos = pinfo->cos; pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * single_set_tos - Set the port tos value * * DESCRIPTION * Set the given port list with the given tos * * RETURNS: N/A * * SEE ALSO: */ void single_set_tos(port_info_t *pinfo, uint8_t tos) { pinfo->tos = tos; pinfo->seq_pkt[SINGLE_PKT].tos = pinfo->tos; pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * single_set_vxlan - Set the port vxlan value * * DESCRIPTION * Set the given port list with the given vxlan * * RETURNS: N/A * * SEE ALSO: */ void single_set_vxlan(port_info_t *pinfo, uint16_t flags, uint16_t group_id, uint32_t vxlan_id) { pinfo->vni_flags = flags; pinfo->group_id = group_id; pinfo->vxlan_id = vxlan_id; pinfo->seq_pkt[SINGLE_PKT].vni_flags = pinfo->vni_flags; pinfo->seq_pkt[SINGLE_PKT].group_id = pinfo->group_id; pinfo->seq_pkt[SINGLE_PKT].vxlan_id = pinfo->vxlan_id; pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * single_set_latsamp_params - Set the port latency sampler parameters * * DESCRIPTION * Set the given port list with the given latency sampler parameters * * RETURNS: N/A * * SEE ALSO: */ void single_set_latsampler_params(port_info_t *pinfo, char *type, uint32_t num_samples, uint32_t sampling_rate, char outfile[]) { FILE *fp = NULL; uint32_t sampler_type; /* Stop if latency sampler is running */ if (pktgen_tst_port_flags(pinfo, SAMPLING_LATENCIES)) { pktgen_log_warning("Latency sampler is already running, stop it first!"); return; } /* Validate sampler type*/ if (!strcasecmp(type, "simple")) sampler_type = LATSAMPLER_SIMPLE; else if (!strcasecmp(type, "poisson")) sampler_type = LATSAMPLER_POISSON; else { pktgen_log_error("Unknown latsampler type %s! Valid values: simple, poisson", type); return; } /* Validate file path */ fp = fopen(outfile, "w+"); if (fp == NULL) { pktgen_log_error("Cannot write to file path %s!", outfile); return; } fclose(fp); if (num_samples > MAX_LATENCY_ENTRIES) { pktgen_log_error("Too many samples requested. Max %d!", MAX_LATENCY_ENTRIES); return; } pinfo->latsamp_type = sampler_type; pinfo->latsamp_rate = sampling_rate; pinfo->latsamp_num_samples = num_samples; strcpy(pinfo->latsamp_outfile, outfile); pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * enable_mpls - Set the port to send a mpls ID * * DESCRIPTION * Set the given port list to send mpls ID packets. * * RETURNS: N/A * * SEE ALSO: */ void enable_mpls(port_info_t *pinfo, uint32_t onOff) { if (onOff == ENABLE_STATE) { pktgen_clr_port_flags(pinfo, EXCLUSIVE_PKT_MODES); pktgen_set_port_flags(pinfo, SEND_MPLS_LABEL | SEND_SINGLE_PKTS); } else pktgen_clr_port_flags(pinfo, SEND_MPLS_LABEL); pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * range_set_mpls_entry - Set the port MPLS entry value * * DESCRIPTION * Set the given port list with the given MPLS entry. * * RETURNS: N/A * * SEE ALSO: */ void range_set_mpls_entry(port_info_t *pinfo, uint32_t mpls_entry) { pinfo->mpls_entry = mpls_entry; pinfo->seq_pkt[SINGLE_PKT].mpls_entry = pinfo->mpls_entry; pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * enable_qinq - Set the port to send a Q-in-Q header * * DESCRIPTION * Set the given port list to send Q-in-Q ID packets. * * RETURNS: N/A * * SEE ALSO: */ void enable_qinq(port_info_t *pinfo, uint32_t onOff) { if (onOff == ENABLE_STATE) { pktgen_clr_port_flags(pinfo, EXCLUSIVE_MODES); pktgen_clr_port_flags(pinfo, EXCLUSIVE_PKT_MODES); pktgen_set_port_flags(pinfo, SEND_Q_IN_Q_IDS | SEND_SINGLE_PKTS); } else pktgen_clr_port_flags(pinfo, SEND_Q_IN_Q_IDS); pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * single_set_qinqids - Set the port Q-in-Q ID values * * DESCRIPTION * Set the given port list with the given Q-in-Q ID's. * * RETURNS: N/A * * SEE ALSO: */ void single_set_qinqids(port_info_t *pinfo, uint16_t outerid, uint16_t innerid) { pinfo->seq_pkt[SINGLE_PKT].qinq_outerid = outerid; pinfo->seq_pkt[SINGLE_PKT].qinq_innerid = innerid; pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * range_set_qinqids - Set the port Q-in-Q ID values * * DESCRIPTION * Set the given port list with the given Q-in-Q ID's. * * RETURNS: N/A * * SEE ALSO: */ void range_set_qinqids(port_info_t *pinfo, uint16_t outerid, uint16_t innerid) { pinfo->seq_pkt[RANGE_PKT].qinq_outerid = outerid; pinfo->seq_pkt[RANGE_PKT].qinq_innerid = innerid; pktgen_packet_ctor(pinfo, RANGE_PKT, -1); } /** * * enable_gre - Set the port to send GRE with IPv4 payload * * DESCRIPTION * Set the given port list to send GRE with IPv4 payload * * RETURNS: N/A * * SEE ALSO: */ void enable_gre(port_info_t *pinfo, uint32_t onOff) { if (onOff == ENABLE_STATE) { pktgen_clr_port_flags(pinfo, EXCLUSIVE_PKT_MODES); pktgen_set_port_flags(pinfo, SEND_GRE_IPv4_HEADER | SEND_SINGLE_PKTS); } else pktgen_clr_port_flags(pinfo, SEND_GRE_IPv4_HEADER); pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * pktgen_set_gre_eth - Set the port to send GRE with Ethernet payload * * DESCRIPTION * Set the given port list to send GRE with Ethernet payload * * RETURNS: N/A * * SEE ALSO: */ void enable_gre_eth(port_info_t *pinfo, uint32_t onOff) { if (onOff == ENABLE_STATE) { pktgen_clr_port_flags(pinfo, EXCLUSIVE_PKT_MODES); pktgen_set_port_flags(pinfo, SEND_GRE_ETHER_HEADER | SEND_SINGLE_PKTS); } else pktgen_clr_port_flags(pinfo, SEND_GRE_ETHER_HEADER); pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * range_set_gre_key - Set the port GRE key * * DESCRIPTION * Set the given port list with the given GRE key. * * RETURNS: N/A * * SEE ALSO: */ void range_set_gre_key(port_info_t *pinfo, uint32_t gre_key) { pinfo->gre_key = gre_key; pinfo->seq_pkt[SINGLE_PKT].gre_key = pinfo->gre_key; pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * pktgen_clear_stats - Clear a given port list of stats. * * DESCRIPTION * Clear the given port list of all statistics. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_clear_stats(port_info_t *pinfo) { /* curr_stats are reset each time the stats are read */ memset(&pinfo->stats, 0, sizeof(port_stats_t)); struct rte_eth_stats *base = &pinfo->stats.base; /* Normalize the stats to a zero base line */ rte_eth_stats_get(pinfo->pid, base); pktgen.max_total_ipackets = 0; pktgen.max_total_opackets = 0; latency_t *lat = &pinfo->latency; memset(lat->stats, 0, (lat->end_stats - lat->stats) * sizeof(uint64_t)); memset(&pktgen.cumm_rate_totals, 0, sizeof(struct rte_eth_stats)); } /** * * pktgen_port_defaults - Set all ports back to the default values. * * DESCRIPTION * Reset the ports back to the defaults. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_port_defaults(uint16_t pid) { port_info_t *pinfo = l2p_get_port_pinfo(pid); rte_atomic64_set(&pinfo->transmit_count, DEFAULT_TX_COUNT); rte_atomic64_init(&pinfo->current_tx_count); pinfo->tx_rate = DEFAULT_TX_RATE; pinfo->tx_burst = DEFAULT_PKT_TX_BURST; pinfo->rx_burst = DEFAULT_PKT_RX_BURST; pinfo->vlanid = DEFAULT_VLAN_ID; pinfo->cos = DEFAULT_COS; pinfo->tos = DEFAULT_TOS; pinfo->seqCnt = 0; pinfo->seqIdx = 0; pinfo->prime_cnt = DEFAULT_PRIME_COUNT; if (rte_eth_macaddr_get(pid, &pinfo->src_mac) < 0) pktgen_log_panic("Can't get MAC address: port=%u", pid); pktgen.flags |= PRINT_LABELS_FLAG; } /** * * pktgen_seq_defaults - Set all ports back to the default values. * * DESCRIPTION * Reset the ports back to the defaults. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_seq_defaults(uint16_t pid) { port_info_t *pi, *pinfo = l2p_get_port_pinfo(pid); pkt_seq_t *pkt = NULL; pktgen_log_info(" Setting sequence defaults for port %u", pid); /* Setup the port and packet defaults */ for (uint8_t s = 0; s < NUM_TOTAL_PKTS; s++) { pkt = &pinfo->seq_pkt[s]; pkt->pkt_size = (RTE_ETHER_MIN_LEN - RTE_ETHER_CRC_LEN); pkt->sport = DEFAULT_SRC_PORT; pkt->dport = DEFAULT_DST_PORT; pkt->ttl = DEFAULT_TTL; pkt->ipProto = PG_IPPROTO_TCP; pkt->ethType = RTE_ETHER_TYPE_IPV4; pkt->vlanid = DEFAULT_VLAN_ID; pkt->cos = DEFAULT_COS; pkt->tos = DEFAULT_TOS; pkt->tcp_flags = DEFAULT_TCP_FLAGS; pkt->ip_mask = DEFAULT_NETMASK; if (pktgen.nb_ports > 1) { if ((pid & 1) == 0) { pkt->ip_src_addr.addr.ipv4.s_addr = DEFAULT_IP_ADDR | (pid << 8) | 1; pkt->ip_dst_addr.addr.ipv4.s_addr = DEFAULT_IP_ADDR | ((pid + 1) << 8) | 1; } else { pkt->ip_src_addr.addr.ipv4.s_addr = DEFAULT_IP_ADDR | (pid << 8) | 1; pkt->ip_dst_addr.addr.ipv4.s_addr = DEFAULT_IP_ADDR | ((pid - 1) << 8) | 1; } } else { pkt->ip_src_addr.addr.ipv4.s_addr = DEFAULT_IP_ADDR | (pid << 8) | 1; pkt->ip_dst_addr.addr.ipv4.s_addr = DEFAULT_IP_ADDR | ((pid + 1) << 8) | 1; } rte_ether_addr_copy(&pinfo->src_mac, &pkt->eth_src_addr); if (pid < (pktgen.nb_ports - 1) && (pid & 1) == 0) { pi = l2p_get_port_pinfo(pid + 1); rte_ether_addr_copy(&pi->src_mac, &pkt->eth_dst_addr); } else if (pid > 0 && (pid & 1) == 1) { pi = l2p_get_port_pinfo(pid - 1); rte_ether_addr_copy(&pi->src_mac, &pkt->eth_dst_addr); } pktgen_packet_ctor(pinfo, s, -1); } } /** * * pktgen_ping4 - Send a IPv4 ICMP echo request. * * DESCRIPTION * Send a IPv4 ICMP echo request packet. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_ping4(port_info_t *pinfo) { memcpy(&pinfo->seq_pkt[SPECIAL_PKT], &pinfo->seq_pkt[SINGLE_PKT], sizeof(pkt_seq_t)); pinfo->seq_pkt[SPECIAL_PKT].ipProto = PG_IPPROTO_ICMP; pktgen_packet_ctor(pinfo, SPECIAL_PKT, ICMP4_ECHO); pktgen_set_port_flags(pinfo, SEND_PING4_REQUEST); } #ifdef INCLUDE_PING6 /** * * pktgen_ping6 - Send a IPv6 ICMP echo request packet. * * DESCRIPTION * Send a IPv6 ICMP echo request packet for the given ports. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_ping6(port_info_t *pinfo) { memcpy(&pinfo->pkt[PING_PKT], &pinfo->pkt[SINGLE_PKT], sizeof(pkt_seq_t)); pinfo->pkt[PING_PKT].ipProto = PG_IPPROTO_ICMP; pktgen_packet_ctor(pinfo, PING_PKT, ICMP6_ECHO); pktgen_set_port_flags(pinfo, SEND_PING6_REQUEST); } #endif /** * * pktgen_reset - Reset all ports to the default state * * DESCRIPTION * Reset all ports to the default state. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_reset(port_info_t *pinfo) { char off[8]; if (pinfo == NULL) rte_exit(EXIT_FAILURE, "No port_info_t pointer specified\n"); pktgen_log_info("Reset port %u configuration to default", pinfo->pid); strcpy(off, "off"); pktgen_stop_transmitting(pinfo); pktgen.flags &= ~MAC_FROM_ARP_FLAG; /* Make sure the port is active and enabled. */ if (pinfo->seq_pkt) { pktgen_port_defaults(pinfo->pid); pktgen_seq_defaults(pinfo->pid); pktgen_range_setup(pinfo); pktgen_clear_stats(pinfo); enable_range(pinfo, estate(off)); enable_latency(pinfo, estate(off)); memset(pinfo->rnd_bitfields, 0, sizeof(struct rnd_bits_s)); pktgen_rnd_bits_init(&pinfo->rnd_bitfields); pktgen_set_port_seqCnt(pinfo, 0); } else pktgen_log_info("No sequence packets allocated for port %u", pinfo->pid); pktgen_update_display(); } /** * * pktgen_port_restart - Attempt to cleanup port state. * * DESCRIPTION * Reset all ports * * RETURNS: N/A * * SEE ALSO: */ void pktgen_port_restart(port_info_t *pinfo) { if (pinfo == NULL) pinfo = l2p_get_port_pinfo(0); printf("Port %d attempt to stop/start PMD\n", pinfo->pid); pktgen_set_receive_state(pinfo, 1); pktgen_stop_transmitting(pinfo); rte_delay_us_sleep(10 * 1000); /* Stop and start the device to flush TX and RX buffers from the device rings. */ if (rte_eth_dev_stop(pinfo->pid) < 0) printf("Unable to stop device %d\n", pinfo->pid); rte_delay_us_sleep(250); if (rte_eth_dev_start(pinfo->pid) < 0) printf("Unable to start device %d\n", pinfo->pid); pktgen_set_receive_state(pinfo, 0); pktgen_update_display(); } /** * * single_set_tx_count - Set the number of packets to transmit on a port. * * DESCRIPTION * Set the transmit count for all ports in the list. * * RETURNS: N/A * * SEE ALSO: */ void single_set_tx_count(port_info_t *pinfo, uint32_t cnt) { rte_atomic64_set(&pinfo->transmit_count, cnt); } /** * * pktgen_set_port_seqCnt - Set the sequence count for a port * * DESCRIPTION * Set a sequence count of packets for all ports in the list. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_set_port_seqCnt(port_info_t *pinfo, uint32_t cnt) { if (cnt > NUM_SEQ_PKTS) cnt = NUM_SEQ_PKTS; pinfo->seqCnt = cnt; if (cnt) { pktgen_clr_port_flags(pinfo, EXCLUSIVE_MODES); pktgen_set_port_flags(pinfo, SEND_SEQ_PKTS); } else { pktgen_clr_port_flags(pinfo, EXCLUSIVE_MODES); pktgen_set_port_flags(pinfo, SEND_SINGLE_PKTS); } } /** * * pktgen_set_port_prime - Set the number of packets to send on a prime command * * DESCRIPTION * Set the number packets to send on the prime command for all ports in list. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_set_port_prime(port_info_t *pinfo, uint32_t cnt) { if (cnt > MAX_PRIME_COUNT) cnt = MAX_PRIME_COUNT; else if (cnt == 0) cnt = DEFAULT_PRIME_COUNT; pinfo->prime_cnt = cnt; } /** * * pktgen_set_port_dump - Set the number of received packets to dump to screen. * * DESCRIPTION * Set the number of received packets to dump to screen. * * RETURNS: N/A * * SEE ALSO: */ void debug_set_port_dump(port_info_t *pinfo, uint32_t cnt) { int i; if (cnt > MAX_DUMP_PACKETS) cnt = MAX_DUMP_PACKETS; /* Prevent concurrency issues by setting the fields in this specific order */ pinfo->dump_count = 0; pinfo->dump_tail = 0; pinfo->dump_head = 0; for (i = 0; i < MAX_DUMP_PACKETS; ++i) if (pinfo->dump_list->data != NULL) { rte_free(pinfo->dump_list->data); pinfo->dump_list->data = NULL; } pinfo->dump_count = cnt; } /** * * single_set_tx_burst - Set the transmit burst count. * * DESCRIPTION * Set the transmit burst count for all packets. * * RETURNS: N/A * * SEE ALSO: */ void single_set_tx_burst(port_info_t *pinfo, uint32_t burst) { if (burst == 0) burst = 1; else if (burst > MAX_PKT_TX_BURST) burst = MAX_PKT_TX_BURST; pinfo->tx_burst = burst; pinfo->tx_cycles = 0; pktgen_packet_rate(pinfo); } /** * * single_set_rx_burst - Set the receive burst count. * * DESCRIPTION * Set the receive burst count for all packets. * * RETURNS: N/A * * SEE ALSO: */ void single_set_rx_burst(port_info_t *pinfo, uint32_t burst) { if (burst == 0) burst = 1; else if (burst > MAX_PKT_RX_BURST) burst = MAX_PKT_RX_BURST; pinfo->rx_burst = burst; pktgen_packet_rate(pinfo); } /** * * debug_set_tx_cycles - Set the number of Transmit cycles to use. * * DESCRIPTION * Set the number of transmit cycles for the given port list. * * RETURNS: N/A * * SEE ALSO: */ void debug_set_tx_cycles(port_info_t *pinfo, uint32_t cycles) { pinfo->tx_cycles = cycles; } /** * * single_set_pkt_size - Set the size of the packets to send. * * DESCRIPTION * Set the pkt size for the single packet transmit. * * RETURNS: N/A * * SEE ALSO: */ void single_set_pkt_size(port_info_t *pinfo, uint16_t size) { pkt_seq_t *pkt = &pinfo->seq_pkt[SINGLE_PKT]; if (size < RTE_ETHER_MIN_LEN) size = RTE_ETHER_MIN_LEN; if ((pktgen.flags & JUMBO_PKTS_FLAG) && size > RTE_ETHER_MAX_JUMBO_FRAME_LEN) size = RTE_ETHER_MAX_JUMBO_FRAME_LEN; else if (!(pktgen.flags & JUMBO_PKTS_FLAG) && size > RTE_ETHER_MAX_LEN) size = RTE_ETHER_MAX_LEN; if ((pkt->ethType == RTE_ETHER_TYPE_IPV6) && (size < MIN_v6_PKT_SIZE)) size = MIN_v6_PKT_SIZE; pkt->pkt_size = (size - RTE_ETHER_CRC_LEN); pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); pktgen_packet_rate(pinfo); } /** * * single_set_port_value - Set the port value for single or sequence packets. * * DESCRIPTION * Set the port value for single or sequence packets for the ports listed. * * RETURNS: N/A * * SEE ALSO: */ void single_set_port_value(port_info_t *pinfo, char type, uint32_t portValue) { if (type == 'd') pinfo->seq_pkt[SINGLE_PKT].dport = (uint16_t)portValue; else pinfo->seq_pkt[SINGLE_PKT].sport = (uint16_t)portValue; pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * single_set_tx_rate - Set the transmit rate as a percent value. * * DESCRIPTION * Set the transmit rate as a percent value for all ports listed. * * RETURNS: N/A * * SEE ALSO: */ void single_set_tx_rate(port_info_t *pinfo, const char *r) { double rate = strtod(r, NULL); if (rate == 0) rate = 0.01; else if (rate > 100.00) rate = 100.00; pinfo->tx_rate = rate; pinfo->tx_cycles = 0; pktgen_packet_rate(pinfo); } /** * * single_set_ipaddr - Set the IP address for all ports listed * * DESCRIPTION * Set an IP address for all ports listed in the call. * * RETURNS: N/A * * SEE ALSO: */ void single_set_ipaddr(port_info_t *pinfo, char type, struct pg_ipaddr *ip, int ip_ver) { pkt_seq_t *pkt = &pinfo->seq_pkt[SINGLE_PKT]; if (ip_ver == 4) { if (type == 's') { pkt->ip_mask = size_to_mask(ip->prefixlen); pkt->ip_src_addr.addr.ipv4.s_addr = ntohl(ip->ipv4.s_addr); } else if (type == 'd') pkt->ip_dst_addr.addr.ipv4.s_addr = ntohl(ip->ipv4.s_addr); else return; if (pkt->ethType != RTE_ETHER_TYPE_IPV4 && pkt->ethType != RTE_ETHER_TYPE_ARP) single_set_pkt_type(pinfo, "ipv4"); } else if (ip_ver == 6) { if (type == 's') { pkt->ip_src_addr.prefixlen = ip->prefixlen; rte_memcpy(&pkt->ip_src_addr.addr.ipv6, &ip->ipv6, sizeof(struct rte_ipv6_addr)); } else if (type == 'd') rte_memcpy(&pkt->ip_dst_addr.addr.ipv6, &ip->ipv6, sizeof(struct rte_ipv6_addr)); else return; if (pkt->ethType != RTE_ETHER_TYPE_IPV6) single_set_pkt_type(pinfo, "ipv6"); } pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } uint16_t tcp_flags_from_str(const char *str) { tcp_flags_t flag_list[] = TCP_FLAGS_LIST; uint16_t flags = 0; char flags_str[128]; char *fields[16]; int num_fields; memset(flags_str, 0, sizeof(flags_str)); strncpy(flags_str, str, sizeof(flags_str) - 1); num_fields = rte_strsplit(flags_str, strlen(flags_str), fields, RTE_DIM(fields), ','); for (int i = 0; i < num_fields; i++) { if (!strcmp(fields[i], "clr")) { flags = 0; continue; } for (tcp_flags_t *flag = flag_list; flag->name; flag++) { if (!strcmp(fields[i], flag->name)) { flags |= flag->bit; break; } } } return flags; } int tcp_str_from_flags(uint16_t flags, char *buf, size_t len) { tcp_flags_t flag_list[] = TCP_FLAGS_LIST; char *str = NULL; int n = 0; if (buf == NULL || len < 4) return -1; memset(buf, 0, len); if ((flags & TCP_FLAGS_MASK) == 0) { strcpy(buf, "clr"); return 0; } for (tcp_flags_t *flag = flag_list; flag->name; flag++) { if (flags & flag->bit) { if (str) { if (n + strlen(flag->name) + 1 >= len) return -1; n = snprintf(str, len, ",%s", flag->name); } else { str = buf; if (n + strlen(flag->name) + 1 >= len) return -1; n = snprintf(str, len, "%s", flag->name); } str += n; len -= n; } } return 0; } /** * * single_set_tcp_flags - Set a TCP flag * * DESCRIPTION * Set a TCP flag for the single packet. * * RETURNS: N/A * * SEE ALSO: */ void single_set_tcp_flags(port_info_t *pinfo, const char *flags) { pinfo->seq_pkt[SINGLE_PKT].tcp_flags = tcp_flags_from_str(flags); } /** * * range_set_tcp_flags - Set a TCP flag * * DESCRIPTION * Set a TCP flag for the range packet. * * RETURNS: N/A * * SEE ALSO: */ void range_set_tcp_flags(port_info_t *pinfo, const char *flags) { pinfo->range.tcp_flags = tcp_flags_from_str(flags); pinfo->seq_pkt[RANGE_PKT].tcp_flags = pinfo->range.tcp_flags; } /** * * seq_set_tcp_flags - Set a TCP flag * * DESCRIPTION * Set a TCP flag for the single packet. * * RETURNS: N/A * * SEE ALSO: */ void seq_set_tcp_flags(port_info_t *pinfo, uint32_t seqnum, const char *flags) { pinfo->seq_pkt[seqnum].tcp_flags = tcp_flags_from_str(flags); } /** * * single_set_tcp_seq - Set TCP sequence number * * DESCRIPTION * Set TCP sequence number * * RETURNS: N/A * * SEE ALSO: */ void single_set_tcp_seq(port_info_t *pinfo, uint32_t seq) { pinfo->seq_pkt[SINGLE_PKT].tcp_seq = seq; } /** * * single_set_tcp_ack - Set TCP acknowledge number * * DESCRIPTION * Set TCP acknowledge number * * RETURNS: N/A * * SEE ALSO: */ void single_set_tcp_ack(port_info_t *pinfo, uint32_t ack) { pinfo->seq_pkt[SINGLE_PKT].tcp_ack = ack; } /** * * range_set_tcp_seq - Set TCP sequence numbers * * DESCRIPTION * Set TCP sequence numbers * * RETURNS: N/A * * SEE ALSO: */ void range_set_tcp_seq(port_info_t *pinfo, char *what, uint32_t seq) { char *str = what; if (_cp("inc") || _cp("increment")) pinfo->range.tcp_seq_inc = seq; else if (_cp("min") || _cp("minimum")) pinfo->range.tcp_seq_min = seq; else if (_cp("max") || _cp("maximum")) pinfo->range.tcp_seq_max = seq; else if (_cp("start")) pinfo->range.tcp_seq = seq; } /** * * range_set_tcp_ack - Set TCP acknowledge numbers * * DESCRIPTION * Set TCP acknowledge numbers * * RETURNS: N/A * * SEE ALSO: */ void range_set_tcp_ack(port_info_t *pinfo, char *what, uint32_t ack) { char *str = what; if (_cp("inc") || _cp("increment")) pinfo->range.tcp_ack_inc = ack; else if (_cp("min") || _cp("minimum")) pinfo->range.tcp_ack_min = ack; else if (_cp("max") || _cp("maximum")) pinfo->range.tcp_ack_max = ack; else if (_cp("start")) pinfo->range.tcp_ack = ack; } /** * * single_set_mac - Setup the MAC address * * DESCRIPTION * Set the MAC address for all ports given. * * RETURNS: N/A * * SEE ALSO: */ void single_set_mac(port_info_t *pinfo, const char *which, struct rte_ether_addr *mac) { if (!strcmp(which, "dst")) { rte_ether_addr_copy(mac, &pinfo->seq_pkt[SINGLE_PKT].eth_dst_addr); pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } else if (!strcmp(which, "src")) { rte_ether_addr_copy(mac, &pinfo->seq_pkt[SINGLE_PKT].eth_src_addr); pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } } /** * * single_set_dst_mac - Setup the destination MAC address * * DESCRIPTION * Set the destination MAC address for all ports given. * * RETURNS: N/A * * SEE ALSO: */ void single_set_dst_mac(port_info_t *pinfo, struct rte_ether_addr *mac) { rte_ether_addr_copy(mac, &pinfo->seq_pkt[SINGLE_PKT].eth_dst_addr); pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * single_set_src_mac - Setup the source MAC address * * DESCRIPTION * Set the source MAC address for all ports given. * * RETURNS: N/A * * SEE ALSO: */ void single_set_src_mac(port_info_t *pinfo, struct rte_ether_addr *mac) { rte_ether_addr_copy(mac, &pinfo->seq_pkt[SINGLE_PKT].eth_src_addr); pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * single_set_ttl_ttl - Setup the Time to Live * * DESCRIPTION * Set the TTL for all ports given. * * RETURNS: N/A * * SEE ALSO: */ void single_set_ttl_value(port_info_t *pinfo, uint8_t ttl) { pinfo->seq_pkt[SINGLE_PKT].ttl = ttl; pktgen_packet_ctor(pinfo, SINGLE_PKT, -1); } /** * * enable_range - Enable or disable range packet sending. * * DESCRIPTION * Enable or disable range packet sending. * * RETURNS: N/A * * SEE ALSO: */ void enable_range(port_info_t *pinfo, uint32_t state) { if (state == ENABLE_STATE) { if (pktgen_tst_port_flags(pinfo, SENDING_PACKETS)) { pktgen_log_warning("Cannot enable the range settings while sending packets!"); return; } pktgen_clr_port_flags(pinfo, EXCLUSIVE_MODES); pktgen_set_port_flags(pinfo, SEND_RANGE_PKTS); } else { pktgen_clr_port_flags(pinfo, EXCLUSIVE_MODES); pktgen_set_port_flags(pinfo, SEND_SINGLE_PKTS); } pktgen_packet_rate(pinfo); } /** * * enable_latency - Enable or disable latency testing. * * DESCRIPTION * Enable or disable latency testing. * * RETURNS: N/A * * SEE ALSO: */ void enable_latency(port_info_t *pinfo, uint32_t state) { if (state == ENABLE_STATE) { pktgen_latency_setup(pinfo); pktgen_set_port_flags(pinfo, SEND_LATENCY_PKTS); } else pktgen_clr_port_flags(pinfo, SEND_LATENCY_PKTS); } /** * * single_set_jitter - Set the jitter threshold. * * DESCRIPTION * Set the jitter threshold. * * RETURNS: N/A * * SEE ALSO: */ void single_set_jitter(port_info_t *pinfo, uint64_t threshold) { latency_t *lat = &pinfo->latency; uint64_t us_per_tick; lat->jitter_threshold_us = threshold; lat->jitter_count = 0; us_per_tick = pktgen_get_timer_hz() / 1000000; lat->jitter_threshold_cycles = lat->jitter_threshold_us * us_per_tick; } /** * * pattern_set_type - Set the pattern type per port. * * DESCRIPTION * Set the given pattern type. * * RETURNS: N/A * * SEE ALSO: */ void pattern_set_type(port_info_t *pinfo, char *str) { if (strncmp(str, "abc", 3) == 0) pinfo->fill_pattern_type = ABC_FILL_PATTERN; else if (strncmp(str, "none", 4) == 0) pinfo->fill_pattern_type = NO_FILL_PATTERN; else if (strncmp(str, "user", 4) == 0) pinfo->fill_pattern_type = USER_FILL_PATTERN; else if (strncmp(str, "zero", 4) == 0) pinfo->fill_pattern_type = ZERO_FILL_PATTERN; } /** * * pattern_set_user_pattern - Set the user pattern string. * * DESCRIPTION * Set the given user pattern string. * * RETURNS: N/A * * SEE ALSO: */ void pattern_set_user_pattern(port_info_t *pinfo, char *str) { char copy[USER_PATTERN_SIZE + 1], *cp; memset(copy, 0, sizeof(copy)); strcpy(copy, str); cp = ©[0]; if ((cp[0] == '"') || (cp[0] == '\'')) { cp[strlen(cp) - 1] = 0; cp++; } memset(pinfo->user_pattern, 0, USER_PATTERN_SIZE); snprintf(pinfo->user_pattern, USER_PATTERN_SIZE, "%s", cp); pinfo->fill_pattern_type = USER_FILL_PATTERN; } /** * * range_set_dest_mac - Set the destination MAC address * * DESCRIPTION * Set the destination MAC address for all ports given. * * RETURNS: N/A * * SEE ALSO: */ void range_set_dest_mac(port_info_t *pinfo, const char *what, struct rte_ether_addr *mac) { if (!strcmp(what, "min") || !strcmp(what, "minimum")) inet_mtoh64(mac, &pinfo->range.dst_mac_min); else if (!strcmp(what, "max") || !strcmp(what, "maximum")) inet_mtoh64(mac, &pinfo->range.dst_mac_max); else if (!strcmp(what, "inc") || !strcmp(what, "increment")) inet_mtoh64(mac, &pinfo->range.dst_mac_inc); else if (!strcmp(what, "start")) { inet_mtoh64(mac, &pinfo->range.dst_mac); /* Changes add below to reflect MAC value in range */ rte_ether_addr_copy(mac, &pinfo->seq_pkt[RANGE_PKT].eth_dst_addr); } } /** * * range_set_src_mac - Set the source MAC address for the ports. * * DESCRIPTION * Set the source MAC address for the ports given in the list. * * RETURNS: N/A * * SEE ALSO: */ void range_set_src_mac(port_info_t *pinfo, const char *what, struct rte_ether_addr *mac) { if (!strcmp(what, "min") || !strcmp(what, "minimum")) inet_mtoh64(mac, &pinfo->range.src_mac_min); else if (!strcmp(what, "max") || !strcmp(what, "maximum")) inet_mtoh64(mac, &pinfo->range.src_mac_max); else if (!strcmp(what, "inc") || !strcmp(what, "increment")) inet_mtoh64(mac, &pinfo->range.src_mac_inc); else if (!strcmp(what, "start")) { inet_mtoh64(mac, &pinfo->range.src_mac); /* Changes add below to reflect MAC value in range */ rte_ether_addr_copy(mac, &pinfo->seq_pkt[RANGE_PKT].eth_src_addr); } } /** * * range_set_src_ip - Set the source IP address value. * * DESCRIPTION * Set the source IP address for all of the ports listed. * * RETURNS: N/A * * SEE ALSO: */ void range_set_src_ip(port_info_t *pinfo, char *what, struct pg_ipaddr *ip) { if (pinfo->seq_pkt[RANGE_PKT].ethType == RTE_ETHER_TYPE_IPV6) { if (!strcmp(what, "min") || !strcmp(what, "minimum")) rte_memcpy(pinfo->range.src_ipv6_min, &ip->ipv6, sizeof(struct rte_ipv6_addr)); else if (!strcmp(what, "max") || !strcmp(what, "maximum")) rte_memcpy(pinfo->range.src_ipv6_max, &ip->ipv6, sizeof(struct rte_ipv6_addr)); else if (!strcmp(what, "inc") || !strcmp(what, "increment")) rte_memcpy(pinfo->range.src_ipv6_inc, &ip->ipv6, sizeof(struct rte_ipv6_addr)); else if (!strcmp(what, "start")) rte_memcpy(pinfo->range.src_ipv6, &ip->ipv6, sizeof(struct rte_ipv6_addr)); } else { if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.src_ip_min = ntohl(ip->ipv4.s_addr); else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.src_ip_max = ntohl(ip->ipv4.s_addr); else if (!strcmp(what, "inc") || !strcmp(what, "increment")) pinfo->range.src_ip_inc = ntohl(ip->ipv4.s_addr); else if (!strcmp(what, "start")) pinfo->range.src_ip = ntohl(ip->ipv4.s_addr); } } /** * * range_set_dst_ip - Set the destination IP address values * * DESCRIPTION * Set the destination IP address values. * * RETURNS: N/A * * SEE ALSO: */ void range_set_dst_ip(port_info_t *pinfo, char *what, struct pg_ipaddr *ip) { if (pinfo->seq_pkt[RANGE_PKT].ethType == RTE_ETHER_TYPE_IPV6) { if (!strcmp(what, "min") || !strcmp(what, "minimum")) rte_memcpy(pinfo->range.dst_ipv6_min, &ip->ipv6, sizeof(struct rte_ipv6_addr)); else if (!strcmp(what, "max") || !strcmp(what, "maximum")) rte_memcpy(pinfo->range.dst_ipv6_max, &ip->ipv6, sizeof(struct rte_ipv6_addr)); else if (!strcmp(what, "inc") || !strcmp(what, "increment")) rte_memcpy(pinfo->range.dst_ipv6_inc, &ip->ipv6, sizeof(struct rte_ipv6_addr)); else if (!strcmp(what, "start")) rte_memcpy(pinfo->range.dst_ipv6, &ip->ipv6, sizeof(struct rte_ipv6_addr)); } else { if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.dst_ip_min = ntohl(ip->ipv4.s_addr); else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.dst_ip_max = ntohl(ip->ipv4.s_addr); else if (!strcmp(what, "inc") || !strcmp(what, "increment")) pinfo->range.dst_ip_inc = ntohl(ip->ipv4.s_addr); else if (!strcmp(what, "start")) pinfo->range.dst_ip = ntohl(ip->ipv4.s_addr); } } /** * * range_set_src_port - Set the source IP port number for the ports * * DESCRIPTION * Set the source IP port number for the ports listed. * * RETURNS: N/A * * SEE ALSO: */ void range_set_src_port(port_info_t *pinfo, char *what, uint16_t port) { if (!strcmp(what, "inc") || !strcmp(what, "increment")) { if (port > 64) port = 64; pinfo->range.src_port_inc = port; } else { if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.src_port_min = port; else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.src_port_max = port; else if (!strcmp(what, "start")) pinfo->range.src_port = port; } } /** * * range_set_gtpu_teid - Set the TEID for GTPU header * * DESCRIPTION * Set the GTP-U TEID for the ports listed. * * RETURNS: N/A * * SEE ALSO: */ void range_set_gtpu_teid(port_info_t *pinfo, char *what, uint32_t teid) { if (!strcmp(what, "inc") || !strcmp(what, "increment")) { if (teid != 0) pinfo->range.gtpu_teid_inc = teid; } else { if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.gtpu_teid_min = teid; else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.gtpu_teid_max = teid; else if (!strcmp(what, "start")) { pinfo->range.gtpu_teid = teid; pinfo->seq_pkt[RANGE_PKT].gtpu_teid = teid; } } } /** * * range_set_dst_port - Set the destination port value * * DESCRIPTION * Set the destination port values. * * RETURNS: N/A * * SEE ALSO: */ void range_set_dst_port(port_info_t *pinfo, char *what, uint16_t port) { if (!strcmp(what, "inc") || !strcmp(what, "increment")) { if (port > 64) port = 64; pinfo->range.dst_port_inc = port; } else { if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.dst_port_min = port; else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.dst_port_max = port; else if (!strcmp(what, "start")) pinfo->range.dst_port = port; } } /** * * range_set_ttl - Set the ttl value * * DESCRIPTION * Set the Time to Live values * * RETURNS: N/A * * SEE ALSO: */ void range_set_ttl(port_info_t *pinfo, char *what, uint8_t ttl) { if (!strcmp(what, "inc") || !strcmp(what, "increment")) pinfo->range.ttl_inc = ttl; else if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.ttl_min = ttl; else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.ttl_max = ttl; else if (!strcmp(what, "start")) pinfo->range.ttl = ttl; } /** * * range_set_hop_limits - Set the hop_limits value * * DESCRIPTION * Set the Hop Limits values * * RETURNS: N/A * * SEE ALSO: */ void range_set_hop_limits(port_info_t *pinfo, char *what, uint8_t hop_limits) { if (!strcmp(what, "inc") || !strcmp(what, "increment")) pinfo->range.hop_limits_inc = hop_limits; else if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.hop_limits_min = hop_limits; else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.hop_limits_max = hop_limits; else if (!strcmp(what, "start")) pinfo->range.hop_limits = hop_limits; } /** * * range_set_vlan_id - Set the VLAN id value * * DESCRIPTION * Set the VLAN id values. * * RETURNS: N/A * * SEE ALSO: */ void range_set_vlan_id(port_info_t *pinfo, char *what, uint16_t id) { if (!strcmp(what, "inc") || !strcmp(what, "increment")) { if (id > 64) id = 64; pinfo->range.vlan_id_inc = id; } else { if ((id < MIN_VLAN_ID) || (id > MAX_VLAN_ID)) id = MIN_VLAN_ID; if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.vlan_id_min = id; else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.vlan_id_max = id; else if (!strcmp(what, "start")) pinfo->range.vlan_id = id; } } /** * * range_set_tos_id - Set the tos value * * DESCRIPTION * Set the tos values. * * RETURNS: N/A * * SEE ALSO: */ void range_set_tos_id(port_info_t *pinfo, char *what, uint8_t id) { if (!strcmp(what, "inc") || !strcmp(what, "increment")) { pinfo->range.tos_inc = id; } else { if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.tos_min = id; else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.tos_max = id; else if (!strcmp(what, "start")) pinfo->range.tos = id; } } /** * * range_set_traffic_class - Set the traffic class value * * DESCRIPTION * Set the traffic class value. * * RETURNS: N/A * * SEE ALSO: */ void range_set_traffic_class(port_info_t *pinfo, char *what, uint8_t traffic_class) { if (!strcmp(what, "inc") || !strcmp(what, "increment")) { pinfo->range.traffic_class_inc = traffic_class; } else { if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.traffic_class_min = traffic_class; else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.traffic_class_max = traffic_class; else if (!strcmp(what, "start")) pinfo->range.traffic_class = traffic_class; } } /** * * range_set_cos_id - Set the prio (cos) value * * DESCRIPTION * Set the prio (cos) values. * * RETURNS: N/A * * SEE ALSO: */ void range_set_cos_id(port_info_t *pinfo, char *what, uint8_t id) { if (!strcmp(what, "inc") || !strcmp(what, "increment")) { if (id > 7) id = 7; pinfo->range.cos_inc = id; } else { if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.cos_min = id; else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.cos_max = id; else if (!strcmp(what, "start")) pinfo->range.cos = id; } } /** * * range_set_pkt_size - Set the Packet size value * * DESCRIPTION * Set the packet size values. * * RETURNS: N/A * * SEE ALSO: */ void range_set_pkt_size(port_info_t *pinfo, char *what, uint16_t size) { if (!strcmp(what, "inc") || !strcmp(what, "increment")) { if ((pktgen.flags & JUMBO_PKTS_FLAG) && (size > RTE_ETHER_MAX_JUMBO_FRAME_LEN)) size = RTE_ETHER_MAX_JUMBO_FRAME_LEN; pinfo->range.pkt_size_inc = size; } else { if (size < RTE_ETHER_MIN_LEN) size = RTE_ETHER_MIN_LEN; const uint32_t max_size = (pktgen.flags & JUMBO_PKTS_FLAG) ? RTE_ETHER_MAX_JUMBO_FRAME_LEN : RTE_ETHER_MAX_LEN; if (size > max_size) size = max_size; if (pinfo->seq_pkt[RANGE_PKT].ethType == RTE_ETHER_TYPE_IPV6 && size < MIN_v6_PKT_SIZE) size = MIN_v6_PKT_SIZE; if (!strcmp(what, "start")) pinfo->range.pkt_size = (size - RTE_ETHER_CRC_LEN); else if (!strcmp(what, "min") || !strcmp(what, "minimum")) pinfo->range.pkt_size_min = (size - RTE_ETHER_CRC_LEN); else if (!strcmp(what, "max") || !strcmp(what, "maximum")) pinfo->range.pkt_size_max = (size - RTE_ETHER_CRC_LEN); } } /** * * pktgen_send_arp_requests - Send an ARP request for a given port. * * DESCRIPTION * Using the port list do an ARp send for all ports. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_send_arp_requests(port_info_t *pinfo, uint32_t type) { if (type == GRATUITOUS_ARP) pktgen_set_port_flags(pinfo, SEND_GRATUITOUS_ARP); else pktgen_set_port_flags(pinfo, SEND_ARP_REQUEST); } /** * * pktgen_set_page - Set the page type to display * * DESCRIPTION * Set the page type to display * * RETURNS: N/A * * SEE ALSO: */ void pktgen_set_page(char *str) { uint16_t page = 0; port_info_t *pinfo = NULL; if (str == NULL) return; if ((str[0] >= '0') && (str[0] <= '9')) { page = atoi(str); if (page > pktgen.nb_ports) return; } pinfo = l2p_get_port_pinfo(pktgen.curr_port); pcap_info_t *pcap = l2p_get_pcap(pinfo->pid); /* Switch to the correct page */ if (_cp("next")) { if (pcap) { if ((pcap->pkt_index + PCAP_PAGE_SIZE) < pcap->pkt_count) pcap->pkt_index += PCAP_PAGE_SIZE; else pcap->pkt_index = 0; } pktgen.flags |= PRINT_LABELS_FLAG; } else if (_cp("system") || _cp("sys")) { pktgen.flags &= ~PAGE_MASK_BITS; pktgen.flags |= SYSTEM_PAGE_FLAG; } else if (_cp("range")) { pktgen.flags &= ~PAGE_MASK_BITS; pktgen.flags |= RANGE_PAGE_FLAG; } else if (_cp("cpu")) { pktgen.flags &= ~PAGE_MASK_BITS; pktgen.flags |= CPU_PAGE_FLAG; } else if (_cp("stats") || _cp("qstats")) { pktgen.flags &= ~PAGE_MASK_BITS; pktgen.flags |= QSTATS_PAGE_FLAG; } else if (_cp("xstats")) { pktgen.flags &= ~PAGE_MASK_BITS; pktgen.flags |= XSTATS_PAGE_FLAG; } else if (_cp("sequence") || _cp("seq")) { pktgen.flags &= ~PAGE_MASK_BITS; pktgen.flags |= SEQUENCE_PAGE_FLAG; } else if (_cp("random") || _cp("rnd")) { pktgen.flags &= ~PAGE_MASK_BITS; pktgen.flags |= RND_BITFIELD_PAGE_FLAG; } else if (_cp("log")) { pktgen.flags &= ~PAGE_MASK_BITS; pktgen.flags |= LOG_PAGE_FLAG; } else if (_cp("latency") || _cp("lat")) { pktgen.flags &= ~PAGE_MASK_BITS; pktgen.flags |= LATENCY_PAGE_FLAG; } else { uint16_t start_port; if (_cp("main")) page = 0; start_port = (page * pktgen.nb_ports_per_page); if ((pktgen.starting_port != start_port) && (start_port < pktgen.nb_ports)) { pktgen.starting_port = start_port; pktgen.ending_port = start_port + pktgen.nb_ports_per_page; if (pktgen.ending_port > (pktgen.starting_port + pktgen.nb_ports)) pktgen.ending_port = (pktgen.starting_port + pktgen.nb_ports); } if (pktgen.flags & PAGE_MASK_BITS) { pktgen.flags &= ~PAGE_MASK_BITS; pktgen.flags |= (MAIN_PAGE_FLAG | PRINT_LABELS_FLAG); } } pktgen_clear_display(); } /** * * pktgen_set_seq - Set a sequence packet for given port * * DESCRIPTION * Set the sequence packet information for all ports listed. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_set_seq(port_info_t *pinfo, uint32_t seqnum, struct rte_ether_addr *daddr, struct rte_ether_addr *saddr, struct pg_ipaddr *ip_daddr, struct pg_ipaddr *ip_saddr, uint32_t sport, uint32_t dport, char type, char proto, uint16_t vlanid, uint32_t pktsize, uint32_t gtpu_teid) { pkt_seq_t *pkt; pkt = &pinfo->seq_pkt[seqnum]; rte_ether_addr_copy(daddr, &pkt->eth_dst_addr); rte_ether_addr_copy(saddr, &pkt->eth_src_addr); pkt->ip_mask = size_to_mask(ip_saddr->prefixlen); if (type == '4') { pkt->ip_src_addr.addr.ipv4.s_addr = htonl(ip_saddr->ipv4.s_addr); pkt->ip_dst_addr.addr.ipv4.s_addr = htonl(ip_daddr->ipv4.s_addr); } else { memcpy(&pkt->ip_src_addr.addr.ipv6, &ip_saddr->ipv6, sizeof(struct rte_ipv6_addr)); memcpy(&pkt->ip_dst_addr.addr.ipv6, &ip_daddr->ipv6, sizeof(struct rte_ipv6_addr)); } pkt->dport = dport; pkt->sport = sport; pkt->pkt_size = pktsize - RTE_ETHER_CRC_LEN; pkt->ipProto = (proto == 'u') ? PG_IPPROTO_UDP : (proto == 'i') ? PG_IPPROTO_ICMP : PG_IPPROTO_TCP; /* Force the IP protocol to IPv4 if this is a ICMP packet. */ if (proto == 'i') type = '4'; pkt->ethType = (type == '6') ? RTE_ETHER_TYPE_IPV6 : RTE_ETHER_TYPE_IPV4; pkt->vlanid = vlanid; pkt->gtpu_teid = gtpu_teid; pktgen_packet_ctor(pinfo, seqnum, -1); } void pktgen_set_cos_tos_seq(port_info_t *pinfo, uint32_t seqnum, uint32_t cos, uint32_t tos) { pkt_seq_t *pkt; pkt = &pinfo->seq_pkt[seqnum]; pkt->cos = cos; pkt->tos = tos; pktgen_packet_ctor(pinfo, seqnum, -1); } void pktgen_set_vxlan_seq(port_info_t *pinfo, uint32_t seqnum, uint32_t flag, uint32_t gid, uint32_t vid) { pkt_seq_t *pkt; pkt = &pinfo->seq_pkt[seqnum]; pkt->vni_flags = flag; pkt->group_id = gid; pkt->vxlan_id = vid; pktgen_packet_ctor(pinfo, seqnum, -1); } /** * * pktgen_quit - Exit pktgen. * * DESCRIPTION * Close and exit Pktgen. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_quit(void) { cli_quit(); } static void _pcap_file_close(port_info_t *port) { pktgen_close_pcap_file(port->pcap_file); port->pcap_file = NULL; } static void _pcap_file_open(port_info_t *port, char *filename) { _pcap_file_close(port); port->pcap_file = pktgen_create_pcap_file(filename); } void pktgen_pcap_handler(port_info_t *pinfo, uint32_t state) { if (state == ENABLE_STATE) { char filename[64]; snprintf(filename, sizeof(filename), "tx-%d.pcap", pinfo->pid); _pcap_file_open(pinfo, filename); } else if (state == DISABLE_STATE) { _pcap_file_close(pinfo); } } ================================================ FILE: app/pktgen-cmds.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_CMDS_H_ #define _PKTGEN_CMDS_H_ /** * @file * * Pktgen command API: all functions invoked by the CLI and Lua scripting layer. * * Covers internal display helpers, global port control, single/range/sequence * packet configuration, debug utilities, enable/disable toggles, PCAP handling, * and TCP flag parsing helpers. */ #include #include #include #include "pktgen.h" #include #include #ifdef __cplusplus extern "C" { #endif /* Internal APIs */ /** Format the active port flags as a human-readable string. */ char *pktgen_flags_string(port_info_t *pinfo); /** Format the TX count and rate for a port into @p buff. */ char *pktgen_transmit_count_rate(int port, char *buff, int len); /** Schedule a display refresh on the next timer tick. */ void pktgen_update_display(void); /** Refresh port statistics and redraw the active display page. */ void pktgen_update(void); /** Format the link state for a port into @p buff. */ char *pktgen_link_state(int port, char *buff, int len); /** Format the TX packet count for a port into @p buff. */ char *pktgen_transmit_count(int port, char *buff, int len); /** Format the TX rate percentage for a port into @p buff. */ char *pktgen_transmit_rate(int port, char *buff, int len); /** * Copy the current statistics for a port into a caller-supplied struct. * * @return * 0 on success, negative on invalid port. */ int pktgen_port_stats(int port, port_stats_t *pstats); /* Global commands */ /** * Send an ARP request of the given type on a port. * * @param pinfo * Per-port state. * @param type * ARP type flag (SEND_ARP_REQUEST or SEND_GRATUITOUS_ARP). */ void pktgen_send_arp_requests(port_info_t *pinfo, uint32_t type); /** Start packet transmission on a port. */ void pktgen_start_transmitting(port_info_t *pinfo); /** Stop packet transmission on a port. */ void pktgen_stop_transmitting(port_info_t *pinfo); /** Return non-zero if a port is currently transmitting. */ int pktgen_port_transmitting(int port); /** Switch the active display page (e.g. "main", "range", "seq"). */ void pktgen_set_page(char *str); /** Enable or disable the terminal screen. */ void pktgen_screen(int state); /** Force an immediate display update regardless of the timer tick. */ void pktgen_force_update(void); void pktgen_update_display(void); /** Clear and redraw the display screen. */ void pktgen_clear_display(void); /** Start or stop the latency sampler on a port based on @p state. */ void pktgen_start_stop_latency_sampler(port_info_t *pinfo, uint32_t state); /** Start the latency sampler on a port. */ void pktgen_start_latency_sampler(port_info_t *pinfo); /** Stop the latency sampler on a port. */ void pktgen_stop_latency_sampler(port_info_t *pinfo); /** * Save the current pktgen configuration to a file. * * @param path * Destination file path. * @return * 0 on success, negative on error. */ int pktgen_save(char *path); /** Clear the terminal screen. */ void pktgen_cls(void); /** Send an IPv4 ping (ICMP Echo Request) on a port. */ void pktgen_ping4(port_info_t *pinfo); #ifdef INCLUDE_PING6 /** Send an IPv6 ping (ICMPv6 Echo Request) on a port. */ void pktgen_ping6(port_info_t *pinfo); #endif /** Clear accumulated statistics for a port. */ void pktgen_clear_stats(port_info_t *pinfo); /** Reset a port's configuration to factory defaults. */ void pktgen_reset(port_info_t *pinfo); /** Stop and restart the Ethernet device for a port. */ void pktgen_port_restart(port_info_t *pinfo); /** Enable or disable learning the source MAC address from received ARP replies. */ void pktgen_mac_from_arp(int state); /** Send a burst of priming packets to pre-fill downstream buffers. */ void pktgen_prime_ports(port_info_t *pinfo); /** Terminate pktgen cleanly. */ void pktgen_quit(void); /** Set the number of ports displayed per screen page. */ void pktgen_set_page_size(uint32_t page_size); /** Set the currently displayed port number. */ void pktgen_set_port_number(uint16_t port_number); /** Set the prime-burst packet count for a port. */ void pktgen_set_port_prime(port_info_t *pinfo, uint32_t cnt); /** Reset per-port settings for a port to their defaults. */ void pktgen_port_defaults(uint16_t pid); /** Reset all sequence-packet slots for a port to their defaults. */ void pktgen_seq_defaults(uint16_t pid); struct pg_ipaddr; /* Single */ /** * Set the source or destination IP address for single-packet mode. * * @param pinfo Per-port state. * @param type 's' for source, 'd' for destination. * @param ip Parsed IP address. * @param ip_ver 4 for IPv4, 6 for IPv6. */ void single_set_ipaddr(port_info_t *pinfo, char type, struct pg_ipaddr *ip, int ip_ver); /** Set the transport protocol ("tcp", "udp", or "icmp"). */ void single_set_proto(port_info_t *pinfo, char *type); /** Set the TCP initial sequence number. */ void single_set_tcp_seq(port_info_t *pinfo, uint32_t seq); /** Set the TCP initial acknowledgement number. */ void single_set_tcp_ack(port_info_t *pinfo, uint32_t ack); /** Set TCP flags from a string (e.g. "syn", "ack", "fin"). */ void single_set_tcp_flags(port_info_t *pinfo, const char *flags); /** Set the VLAN ID for single-packet mode. */ void single_set_vlan_id(port_info_t *pinfo, uint16_t vlanid); /** Set the CoS / 802.1p priority value. */ void single_set_cos(port_info_t *pinfo, uint8_t cos); /** Set the ToS (DSCP) byte value. */ void single_set_tos(port_info_t *pinfo, uint8_t tos); /** * Set a MAC address for single-packet mode. * * @param which "src" or "dst". */ void single_set_mac(port_info_t *pinfo, const char *which, struct rte_ether_addr *mac); /** Set the destination MAC address. */ void single_set_dst_mac(port_info_t *pinfo, struct rte_ether_addr *mac); /** Set the source MAC address. */ void single_set_src_mac(port_info_t *pinfo, struct rte_ether_addr *mac); /** Set the packet type ("ipv4", "ipv6", "vlan", etc.). */ void single_set_pkt_type(port_info_t *pinfo, const char *type); /** Set the TX packet count (0 means send forever). */ void single_set_tx_count(port_info_t *pinfo, uint32_t cnt); /** Set the TX burst size. */ void single_set_tx_burst(port_info_t *pinfo, uint32_t burst); /** Set the RX burst size. */ void single_set_rx_burst(port_info_t *pinfo, uint32_t burst); /** Set the packet size in bytes (excluding FCS). */ void single_set_pkt_size(port_info_t *pinfo, uint16_t size); /** Set the TX rate as a percentage string (e.g. "100" or "50.5"). */ void single_set_tx_rate(port_info_t *pinfo, const char *rate); /** Set the jitter threshold in microseconds. */ void single_set_jitter(port_info_t *pinfo, uint64_t threshold); /** Set the IP TTL / hop-limit value. */ void single_set_ttl_value(port_info_t *pinfo, uint8_t ttl); /** * Set the source or destination L4 port number. * * @param type 's' for source, 'd' for destination. * @param portValue Port number to set. */ void single_set_port_value(port_info_t *pinfo, char type, uint32_t portValue); /** Set the outer and inner VLAN IDs for Q-in-Q mode. */ void single_set_qinqids(port_info_t *pinfo, uint16_t outerid, uint16_t innerid); /** Set VxLAN tunnel flags, group ID, and VNI. */ void single_set_vxlan(port_info_t *pinfo, uint16_t flags, uint16_t group_id, uint32_t vxlan_id); /** * Configure the latency sampler for a port. * * @param type Sampler type string ("simple" or "poisson"). * @param num_samples Number of samples to collect. * @param sampling_rate Sampling rate in packets per second. * @param outfile Path to write the sample data, or empty for none. */ void single_set_latsampler_params(port_info_t *pinfo, char *type, uint32_t num_samples, uint32_t sampling_rate, char outfile[]); /* Debug */ /** Dump port debug information to the log. */ void debug_dump(port_info_t *pinfo, char *str); /** Enable or disable port LED blinking. */ void debug_blink(port_info_t *pinfo, uint32_t state); /** Override the TX inter-burst cycle count for debugging. */ void debug_set_tx_cycles(port_info_t *pinfo, uint32_t cycles); /** Override the RX poll cycle count for debugging. */ void debug_set_rx_cycles(port_info_t *pinfo, uint32_t cycles); /** Dump the full L2P lcore-to-port matrix to the log. */ void debug_matrix_dump(void); /** Dump mempool statistics for a named pool on a port. */ void debug_mempool_dump(port_info_t *pinfo, char *name); /** Enable raw packet dump for the next @p cnt received packets. */ void debug_set_port_dump(port_info_t *pinfo, uint32_t cnt); /** Print TX rate debug counters for a port. */ void debug_tx_rate(port_info_t *pinfo); /** Enable or disable PCAP file replay mode on a port. */ void pktgen_pcap_handler(port_info_t *pinfo, uint32_t state); #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) /** Display the bonding driver mode for a port. */ void show_bonding_mode(port_info_t *pinfo); #endif /* Enable or toggle types */ /** Enable or disable the RX TAP interface. */ void enable_rx_tap(port_info_t *pinfo, uint32_t state); /** Enable or disable the TX TAP interface. */ void enable_tx_tap(port_info_t *pinfo, uint32_t state); /** Enable or disable VLAN tagging. */ void enable_vlan(port_info_t *pinfo, uint32_t state); /** Enable or disable VxLAN encapsulation. */ void enable_vxlan(port_info_t *pinfo, uint32_t state); /** Enable or disable Q-in-Q double VLAN tagging. */ void enable_qinq(port_info_t *pinfo, uint32_t state); /** Enable or disable MPLS label insertion. */ void enable_mpls(port_info_t *pinfo, uint32_t state); /** Enable or disable GRE IPv4 encapsulation. */ void enable_gre(port_info_t *pinfo, uint32_t state); /** Enable or disable GRE Ethernet frame encapsulation. */ void enable_gre_eth(port_info_t *pinfo, uint32_t state); /** Enable or disable ICMP Echo reply processing. */ void enable_icmp_echo(port_info_t *pinfo, uint32_t state); /** Enable or disable random source IP address generation. */ void enable_rnd_s_ip(port_info_t *pinfo, uint32_t state); /** Enable or disable random source port generation. */ void enable_rnd_s_pt(port_info_t *pinfo, uint32_t state); /** Enable or disable random bitfield packet mode. */ void enable_random(port_info_t *pinfo, uint32_t state); /** Enable or disable latency measurement packet injection. */ void enable_latency(port_info_t *pinfo, uint32_t state); /** Enable or disable global MAC-from-ARP learning. */ void enable_mac_from_arp(uint32_t state); /** Enable or disable clock_gettime() as the time source (vs rdtsc). */ void enable_clock_gettime(uint32_t state); /** Enable or disable input packet processing (ARP/ICMP handling). */ void enable_process(port_info_t *pinfo, int state); /** Enable or disable packet capture to memory. */ void enable_capture(port_info_t *pinfo, uint32_t state); /** Enable or disable range-mode packet generation. */ void enable_range(port_info_t *pinfo, uint32_t state); /** Enable or disable PCAP file replay mode. */ void enable_pcap(port_info_t *pinfo, uint32_t state); #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) /** Enable or disable the bonding driver TX-zero-packets workaround. */ void enable_bonding(port_info_t *pinfo, uint32_t state); #endif /* PCAP */ /** Set a BPF filter string for PCAP packet capture. */ void pcap_filter(port_info_t *pinfo, char *str); /* Range commands */ /** * Set start, minimum, or maximum destination MAC in range mode. * * @param what "start", "min", "max", or "inc". */ void range_set_dest_mac(port_info_t *pinfo, const char *what, struct rte_ether_addr *mac); /** Set start, minimum, maximum, or increment for the source MAC in range mode. */ void range_set_src_mac(port_info_t *pinfo, const char *what, struct rte_ether_addr *mac); /** Set start, minimum, maximum, or increment for the source IP in range mode. */ void range_set_src_ip(port_info_t *pinfo, char *what, struct pg_ipaddr *ip); /** Set start, minimum, maximum, or increment for the destination IP in range mode. */ void range_set_dst_ip(port_info_t *pinfo, char *what, struct pg_ipaddr *ip); /** Set start, minimum, maximum, or increment for the source L4 port in range mode. */ void range_set_src_port(port_info_t *pinfo, char *what, uint16_t port); /** Set start, minimum, maximum, or increment for the destination L4 port in range mode. */ void range_set_dst_port(port_info_t *pinfo, char *what, uint16_t port); /** Set the transport protocol for range mode ("tcp" or "udp"). */ void range_set_proto(port_info_t *pinfo, const char *type); /** Set the packet type for range mode. */ void range_set_pkt_type(port_info_t *pinfo, const char *type); /** Set TCP flags for range mode from a string. */ void range_set_tcp_flags(port_info_t *pinfo, const char *flags); /** Set start, minimum, maximum, or increment for the TCP sequence number in range mode. */ void range_set_tcp_seq(port_info_t *pinfo, char *what, uint32_t seq); /** Set start, minimum, maximum, or increment for the TCP acknowledgement number in range mode. */ void range_set_tcp_ack(port_info_t *pinfo, char *what, uint32_t ack); /** Set start, minimum, maximum, or increment for the packet size in range mode. */ void range_set_pkt_size(port_info_t *pinfo, char *what, uint16_t size); /** Set start, minimum, maximum, or increment for the GTP-U TEID in range mode. */ void range_set_gtpu_teid(port_info_t *pinfo, char *what, uint32_t teid); /** Set start, minimum, maximum, or increment for the VLAN ID in range mode. */ void range_set_vlan_id(port_info_t *pinfo, char *what, uint16_t id); /** Set start, minimum, maximum, or increment for the ToS byte in range mode. */ void range_set_tos_id(port_info_t *pinfo, char *what, uint8_t id); /** Set start, minimum, maximum, or increment for the CoS value in range mode. */ void range_set_cos_id(port_info_t *pinfo, char *what, uint8_t id); /** Set the MPLS label entry for range mode. */ void range_set_mpls_entry(port_info_t *pinfo, uint32_t mpls_entry); /** Set the Q-in-Q outer and inner VLAN IDs for range mode. */ void range_set_qinqids(port_info_t *pinfo, uint16_t outerid, uint16_t innerid); /** Set the GRE key for range mode. */ void range_set_gre_key(port_info_t *pinfo, uint32_t gre_key); /** Set start, minimum, maximum, or increment for the TTL in range mode. */ void range_set_ttl(port_info_t *pinfo, char *what, uint8_t ttl); /** Set start, minimum, maximum, or increment for the IPv6 hop limit in range mode. */ void range_set_hop_limits(port_info_t *pinfo, char *what, uint8_t hop_limits); /** Set start, minimum, maximum, or increment for the IPv6 traffic class in range mode. */ void range_set_traffic_class(port_info_t *pinfo, char *what, uint8_t traffic_class); /* Sequence */ /** Set the number of active sequence packets for a port. */ void pktgen_set_port_seqCnt(port_info_t *pinfo, uint32_t cnt); /** * Set all fields for one sequence-packet slot. * * @param seqnum Slot index (0 .. NUM_SEQ_PKTS-1). * @param daddr Destination MAC address. * @param saddr Source MAC address. * @param ip_daddr Destination IP address. * @param ip_saddr Source IP address. * @param sport Source L4 port. * @param dport Destination L4 port. * @param ip 'v' for IPv4, '6' for IPv6. * @param proto 't' for TCP, 'u' for UDP, 'i' for ICMP. * @param vlanid VLAN ID. * @param pktsize Packet size in bytes. * @param gtpu_teid GTP-U TEID (0 if unused). */ void pktgen_set_seq(port_info_t *pinfo, uint32_t seqnum, struct rte_ether_addr *daddr, struct rte_ether_addr *saddr, struct pg_ipaddr *ip_daddr, struct pg_ipaddr *ip_saddr, uint32_t sport, uint32_t dport, char ip, char proto, uint16_t vlanid, uint32_t pktsize, uint32_t gtpu_teid); /** Set CoS and ToS values for a sequence-packet slot. */ void pktgen_set_cos_tos_seq(port_info_t *pinfo, uint32_t seqnum, uint32_t cos, uint32_t tos); /** Set VxLAN parameters for a sequence-packet slot. */ void pktgen_set_vxlan_seq(port_info_t *pinfo, uint32_t seqnum, uint32_t flag, uint32_t gid, uint32_t vid); /** Set TCP flags for a sequence-packet slot from a string. */ void seq_set_tcp_flags(port_info_t *pinfo, uint32_t seqnum, const char *flags); /* Pattern */ /** Set the payload fill pattern type ("zero", "abc", "user", or "none"). */ void pattern_set_type(port_info_t *pinfo, char *str); /** Set the user-defined payload fill pattern string. */ void pattern_set_user_pattern(port_info_t *pinfo, char *str); /** * Parse a TCP flags string into a bitmask. * * @return * Bitmask of TCP flag bits. */ uint16_t tcp_flags_from_str(const char *str); /** * Format a TCP flag bitmask as a human-readable string. * * @param flags Bitmask of TCP flag bits. * @param buf Output buffer. * @param len Size of @p buf. * @return * 0 on success, negative if the buffer is too small. */ int tcp_str_from_flags(uint16_t flags, char *buf, size_t len); #ifdef __cplusplus } #endif #endif /* _PKTGEN_CMDS_H_ */ ================================================ FILE: app/pktgen-constants.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_CONSTANTS_H_ #define _PKTGEN_CONSTANTS_H_ /** * @file * * Memory and burst sizing constants for Pktgen. */ #include #ifdef __cplusplus extern "C" { #endif enum { MAX_PKT_RX_BURST = 256, /**< Maximum RX burst size (array dimension) */ MAX_PKT_TX_BURST = 256, /**< Maximum TX burst size (array dimension) */ DEFAULT_PKT_RX_BURST = 64, /**< Default RX burst size */ DEFAULT_PKT_TX_BURST = 32, /**< Default TX burst size */ DEFAULT_RX_DESC = 1024, /**< Default RX descriptor ring size */ DEFAULT_TX_DESC = 1024, /**< Default TX descriptor ring size */ DEFAULT_MBUFS_PER_PORT_MULTIPLIER = 16, /**< Multiplier for mbufs-per-port calculation */ MAX_SPECIAL_MBUFS = 1024, /**< Maximum special mbufs per port */ MBUF_CACHE_SIZE = 256, /**< Per-lcore mempool cache size */ DEFAULT_PRIV_SIZE = 0, /**< Default mbuf private data size */ }; /** Compute the total mbufs needed for a port given its descriptor ring sizes. */ #define MAX_MBUFS_PER_PORT(rxd, txd) ((rxd + txd) * DEFAULT_MBUFS_PER_PORT_MULTIPLIER) #ifdef __cplusplus } #endif #endif /* _PKTGEN_CONSTANTS_H_ */ ================================================ FILE: app/pktgen-cpu.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include "pktgen-display.h" #include "pktgen-cpu.h" #include "pktgen-log.h" #include "pktgen.h" static char *uname_str; static int save_uname(char *line, int i __rte_unused) { uname_str = pg_strdupf(uname_str, line); return 0; } /** * * pktgen_page_cpu - Display the CPU page. * * DESCRIPTION * Display the CPU page. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_page_cpu(void) { uint32_t row, cnt, nb_sockets, nb_cores, nb_threads; static int counter = 0; display_topline("", 0, 0, 0); cnt = coreinfo_lcore_cnt(); if ((cnt == 0) || (pktgen.lscpu == NULL)) pktgen_cpu_init(); nb_sockets = coreinfo_socket_cnt(); nb_cores = coreinfo_core_cnt(); nb_threads = coreinfo_thread_cnt(); if ((counter++ & 3) != 0) return; pktgen_display_set_color("stats.stat.label"); row = 3; scrn_printf(row++, 1, "Kernel: %s", uname_str); row++; pktgen_display_set_color("stats.stat.values"); scrn_printf(row++, 1, "Model Name: %s", pktgen.lscpu->model_name); scrn_printf(row++, 1, "Cache Size: %s", pktgen.lscpu->cache_size); row++; pktgen_display_set_color("top.ports"); scrn_printf(row++, 1, "CPU Flags : %s", pktgen.lscpu->cpu_flags); row += 6; pktgen_display_set_color("stats.total.data"); scrn_printf(row++, 1, "Number of sockets %d, cores/socket %d, threads/core %d, total %d", nb_sockets, nb_cores, nb_threads, cnt); if (pktgen.flags & PRINT_LABELS_FLAG) { pktgen.last_row = row + pktgen.nb_ports; display_dashline(pktgen.last_row); scrn_setw(pktgen.last_row); scrn_printf(100, 1, ""); /* Put cursor on the last row. */ } pktgen_display_set_color(NULL); pktgen.flags &= ~PRINT_LABELS_FLAG; } /** * * pktgen_cfg_init - Init the CPU information * * DESCRIPTION * initialize the CPU information * * RETURNS: N/A * * SEE ALSO: */ void pktgen_cpu_init(void) { do_command("uname -a", save_uname); pktgen.lscpu = lscpu_info(NULL, NULL); } ================================================ FILE: app/pktgen-cpu.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_CPU_H_ #define _PKTGEN_CPU_H_ /** * @file * * CPU topology initialisation and display page for Pktgen. */ #ifdef __cplusplus extern "C" { #endif /** * Initialise the CPU topology information for all detected lcores. * * Populates socket, core, and thread IDs for each lcore in the * global coreinfo table. */ void pktgen_cpu_init(void); /** * Render the CPU topology display page to the console. */ void pktgen_page_cpu(void); /** * Look up the lcore ID for a given socket/core/thread combination. * * @param s * NUMA socket ID. * @param c * Physical core ID within the socket. * @param t * Hyper-thread (sibling thread) index within the core. * @return * Logical core (lcore) ID on success, or 0 if not found. */ static inline uint16_t sct(uint8_t s, uint8_t c, uint8_t t) { coreinfo_t *ci; for (uint16_t i = 0; i < coreinfo_lcore_cnt(); i++) { ci = coreinfo_get(i); if (ci->socket_id == s && ci->core_id == c && ci->thread_id == t) return ci->lcore_id; } return 0; } #ifdef __cplusplus } #endif #endif /* _PKTGEN_CPU_H_ */ ================================================ FILE: app/pktgen-display.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include "lua_config.h" #include "pktgen-display.h" #include "pktgen-cmds.h" #define MAX_COLOR_NAME_SIZE 64 #define MAX_PROMPT_STRING_SIZE 64 static char prompt_str[MAX_PROMPT_STRING_SIZE] = {0}; /* String to color value mapping */ typedef struct string_color_map_s { const char *name; /**< Color name */ scrn_color_e color; /**< Color value for scrn_{fg,bg}color() */ } string_color_map_t; // clang-format off string_color_map_t string_color_map[] = { {"black", SCRN_BLACK}, {"white", SCRN_DEFAULT_FG}, {"red", SCRN_RED}, {"green", SCRN_GREEN}, {"yellow", SCRN_YELLOW}, {"blue", SCRN_BLUE}, {"magenta", SCRN_MAGENTA}, {"cyan", SCRN_CYAN}, {"white", SCRN_WHITE}, {"black", SCRN_DEFAULT_BG}, {"default", SCRN_BLACK}, /* alias */ {"none", SCRN_NO_CHANGE}, {"default_fg", SCRN_WHITE}, {"default_bg", SCRN_BLACK}, {NULL, 0} }; // clang-format on /* String to attribute mapping */ typedef struct string_attr_map_s { const char *name; /**< Attribute name */ scrn_attr_e attr; /**< Attribute value for scrn_{fg,bg}color_attr() */ } string_attr_map_t; // clang-format off string_attr_map_t string_attr_map[] = { {"off", SCRN_OFF}, {"default", SCRN_OFF}, /* alias */ {"bold", SCRN_BOLD}, {"bright", SCRN_BOLD}, /* alias */ {"underscore", SCRN_UNDERSCORE}, {"underline", SCRN_UNDERSCORE}, /* alias */ {"blink", SCRN_BLINK}, {"reverse", SCRN_REVERSE}, {"concealed", SCRN_CONCEALED}, {NULL, 0} }; // clang-format on /* Element to color mapping */ typedef struct theme_color_map_s { const char *name; /**< Display element name */ scrn_color_e fg_color; scrn_color_e bg_color; scrn_attr_e attr; } theme_color_map_t; // clang-format off theme_color_map_t theme_color_map[] = { /* { "element name", FG_COLOR, BG_COLOR, ATTR } */ { "default", SCRN_DEFAULT_FG, SCRN_DEFAULT_BG, SCRN_OFF }, /* * Top line of the screen */ { "top.spinner", SCRN_CYAN, SCRN_NO_CHANGE, SCRN_BOLD }, { "top.ports", SCRN_GREEN, SCRN_NO_CHANGE, SCRN_BOLD }, { "top.page", SCRN_WHITE, SCRN_NO_CHANGE, SCRN_BOLD }, { "top.copyright", SCRN_YELLOW, SCRN_NO_CHANGE, SCRN_OFF }, { "top.poweredby", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_BOLD }, /* * Separator between displayed values and command history */ { "sep.dash", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_OFF }, { "sep.text", SCRN_WHITE, SCRN_NO_CHANGE, SCRN_OFF }, /* * Stats screen */ /* Port related */ { "stats.port.label", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_BOLD }, { "stats.port.flags", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_BOLD }, { "stats.port.data", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_OFF }, { "stats.port.status", SCRN_GREEN, SCRN_NO_CHANGE, SCRN_OFF }, { "stats.port.linklbl", SCRN_GREEN, SCRN_NO_CHANGE, SCRN_BOLD }, { "stats.port.link", SCRN_GREEN, SCRN_NO_CHANGE, SCRN_OFF }, { "stats.port.ratelbl", SCRN_YELLOW, SCRN_NO_CHANGE, SCRN_BOLD }, { "stats.port.rate", SCRN_YELLOW, SCRN_NO_CHANGE, SCRN_OFF }, { "stats.port.sizelbl", SCRN_CYAN, SCRN_NO_CHANGE, SCRN_BOLD }, { "stats.port.sizes", SCRN_CYAN, SCRN_NO_CHANGE, SCRN_OFF }, { "stats.port.errlbl", SCRN_RED, SCRN_NO_CHANGE, SCRN_BOLD }, { "stats.port.errors", SCRN_RED, SCRN_NO_CHANGE, SCRN_OFF }, { "stats.port.totlbl", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_BOLD }, { "stats.port.totals", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_OFF }, /* Dynamic elements (updated every second) */ { "stats.dyn.label", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_BOLD }, { "stats.dyn.values", SCRN_GREEN, SCRN_NO_CHANGE, SCRN_OFF }, /* Static elements (only update when explicitly set to different value) */ { "stats.stat.label", SCRN_MAGENTA, SCRN_NO_CHANGE, SCRN_OFF }, { "stats.stat.values", SCRN_WHITE, SCRN_NO_CHANGE, SCRN_OFF }, /* Total statistics */ { "stats.total.label", SCRN_RED, SCRN_NO_CHANGE, SCRN_BOLD }, { "stats.total.data", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_BOLD }, /* Colon separating labels and values */ { "stats.colon", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_BOLD }, /* Highlight some static values */ { "stats.rate.count", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_BOLD }, { "stats.bdf", SCRN_BLUE, SCRN_NO_CHANGE, SCRN_OFF }, { "stats.mac", SCRN_GREEN, SCRN_NO_CHANGE, SCRN_OFF }, { "stats.ip", SCRN_CYAN, SCRN_NO_CHANGE, SCRN_OFF }, /* * Misc. */ { "pktgen.prompt", SCRN_GREEN, SCRN_NO_CHANGE, SCRN_OFF }, { NULL, 0, 0, 0 } }; // clang-format on /* Initialize screen data structures */ /* Print out the top line on the screen */ void display_topline(const char *msg, int pstart, int pstop, int pcnt) { if (this_scrn && this_scrn->type != SCRN_STDIN_TYPE) { scrn_puts("\n"); return; } pktgen_display_set_color("top.page"); scrn_printf(PAGE_TITLE_ROW, 3, "%s ", msg); if (pcnt > 0) { pktgen_display_set_color("stats.port.totlbl"); scrn_puts("Ports %d-%d of %d", pstart, pstop, pcnt); } pktgen_display_set_color("top.copyright"); scrn_puts(" %s", copyright_msg_short()); } /* Print out the dashed line on the screen. */ void display_dashline(int last_row) { int i; if (this_scrn && this_scrn->type != SCRN_STDIN_TYPE) { scrn_puts("\n"); return; } scrn_setw(last_row); last_row--; scrn_pos(last_row, 1); pktgen_display_set_color("sep.dash"); for (i = 0; i < 79; i++) scrn_fprintf(0, 0, stdout, "-"); pktgen_display_set_color("stats.port.linklbl"); scrn_printf(last_row, 3, " %s ", pktgen_version()); pktgen_display_set_color("top.poweredby"); scrn_puts(" %s %s", powered_by(), rte_version()); pktgen_display_set_color("stats.port.rate"); scrn_puts(" (pid:%d) ", getpid()); pktgen_display_set_color(NULL); } /* Get the display geometry */ void pktgen_display_get_geometry(uint16_t *rows, uint16_t *cols) { if (!this_scrn) return; if (rows != NULL) *rows = this_scrn->nrows; if (cols != NULL) *cols = this_scrn->ncols; } /* Look up the named color in the colormap */ static theme_color_map_t * lookup_item(const char *elem) { theme_color_map_t *result; if (elem == NULL) elem = "default"; /* Look up colors and attributes for elem */ for (result = theme_color_map; result->name != NULL; ++result) if (strncasecmp(result->name, elem, MAX_COLOR_NAME_SIZE) == 0) break; /* Report failure if element is not found */ if (result->name == NULL) result = NULL; return result; } /* Changes the color to the color of the specified element */ void pktgen_display_set_color(const char *elem) { theme_color_map_t *theme_color; if (!this_scrn || this_scrn->theme == SCRN_THEME_OFF) return; theme_color = lookup_item(elem); if (theme_color == NULL) { pktgen_log_error("Unknown color '%s'", elem); return; } scrn_color(theme_color->fg_color, theme_color->bg_color, theme_color->attr); } /* String to use as prompt, with proper ANSI color codes */ void __set_prompt(void) { theme_color_map_t *def, *prompt; if (!this_scrn) return; /* Set default return value. */ snprintf(prompt_str, sizeof(prompt_str), "%s> ", PKTGEN_VER_PREFIX); if ((this_scrn->theme == SCRN_THEME_ON) && !scrn_is_paused()) { /* Look up the default and prompt values */ def = lookup_item(NULL); prompt = lookup_item("pktgen.prompt"); if ((def == NULL) || (prompt == NULL)) pktgen_log_error("Prompt and/or default color undefined"); else snprintf(prompt_str, sizeof(prompt_str), "\033[%d;%d;%dm%s>\033[%d;%d;%dm ", prompt->attr, 30 + prompt->fg_color, 40 + prompt->bg_color, PKTGEN_VER_PREFIX, def->attr, 30 + def->fg_color, 40 + def->bg_color); } } static const char * get_name_by_color(scrn_color_e color) { int i; for (i = 0; string_color_map[i].name; i++) if (color == string_color_map[i].color) return string_color_map[i].name; return NULL; } static const char * get_name_by_attr(scrn_attr_e attr) { int i; for (i = 0; string_attr_map[i].name; i++) if (attr == string_attr_map[i].attr) return string_attr_map[i].name; return NULL; } static scrn_color_e get_color_by_name(char *name) { int i; for (i = 0; string_color_map[i].name; i++) if (strcmp(name, string_color_map[i].name) == 0) return string_color_map[i].color; return SCRN_UNKNOWN_COLOR; } static scrn_attr_e get_attr_by_name(char *name) { int i; for (i = 0; string_attr_map[i].name; i++) if (strcmp(name, string_attr_map[i].name) == 0) return string_attr_map[i].attr; return SCRN_UNKNOWN_ATTR; } void pktgen_theme_show(void) { int i; if (!this_scrn) return; printf("*** Theme Color Map Names (%s) ***\n", this_scrn->theme ? "Enabled" : "Disabled"); printf(" %-30s %-10s %-10s %s\n", "name", "FG Color", "BG Color", "Attribute"); for (i = 0; theme_color_map[i].name; i++) { printf(" %-32s %-10s %-10s %-6s", theme_color_map[i].name, get_name_by_color(theme_color_map[i].fg_color), get_name_by_color(theme_color_map[i].bg_color), get_name_by_attr(theme_color_map[i].attr)); printf(" "); pktgen_display_set_color(theme_color_map[i].name); printf("%-s", theme_color_map[i].name); pktgen_display_set_color(NULL); printf("\n"); } } void pktgen_theme_state(const char *state) { if (!this_scrn) return; if (estate(state) == DISABLE_STATE) this_scrn->theme = SCRN_THEME_OFF; else this_scrn->theme = SCRN_THEME_ON; __set_prompt(); } void pktgen_set_theme_item(char *item, char *fg_color, char *bg_color, char *attr) { theme_color_map_t *elem; scrn_color_e fg, bg; scrn_attr_e at; elem = lookup_item(item); if (elem == NULL) { pktgen_log_error("Unknown item name (%s)\n", item); return; } fg = get_color_by_name(fg_color); bg = get_color_by_name(bg_color); at = get_attr_by_name(attr); if ((fg == SCRN_UNKNOWN_COLOR) || (bg == SCRN_UNKNOWN_COLOR) || (at == SCRN_UNKNOWN_ATTR)) { pktgen_log_error("Unknown color or attribute (%s, %s, %s)", fg_color, bg_color, attr); return; } elem->fg_color = fg; elem->bg_color = bg; elem->attr = at; } void pktgen_theme_save(char *filename) { FILE *f; int i; f = fopen(filename, "w+"); if (f == NULL) { pktgen_log_error("Unable to open file %s", filename); return; } for (i = 0; theme_color_map[i].name; i++) fprintf(f, "theme %s %s %s %s\n", theme_color_map[i].name, get_name_by_color(theme_color_map[i].fg_color), get_name_by_color(theme_color_map[i].bg_color), get_name_by_attr(theme_color_map[i].attr)); fprintf(f, "cls\n"); fchmod(fileno(f), 0666); fclose(f); } void pktgen_print_div(uint32_t row_first, uint32_t row_last, uint32_t col) { uint32_t row; if (this_scrn && this_scrn->type != SCRN_STDIN_TYPE) return; pktgen_display_set_color("stats.colon"); for (row = row_first; row < row_last; row++) scrn_printf(row, col, ":"); } ================================================ FILE: app/pktgen-display.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_DISPLAY_H_ #define _PKTGEN_DISPLAY_H_ /** * @file * * Terminal display helpers for Pktgen: screen initialisation, top/dash * line drawing, geometry queries, colour-theme management, and column * divider rendering. */ /* TODO create pktgen_display_*() abstractions and remove this #include */ #include #include #include "pktgen.h" #ifdef __cplusplus extern "C" { #endif /** * Initialise the terminal screen and colour theme. * * @param theme * Non-zero to enable the colour theme; 0 for monochrome output. */ void pktgen_init_screen(int theme); /** * Print the top banner line of the display with page title and port range. * * @param msg * Page title string to centre in the top line. * @param pstart * First port index included on this display page. * @param pstop * Last port index included on this display page (inclusive). * @param pcnt * Total number of ports shown on this page. */ void display_topline(const char *msg, int pstart, int pstop, int pcnt); /** * Print a full-width dashed separator line at the given row. * * @param last_row * Terminal row number at which to draw the dashed line. */ void display_dashline(int last_row); /** * Query the current terminal geometry. * * @param rows * Output: number of terminal rows. * @param cols * Output: number of terminal columns. */ void pktgen_display_get_geometry(uint16_t *rows, uint16_t *cols); /** * Apply the foreground/background colours associated with theme element @p elem. * * @param elem * Theme element name string (e.g. "top.page", "stats.total.label"). */ void pktgen_display_set_color(const char *elem); /** * Update the CLI prompt string to reflect the current colour theme. * * When the colour theme is enabled the prompt includes ANSI escape codes; * when disabled it is plain text. */ void pktgen_set_prompt(void); /** * Print all defined colour theme entries with their associated colours. */ void pktgen_show_theme(void); /** * Set the colours and text attribute for a named theme item. * * @param item * Theme item name to update. * @param fg_color * Foreground colour name string (e.g. "red", "default"). * @param bg_color * Background colour name string. * @param attr * Text attribute string (e.g. "bold", "underline", "none"). */ void pktgen_set_theme_item(char *item, char *fg_color, char *bg_color, char *attr); /** * Save the current colour theme as a sequence of Pktgen commands to @p filename. * * @param filename * Path to the file in which the theme commands are written. */ void pktgen_theme_save(char *filename); /** * Enable or disable the colour theme. * * @param state * "on" to enable the colour theme, "off" to disable it. */ void pktgen_theme_state(const char *state); /** * Print the current colour-theme enable/disable state to the console. */ void pktgen_theme_show(void); /** * Draw a vertical column divider (colon characters) between two rows. * * @param row_first * First terminal row at which to start drawing the divider. * @param row_last * Last terminal row at which to stop drawing the divider (inclusive). * @param col * Terminal column at which the divider is drawn. */ void pktgen_print_div(uint32_t row_first, uint32_t row_last, uint32_t col); #ifdef __cplusplus } #endif #endif /* _PKTGEN_DISPLAY_H_ */ ================================================ FILE: app/pktgen-dump.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include "pktgen.h" #include "pktgen-log.h" /** * * pktgen_packet_dump - Dump the contents of a packet * * DESCRIPTION * Dump the contents of a packet. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_packet_dump(struct rte_mbuf *m, int pid) { port_info_t *pinfo = l2p_get_port_pinfo(pid); int plen = (m->pkt_len + RTE_ETHER_CRC_LEN); unsigned char *curr_data; struct rte_mbuf *curr_mbuf; /* Checking if pinfo->dump_tail will not overflow is done in the caller */ if (pinfo->dump_list[pinfo->dump_tail].data != NULL) rte_free(pinfo->dump_list[pinfo->dump_tail].data); pinfo->dump_list[pinfo->dump_tail].data = rte_zmalloc_socket("Packet data", plen, 0, pg_socket_id()); pinfo->dump_list[pinfo->dump_tail].len = plen; for (curr_data = pinfo->dump_list[pinfo->dump_tail].data, curr_mbuf = m; curr_mbuf != NULL; curr_data += curr_mbuf->data_len, curr_mbuf = curr_mbuf->next) rte_memcpy(curr_data, (uint8_t *)curr_mbuf->buf_addr + m->data_off, curr_mbuf->data_len); ++pinfo->dump_tail; } /** * * pktgen_packet_dump_bulk - Dump packet contents. * * DESCRIPTION * Dump packet contents for later inspection. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_packet_dump_bulk(struct rte_mbuf **pkts, int nb_dump, int pid) { port_info_t *pinfo = l2p_get_port_pinfo(pid); int i; /* Don't dump more packets than the user asked */ if (nb_dump > pinfo->dump_count) nb_dump = pinfo->dump_count; /* Don't overflow packet array */ if (nb_dump > MAX_DUMP_PACKETS - pinfo->dump_tail) nb_dump = MAX_DUMP_PACKETS - pinfo->dump_tail; if (nb_dump == 0) return; for (i = 0; i < nb_dump; i++) pktgen_packet_dump(pkts[i], pid); pinfo->dump_count -= nb_dump; } /** * * pktgen_print_packet_dump - Print captured packets to the screen * * DESCRIPTION * When some packets are captured on user request, print the packet data to * the screen. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_print_packet_dump(void) { port_info_t *pinfo; unsigned int pid; unsigned int i, j; unsigned char *pdata; uint32_t plen; char buff[4096]; for (pid = 0; pid < RTE_MAX_ETHPORTS; pid++) { pinfo = l2p_get_port_pinfo(pid); if (pinfo == NULL) continue; for (; pinfo->dump_head < pinfo->dump_tail; ++pinfo->dump_head) { pdata = (unsigned char *)pinfo->dump_list[pinfo->dump_head].data; plen = pinfo->dump_list[pinfo->dump_head].len; snprintf(buff, sizeof(buff), "Port %d, packet with length %d:", pid, plen); for (i = 0; i < plen; i += 16) { strncatf(buff, "\n\t"); /* Byte counter */ strncatf(buff, "%06x: ", i); for (j = 0; j < 16; ++j) { /* Hex. value of character */ if (i + j < plen) strncatf(buff, "%02x ", pdata[i + j]); else strncatf(buff, " "); /* Extra padding after 8 hex values for readability */ if ((j + 1) % 8 == 0) strncatf(buff, " "); } /* Separate hex. values and raw characters */ strncatf(buff, "\t"); for (j = 0; j < 16; ++j) if (i + j < plen) strncatf(buff, "%c", isprint(pdata[i + j]) ? pdata[i + j] : '.'); } pktgen_log_info("%s", buff); rte_free(pinfo->dump_list[pinfo->dump_head].data); pinfo->dump_list[pinfo->dump_head].data = NULL; } } } ================================================ FILE: app/pktgen-dump.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_DUMP_H_ #define _PKTGEN_DUMP_H_ /** * @file * * Packet hex-dump capture and printing utilities for Pktgen debugging. */ #include #ifdef __cplusplus extern "C" { #endif /** Maximum number of packets that can be queued for dump output. */ #define MAX_DUMP_PACKETS 32 /** * Capture a single packet for later hex-dump output. * * @param m * Mbuf containing the packet to capture. * @param pid * Port index associated with the packet. */ void pktgen_packet_dump(struct rte_mbuf *m, int pid); /** * Capture up to @p nb_dump packets from an mbuf array for later hex-dump output. * * @param pkts * Array of mbufs to capture. * @param nb_dump * Number of mbufs in @p pkts to capture (capped at MAX_DUMP_PACKETS). * @param pid * Port index associated with the packets. */ void pktgen_packet_dump_bulk(struct rte_mbuf **pkts, int nb_dump, int pid); /** * Print all captured packet hex-dumps to the console and clear the dump queue. */ void pktgen_print_packet_dump(void); #ifdef __cplusplus } #endif #endif /* _PKTGEN_DUMP_H_ */ ================================================ FILE: app/pktgen-ether.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include "pktgen.h" #include "pktgen-ether.h" #include "pktgen-seq.h" #include "pktgen-port-cfg.h" /** * * pktgen_ether_hdr_ctor - Ethernet header constructor routine. * * DESCRIPTION * Construct the ethernet header for a given packet buffer. * * RETURNS: Pointer to memory after the ethernet header. * * SEE ALSO: */ char * pktgen_ether_hdr_ctor(port_info_t *pinfo, pkt_seq_t *pkt) { struct rte_ether_hdr *eth; uint16_t vlan_id; eth = &pkt->hdr->eth; /* src and dest addr */ rte_ether_addr_copy(&pkt->eth_src_addr, ð->src_addr); rte_ether_addr_copy(&pkt->eth_dst_addr, ð->dst_addr); if (pktgen_tst_port_flags(pinfo, SEND_VLAN_ID)) { /* vlan ethernet header */ eth->ether_type = htons(RTE_ETHER_TYPE_VLAN); /* only set the TCI field for now; don't bother with PCP/DEI */ struct rte_vlan_hdr *rte_vlan_hdr = (struct rte_vlan_hdr *)(eth + 1); vlan_id = (pkt->vlanid | (pkt->cos << 13)); rte_vlan_hdr->vlan_tci = htons(vlan_id); rte_vlan_hdr->eth_proto = htons(pkt->ethType); /* adjust header size for VLAN tag */ pkt->ether_hdr_size = sizeof(struct rte_ether_hdr) + sizeof(struct rte_vlan_hdr); return (char *)(rte_vlan_hdr + 1); } else if (pktgen_tst_port_flags(pinfo, SEND_MPLS_LABEL)) { /* MPLS unicast ethernet header */ eth->ether_type = htons(ETHER_TYPE_MPLS_UNICAST); mplsHdr_t *mpls_hdr = (mplsHdr_t *)(eth + 1); /* Only a single MPLS label is supported at the moment. Make sure the * BoS flag is set. */ uint32_t mpls_label = pkt->mpls_entry; MPLS_SET_BOS(mpls_label); mpls_hdr->label = htonl(mpls_label); /* Adjust header size for MPLS label */ pkt->ether_hdr_size = sizeof(struct rte_ether_hdr) + sizeof(mplsHdr_t); return (char *)(mpls_hdr + 1); } else if (pktgen_tst_port_flags(pinfo, SEND_Q_IN_Q_IDS)) { /* Q-in-Q ethernet header */ eth->ether_type = htons(ETHER_TYPE_Q_IN_Q); qinqHdr_t *qinq_hdr = (qinqHdr_t *)(eth + 1); /* only set the TCI field for now; don't bother with PCP/DEI */ qinq_hdr->qinq_tci = htons(pkt->qinq_outerid); qinq_hdr->vlan_tpid = htons(RTE_ETHER_TYPE_VLAN); qinq_hdr->vlan_tci = htons(pkt->qinq_innerid); qinq_hdr->eth_proto = htons(pkt->ethType); /* Adjust header size for Q-in-Q header */ pkt->ether_hdr_size = sizeof(struct rte_ether_hdr) + sizeof(qinqHdr_t); return (char *)(qinq_hdr + 1); } else { /* normal ethernet header */ eth->ether_type = htons(pkt->ethType); pkt->ether_hdr_size = sizeof(struct rte_ether_hdr); } #ifdef TX_DEBUG_PKT if (eth->dst_addr.addr_bytes[0] & 1) rte_hexdump(stdout, "Ether", eth, sizeof(struct rte_ether_hdr)); #endif return (char *)(eth + 1); } ================================================ FILE: app/pktgen-ether.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_ETHER_H_ #define _PKTGEN_ETHER_H_ /** * @file * * Ethernet header construction for Pktgen transmit packets. */ #include #include "pktgen-seq.h" #ifdef __cplusplus extern "C" { #endif struct port_info_s; /** * Construct the Ethernet header for a packet template. * * Fills in the destination and source MAC addresses and EtherType based on * the port configuration and packet sequence entry @p pkt. * * @param info * Port information providing source MAC and port-level configuration. * @param pkt * Packet sequence entry whose Ethernet header fields are populated. * @return * Pointer to the byte immediately following the Ethernet header within * the packet template buffer. */ char *pktgen_ether_hdr_ctor(struct port_info_s *info, pkt_seq_t *pkt); #ifdef __cplusplus } #endif #endif /* _PKTGEN_ETHER_H_ */ ================================================ FILE: app/pktgen-gre.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include #include "pktgen-gre.h" #include "pktgen.h" /** * * pktgen_gre_hdr_ctor - IPv4/GRE header construction routine. * * DESCRIPTION * Construct an IPv4/GRE header in a packet buffer. * * RETURNS: Pointer to memory after the GRE header. * * SEE ALSO: */ char * pktgen_gre_hdr_ctor(port_info_t *info __rte_unused, pkt_seq_t *pkt, greIp_t *gre) { /* Zero out the header space */ memset((char *)gre, 0, sizeof(greIp_t)); /* Create the IP header */ gre->ip.version_ihl = (IPv4_VERSION << 4) | (sizeof(struct rte_ipv4_hdr) / 4); gre->ip.type_of_service = 0; gre->ip.total_length = htons(pkt->pkt_size - pkt->ether_hdr_size); pktgen.ident += 27; /* bump by a prime number */ gre->ip.packet_id = htons(pktgen.ident); gre->ip.fragment_offset = 0; gre->ip.time_to_live = 64; gre->ip.next_proto_id = PG_IPPROTO_GRE; /* FIXME don't hardcode */ #define GRE_SRC_ADDR (10 << 24) | (10 << 16) | (1 << 8) | 1 #define GRE_DST_ADDR (10 << 24) | (10 << 16) | (1 << 8) | 2 gre->ip.src_addr = htonl(GRE_SRC_ADDR); gre->ip.dst_addr = htonl(GRE_DST_ADDR); #undef GRE_SRC_ADDR #undef GRE_DST_ADDR gre->ip.hdr_checksum = rte_ipv4_cksum(&gre->ip); /* Create the GRE header */ gre->gre.chk_present = 0; gre->gre.unused = 0; gre->gre.key_present = 1; gre->gre.seq_present = 0; gre->gre.reserved0_0 = 0; gre->gre.reserved0_1 = 0; gre->gre.version = 0; gre->gre.eth_type = htons(RTE_ETHER_TYPE_IPV4); /* FIXME get EtherType of the actual encapsulated * packet instead of defaulting to IPv4 */ int extra_count = 0; if (gre->gre.chk_present) /* The 16 MSBs of gre->gre.extra_fields[0] must be set to the IP (one's */ /* complement) checksum of the GRE header and the payload packet. */ /* Since the packet is still under construction at this moment, the */ /* checksum cannot be calculated. We just record the presence of this */ /* field, so the correct header length can be calculated. */ ++extra_count; if (gre->gre.key_present) { gre->gre.extra_fields[extra_count] = htonl(pkt->gre_key); ++extra_count; } if (gre->gre.seq_present) /* gre->gre.extra_fields[extra_count] = htonl(); */ /* TODO implement GRE sequence numbers */ ++extra_count; /* 4 * (3 - extra_count) is the amount of bytes that are not used by */ /* optional fields, but are included in sizeof(greIp_t). */ pkt->ether_hdr_size += sizeof(greIp_t) - 4 * (3 - extra_count); return (char *)(gre + 1) - 4 * (3 - extra_count); } /** * * pktgen_gre_ether_hdr_ctor - GRE/Ethernet header construction routine. * * DESCRIPTION * Construct a GRE/Ethernet header in a packet buffer. * * RETURNS: Pointer to memory after the GRE header. * * SEE ALSO: */ char * pktgen_gre_ether_hdr_ctor(port_info_t *info __rte_unused, pkt_seq_t *pkt, greEther_t *gre) { /* Zero out the header space */ memset((char *)gre, 0, sizeof(greEther_t)); /* Create the IP header */ gre->ip.version_ihl = (IPv4_VERSION << 4) | (sizeof(struct rte_ipv4_hdr) / 4); gre->ip.type_of_service = 0; gre->ip.total_length = htons(pkt->pkt_size - pkt->ether_hdr_size); pktgen.ident += 27; /* bump by a prime number */ gre->ip.packet_id = htons(pktgen.ident); gre->ip.fragment_offset = 0; gre->ip.time_to_live = 64; gre->ip.next_proto_id = PG_IPPROTO_GRE; /* FIXME don't hardcode */ #define GRE_SRC_ADDR (10 << 24) | (10 << 16) | (1 << 8) | 1 #define GRE_DST_ADDR (10 << 24) | (10 << 16) | (1 << 8) | 2 gre->ip.src_addr = htonl(GRE_SRC_ADDR); gre->ip.dst_addr = htonl(GRE_DST_ADDR); #undef GRE_SRC_ADDR #undef GRE_DST_ADDR gre->ip.hdr_checksum = rte_ipv4_cksum(&gre->ip); /* Create the GRE header */ gre->gre.chk_present = 0; gre->gre.unused = 0; gre->gre.key_present = 1; gre->gre.seq_present = 0; gre->gre.reserved0_0 = 0; gre->gre.reserved0_1 = 0; gre->gre.version = 0; gre->gre.eth_type = htons(ETHER_TYPE_TRANSP_ETH_BR); int extra_count = 0; if (gre->gre.chk_present) /* The 16 MSBs of gre->gre.extra_fields[0] must be set to the IP (one's */ /* complement) checksum of the GRE header and the payload packet. */ /* Since the packet is still under construction at this moment, the */ /* checksum cannot be calculated. We just record the presence of this */ /* field, so the correct header length can be calculated. */ ++extra_count; if (gre->gre.key_present) { gre->gre.extra_fields[extra_count] = htonl(pkt->gre_key); ++extra_count; } if (gre->gre.seq_present) /* gre->gre.extra_fields[extra_count] = htonl(); */ /* TODO implement GRE sequence numbers */ ++extra_count; /* Inner Ethernet header. Exact offset of start of ethernet header depends * on the presence of optional fields in the GRE header. */ struct rte_ether_hdr *eth_hdr = (struct rte_ether_hdr *)((char *)&gre->gre + 2 /* Flags and version */ + 2 /* Protocol type */ + 4 * extra_count); /* 4 bytes per optional field */ rte_ether_addr_copy(&pkt->eth_src_addr, ð_hdr->src_addr); /* FIXME get inner Ethernet parameters from user */ rte_ether_addr_copy(&pkt->eth_dst_addr, ð_hdr->dst_addr); /* FIXME get inner Ethernet parameters from user */ eth_hdr->ether_type = htons(RTE_ETHER_TYPE_IPV4); /* FIXME get Ethernet type from actual * encapsulated packet instead of hardcoding */ /* 4 * (3 - extra_count) is the amount of bytes that are not used by */ /* optional fields, but are included in sizeof(greIp_t). */ pkt->ether_hdr_size += sizeof(greEther_t) - 4 * (3 - extra_count); return (char *)(gre + 1) - 4 * (3 - extra_count); } ================================================ FILE: app/pktgen-gre.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_GRE_H_ #define _PKTGEN_GRE_H_ /** * @file * * GRE tunnel header construction for Pktgen transmit packets. */ #include #include "pktgen-port-cfg.h" #include "pktgen-seq.h" #ifdef __cplusplus extern "C" { #endif /** * Construct a GRE-over-IP header for a packet template. * * @param pinfo * Port information providing source IP and port-level configuration. * @param pkt * Packet sequence entry whose header fields are populated. * @param gre * Pointer to the GRE-over-IP header region in the packet buffer. * @return * Pointer to the byte immediately following the completed GRE header. */ char *pktgen_gre_hdr_ctor(port_info_t *pinfo, pkt_seq_t *pkt, greIp_t *gre); /** * Construct a GRE-over-Ethernet header for a packet template. * * @param pinfo * Port information providing source MAC and port-level configuration. * @param pkt * Packet sequence entry whose header fields are populated. * @param gre * Pointer to the GRE-over-Ethernet header region in the packet buffer. * @return * Pointer to the byte immediately following the completed GRE header. */ char *pktgen_gre_ether_hdr_ctor(port_info_t *pinfo, pkt_seq_t *pkt, greEther_t *gre); #ifdef __cplusplus } #endif #endif /* _PKTGEN_GRE_H_ */ ================================================ FILE: app/pktgen-gtpu.c ================================================ /*- * Copyright(c) <2015-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2015 by abhinandan.gujjar@intel.com */ #include #include "lua_config.h" #include "pktgen.h" #include "pktgen-gtpu.h" /** * * pktgen_gtpu_udp_hdr_ctor - GTP-U header constructor routine for TCP/UDP. * * DESCRIPTION * Construct the GTP-U header in a packer buffer. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_gtpu_hdr_ctor(pkt_seq_t *pkt, void *hdr, uint16_t ipProto, uint8_t flags, uint16_t seq_no, uint8_t npdu_no, uint8_t next_ext_hdr_type) { unsigned int l4HdrSize; unsigned int options = 0; gtpuHdr_t *gtppHdr; void *p; if (ipProto == PG_IPPROTO_UDP) l4HdrSize = sizeof(struct rte_udp_hdr); else l4HdrSize = sizeof(struct rte_tcp_hdr); gtppHdr = (gtpuHdr_t *)RTE_PTR_ADD(hdr, sizeof(struct rte_ipv4_hdr) + l4HdrSize); /* Zero out the header space */ memset((char *)gtppHdr, 0, sizeof(gtpuHdr_t)); /* Version: It is a 3-bit field. For GTPv1, this has a value of 1. */ gtppHdr->version_flags = flags; /* Message Type: an 8-bit field that indicates the type of GTP message. * Different types of messages are defined in 3GPP TS 29.060 section 7.1 */ gtppHdr->msg_type = 0xff; /* Tunnel endpoint identifier (TEID) * A 32-bit(4-octet) field used to multiplex different connections in the * same GTP tunnel. */ gtppHdr->teid = htonl(pkt->gtpu_teid); p = (void *)gtppHdr; if (gtppHdr->version_flags & GTPu_S_FLAG) { /* Sequence number an (optional) 16-bit field. * This field exists if any of the E, S, or PN bits are on. The field * must be interpreted only if the S bit is on. */ *(uint16_t *)p = seq_no; p = RTE_PTR_ADD(p, 2); gtppHdr->tot_len += 2; options += 2; } if (gtppHdr->version_flags & GTPu_PN_FLAG) { /* N-PDU number an (optional) 8-bit field. This field exists if any of * the E, S, or PN bits are on. * The field must be interpreted only if the PN bit is on. */ *(uint8_t *)p = npdu_no; p = RTE_PTR_ADD(p, 1); gtppHdr->tot_len++; options++; } if (gtppHdr->version_flags & GTPu_E_FLAG) { /* Next extension header type an (optional) 8-bit field. This field * exists if any of the E, S, or PN bits are on. * The field must be interpreted only if the E bit is on. */ *(uint8_t *)p = next_ext_hdr_type; p = RTE_PTR_ADD(p, 1); gtppHdr->tot_len++; options++; } /* Message Length - a 16-bit field that indicates the length of the payload * in bytes (rest of the packet following the mandatory 8-byte GTP header). * Includes the optional fields. */ gtppHdr->tot_len = htons(pkt->pkt_size - (l4HdrSize + sizeof(gtpuHdr_t) + pkt->ether_hdr_size)); } ================================================ FILE: app/pktgen-gtpu.h ================================================ /*- * Copyright(c) <2015-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2015 by abhinandan.gujjar@intel.com */ #ifndef _PKTGEN_GTPU_H_ #define _PKTGEN_GTPU_H_ /** * @file * * GTP-U header construction for Pktgen transmit packets. */ #include #include "pktgen-seq.h" #ifdef __cplusplus extern "C" { #endif /** * Construct a GTP-U header in the packet buffer. * * @param pkt * Packet sequence entry providing TEID and payload length. * @param hdr * Pointer to the start of the GTP-U header region in the packet buffer. * @param ipProto * Inner IP protocol type carried by the GTP-U tunnel. * @param flags * GTP-U flags byte (PT, E, S, PN bits). * @param seq_no * GTP-U sequence number (used when the S flag is set). * @param npdu_no * N-PDU number (used when the PN flag is set). * @param next_ext_hdr_type * Next extension header type (used when the E flag is set). */ void pktgen_gtpu_hdr_ctor(pkt_seq_t *pkt, void *hdr, uint16_t ipProto, uint8_t flags, uint16_t seq_no, uint8_t npdu_no, uint8_t next_ext_hdr_type); #ifdef __cplusplus } #endif #endif /* _PKTGEN_GTPU_H_ */ ================================================ FILE: app/pktgen-ipv4.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include #include "pktgen.h" #include "pktgen-log.h" #include "pktgen-ipv4.h" #include "pktgen-txbuff.h" #include "l2p.h" /** * * pktgen_ipv4_ctor - Construct the IPv4 header for a packet * * DESCRIPTION * Constructor for the IPv4 header for a given packet. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_ipv4_ctor(pkt_seq_t *pkt, void *hdr, bool cksum_offload) { struct rte_ipv4_hdr *ip = hdr; uint16_t tlen; /* IPv4 Header constructor */ tlen = pkt->pkt_size - pkt->ether_hdr_size; /* Zero out the header space */ memset((char *)ip, 0, sizeof(struct rte_ipv4_hdr)); ip->version_ihl = (IPv4_VERSION << 4) | (sizeof(struct rte_ipv4_hdr) / 4); ip->total_length = htons(tlen); ip->time_to_live = pkt->ttl; ip->type_of_service = pkt->tos; pktgen.ident += 27; /* bump by a prime number */ ip->packet_id = htons(pktgen.ident); ip->fragment_offset = 0; ip->next_proto_id = pkt->ipProto; ip->src_addr = htonl(pkt->ip_src_addr.addr.ipv4.s_addr); ip->dst_addr = htonl(pkt->ip_dst_addr.addr.ipv4.s_addr); ip->hdr_checksum = 0; if (!cksum_offload) ip->hdr_checksum = rte_ipv4_cksum((const struct rte_ipv4_hdr *)ip); } /** * * pktgen_send_ping4 - Create and send a Ping or ICMP echo packet. * * DESCRIPTION * Create a ICMP echo request packet and send the packet to a give port. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_send_ping4(uint32_t pid, uint8_t seq_idx) { port_info_t *pinfo = l2p_get_port_pinfo(pid); pkt_seq_t *ppkt = &pinfo->seq_pkt[SPECIAL_PKT]; pkt_seq_t *spkt = &pinfo->seq_pkt[seq_idx]; struct rte_mbuf *m; l2p_port_t *port; port = l2p_get_port(pid); { const uint16_t tx_qid = l2p_get_txqid(rte_lcore_id()); if (rte_mempool_get(port->sp_mp[tx_qid], (void **)&m)) { pktgen_log_warning("No packet buffers found"); return; } } *ppkt = *spkt; /* Copy the sequence setup to the ping setup. */ pktgen_packet_ctor(pinfo, SPECIAL_PKT, ICMP4_ECHO); rte_memcpy(rte_pktmbuf_mtod(m, uint8_t *), (uint8_t *)ppkt->hdr, ppkt->pkt_size); m->pkt_len = ppkt->pkt_size; m->data_len = ppkt->pkt_size; tx_send_packets(pinfo, l2p_get_txqid(rte_lcore_id()), &m, 1); } /** * * pktgen_process_ping4 - Process a input ICMP echo packet for IPv4. * * DESCRIPTION * Process a input packet for IPv4 ICMP echo request and send response if needed. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_process_ping4(struct rte_mbuf *m, uint32_t pid, uint32_t qid, uint32_t vlan) { port_info_t *pinfo = l2p_get_port_pinfo(pid); struct rte_ether_hdr *eth = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); struct rte_ipv4_hdr *ip = (struct rte_ipv4_hdr *)ð[1]; char buff[24]; /* Adjust for a vlan header if present */ if (vlan) ip = (struct rte_ipv4_hdr *)((char *)ip + sizeof(struct rte_vlan_hdr)); /* Look for a ICMP echo requests, but only if enabled. */ if ((rte_atomic64_read(&pinfo->port_flags) & ICMP_ECHO_ENABLE_FLAG) && (ip->next_proto_id == PG_IPPROTO_ICMP)) { struct rte_icmp_hdr *icmp = (struct rte_icmp_hdr *)((uintptr_t)ip + sizeof(struct rte_ipv4_hdr)); uint16_t cksum = ~rte_raw_cksum( icmp, (m->data_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr))); /* We do not handle IP options, which will effect the IP header size. */ if (unlikely(cksum != 0)) { pktgen_log_error("ICMP checksum failed"); return; } if (unlikely(icmp->icmp_type == ICMP4_ECHO)) { int idx; if (ntohl(ip->dst_addr) == INADDR_BROADCAST) { pktgen_log_warning("IP address %s is a Broadcast", inet_ntop4(buff, sizeof(buff), ip->dst_addr, INADDR_BROADCAST)); return; } /* Toss all broadcast addresses and requests not for this port */ idx = pktgen_find_matching_ipsrc(pinfo, ip->dst_addr); /* ARP request not for this interface. */ if (unlikely(idx == -1)) { pktgen_log_warning("IP address %s not found", inet_ntop4(buff, sizeof(buff), ip->dst_addr, INADDR_BROADCAST)); return; } icmp->icmp_type = ICMP4_ECHO_REPLY; /* Recompute the ICMP checksum */ icmp->icmp_cksum = 0; icmp->icmp_cksum = rte_raw_cksum( icmp, (m->data_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr))); /* Swap the IP addresses. */ inetAddrSwap(&ip->src_addr, &ip->dst_addr); /* Bump the ident value */ ip->packet_id = htons(ntohs(ip->packet_id) + m->data_len); /* Recompute the IP checksum */ ip->hdr_checksum = 0; ip->hdr_checksum = ~rte_raw_cksum(ip, sizeof(struct rte_ipv4_hdr)); /* Swap the MAC addresses */ ethAddrSwap(ð->dst_addr, ð->src_addr); tx_send_packets(pinfo, qid, &m, 1); /* No need to free mbuf as it was reused. */ return; } } } ================================================ FILE: app/pktgen-ipv4.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_IPV4_H_ #define _PKTGEN_IPV4_H_ /** * @file * * IPv4 header construction and ICMPv4 ping handling for Pktgen. */ #include "pktgen-seq.h" #ifdef __cplusplus extern "C" { #endif /** * Construct the IPv4 header for a packet template. * * @param pkt * Packet sequence entry providing IP addresses, protocol, and length. * @param hdr * Pointer to the start of the IPv4 header region in the packet buffer. * @param cksum_offload * When true, set the checksum to 0 and rely on hardware offload; * when false, compute the checksum in software. */ void pktgen_ipv4_ctor(pkt_seq_t *pkt, void *hdr, bool cksum_offload); /** * Transmit an ICMPv4 echo-request (ping) on port @p pid. * * @param pid * Port index to send the ping on. * @param seq_idx * Packet sequence slot index to use as the ping template. */ void pktgen_send_ping4(uint32_t pid, uint8_t seq_idx); /** * Process a received ICMPv4 echo-reply and update port statistics. * * @param m * Received mbuf containing the ICMP reply. * @param pid * Port index on which the packet arrived. * @param qid * Queue index on which the packet arrived. * @param vlan * VLAN ID extracted from the packet (0 if untagged). */ void pktgen_process_ping4(struct rte_mbuf *m, uint32_t pid, uint32_t qid, uint32_t vlan); #ifdef __cplusplus } #endif #endif /* _PKTGEN_IPV4_H_ */ ================================================ FILE: app/pktgen-ipv6.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include "pktgen.h" #include "pktgen-ipv6.h" /** * * pktgen_ipv6_ctor - IPv6 packet header constructor routine. * * DESCRIPTION * Construct the IPv6 header constructor routine. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_ipv6_ctor(pkt_seq_t *pkt, void *hdr) { struct rte_ipv6_hdr *ip = hdr; uint16_t tlen; /* IPv6 Header constructor */ memset(ip, 0, sizeof(struct rte_ipv6_hdr)); ip->vtc_flow = htonl(IPv6_VERSION << 28); ip->vtc_flow |= htonl(pkt->traffic_class << RTE_IPV6_HDR_TC_SHIFT); tlen = pkt->pkt_size - (pkt->ether_hdr_size + sizeof(struct rte_ipv6_hdr)); ip->payload_len = htons(tlen); ip->hop_limits = pkt->hop_limits; ip->proto = pkt->ipProto; rte_memcpy(&ip->dst_addr, &pkt->ip_dst_addr.addr.ipv6, sizeof(struct rte_ipv6_addr)); rte_memcpy(&ip->src_addr, &pkt->ip_src_addr.addr.ipv6, sizeof(struct rte_ipv6_addr)); } /** * * pktgen_process_ping6 - Process a IPv6 ICMP echo request packet. * * DESCRIPTION * Process a IPv6 ICMP echo request packet and send response if needed. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_process_ping6(struct rte_mbuf *m __rte_unused, uint32_t pid __rte_unused, uint32_t qid __rte_unused, uint32_t vlan __rte_unused) { #if 0 /* Broken needs to be updated to do IPv6 packets */ port_info_t *info = &pktgen.info[pid]; struct rte_ether_hdr *eth = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); struct rte_ipv6_hdr *ip = (struct rte_ipv6_hdr *)ð[1]; /* Adjust for a vlan header if present */ if (vlan) ip = (struct rte_ipv6_hdr *)((char *)ip + sizeof(struct rte_vlan_hdr)); /* Look for a ICMP echo requests, but only if enabled. */ if ( (rte_atomic32_read(&info->port_flags) & ICMP_ECHO_ENABLE_FLAG) && (ip->next_header == PG_IPPROTO_ICMPV6) ) { #if !defined(RTE_ARCH_X86_64) struct rte_icmp_hdr *icmp = (struct rte_icmp_hdr *)((uint32_t)ip + sizeof(struct rte_ipv4_hdr)); #else struct rte_icmp_hdr *icmp = (struct rte_icmp_hdr *)((uint64_t)ip + sizeof(struct rte_ipv4_hdr)); #endif /* We do not handle IP options, which will effect the IP header size. */ if (rte_ipv6_cksum(icmp, (m->pkt.data_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr))) ) { rte_printf_status("ICMP checksum failed\n"); goto leave : } if (icmp->type == ICMP4_ECHO) { /* Toss all broadcast addresses and requests not for this port */ if ( (ip->dst == INADDR_BROADCAST) || (ip->dst != info->ip_src_addr) ) { char buff[24]; rte_printf_status("IP address %s != ", inet_ntop4(buff, sizeof(buff), ip->dst, INADDR_BROADCAST)); rte_printf_status("%s\n", inet_ntop4(buff, sizeof(buff), htonl(info-> ip_src_addr), INADDR_BROADCAST)); goto leave; } icmp->type = ICMP4_ECHO_REPLY; /* Recompute the ICMP checksum */ icmp->cksum = 0; icmp->cksum = rte_raw_cksum(icmp, (m->pkt.data_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr))); /* Swap the IP addresses. */ inetAddrSwap(&ip->src, &ip->dst); /* Bump the ident value */ ip->ident = htons(ntohs(ip->ident) + m->pkt.data_len); /* Recompute the IP checksum */ ip->cksum = 0; ip->cksum = rte_raw_cksum(ip, sizeof(struct rte_ipv4_hdr)); /* Swap the MAC addresses */ ethAddrSwap(ð->d_addr, ð->s_addr); rte_eth_tx_buffer(pid, 0, info->q[0].txbuff, m); pktgen_set_q_flags(info, 0, DO_TX_FLUSH); /* No need to free mbuf as it was reused */ return; } } leave: #else #endif } ================================================ FILE: app/pktgen-ipv6.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_IPV6_H_ #define _PKTGEN_IPV6_H_ /** * @file * * IPv6 header construction and ICMPv6 ping handling for Pktgen. */ #include "pktgen.h" #ifdef __cplusplus extern "C" { #endif /** * Construct an IPv6 header in the packet buffer. * * @param pkt * Packet sequence entry providing source/destination IPv6 addresses, * traffic class, flow label, and next-header type. * @param hdr * Pointer to the start of the IPv6 header region in the packet buffer. */ void pktgen_ipv6_ctor(pkt_seq_t *pkt, void *hdr); /** * Process a received ICMPv6 echo-reply (ping6 response). * * @param m * Received mbuf containing the ICMPv6 reply. * @param pid * Port index on which the packet arrived. * @param qid * Queue index on which the packet arrived. * @param vlan * VLAN ID extracted from the outer header (0 if untagged). */ void pktgen_process_ping6(struct rte_mbuf *m, uint32_t pid, uint32_t qid, uint32_t vlan); #ifdef __cplusplus } #endif #endif /* _PKTGEN_IPV6_H_ */ ================================================ FILE: app/pktgen-latency.c ================================================ /*- * Copyright(c) <2016-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2016 by Keith Wiles @ intel.com */ #include #include "lua_config.h" #include "pktgen-cmds.h" #include "pktgen-display.h" #include "pktgen.h" #include #include void latency_set_rate(port_info_t *pinfo, uint32_t value) { latency_t *lat = &pinfo->latency; if (value == 0) value = DEFAULT_LATENCY_RATE; if (value > MAX_LATENCY_RATE) value = MAX_LATENCY_RATE; lat->latency_rate_us = value; lat->latency_rate_cycles = pktgen_get_timer_hz() / (MAX_LATENCY_RATE / lat->latency_rate_us); } void latency_set_entropy(port_info_t *pinfo, uint16_t value) { latency_t *lat = &pinfo->latency; lat->latency_entropy = value; } /** * * pktgen_print_static_data - Display the static data on the screen. * * DESCRIPTION * Display a set of port static data on the screen. * * RETURNS: N/A * * SEE ALSO: */ static void pktgen_print_static_data(void) { port_info_t *pinfo; struct rte_eth_dev_info dev = {0}; uint32_t pid, col, row, sp, ip_row; pkt_seq_t *pkt; char buff[32]; int display_cnt; pktgen_display_set_color("top.page"); display_topline("", 0, 0, 0); pktgen_display_set_color("top.ports"); scrn_printf(1, 3, "Ports %d-%d of %d", pktgen.starting_port, (pktgen.ending_port - 1), pktgen.nb_ports); row = PORT_FLAGS_ROW; pktgen_display_set_color("stats.port.label"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Port:Flags"); /* Labels for dynamic fields (update every second) */ pktgen_display_set_color("stats.dyn.label"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Link State"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Pkts/s Max/Rx"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Max/Tx"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "MBits/s Rx/Tx"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Latency:"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Rate (us)"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Entropy"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Total RX Pkts"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Total TX Pkts"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Skipped Pkts"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Cycles/Minimum(us)"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Cycles/Average(us)"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Cycles/Maximum(us)"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Percentiles:"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " 90th Cycles / us"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " 95th Cycles / us"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " 99th Cycles / us"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Jitter:"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Threshold (us)"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Count/Percent"); /* Labels for static fields */ pktgen_display_set_color("stats.stat.label"); ip_row = ++row; scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Pattern Type"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Tx Count/% Rate"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "PktSize/Rx:Tx Burst"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Src/Dest Port"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Type:VLAN ID:Flags"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Dst IP Address"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Src IP Address"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Dst MAC Address"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Src MAC Address"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "NUMA/Vend:ID/PCI"); /* Get the last location to use for the window starting row. */ pktgen.last_row = ++row; display_dashline(pktgen.last_row); /* Display the colon after the row label. */ pktgen_print_div(PORT_FLAGS_ROW, pktgen.last_row - 1, COLUMN_WIDTH_0 - 1); pktgen_display_set_color("stats.stat.values"); sp = pktgen.starting_port; display_cnt = 0; for (pid = 0; pid < pktgen.nb_ports_per_page; pid++) { pinfo = l2p_get_port_pinfo(pid + sp); if (pinfo == NULL) continue; pkt = &pinfo->seq_pkt[SINGLE_PKT]; pktgen_display_set_color("stats.stat.values"); /* Display Port information Src/Dest IP addr, Netmask, Src/Dst MAC addr */ col = (COLUMN_WIDTH_1 * pid) + COLUMN_WIDTH_0; row = ip_row; scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, (pinfo->fill_pattern_type == ABC_FILL_PATTERN) ? "abcd..." : (pinfo->fill_pattern_type == NO_FILL_PATTERN) ? "None" : (pinfo->fill_pattern_type == ZERO_FILL_PATTERN) ? "Zero" : pinfo->user_pattern); pktgen_transmit_count_rate(pid, buff, sizeof(buff)); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); snprintf(buff, sizeof(buff), "%d /%3d:%3d", pkt->pkt_size + RTE_ETHER_CRC_LEN, pinfo->rx_burst, pinfo->tx_burst); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); snprintf(buff, sizeof(buff), "%d/%5d/%5d", pkt->ttl, pkt->sport, pkt->dport); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); snprintf(buff, sizeof(buff), "%s / %s:%04x:%04x", (pkt->ethType == RTE_ETHER_TYPE_IPV4) ? "IPv4" : (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? "IPv6" : (pkt->ethType == RTE_ETHER_TYPE_ARP) ? "ARP" : "Other", (pkt->ipProto == PG_IPPROTO_TCP) ? "TCP" : (pkt->ipProto == PG_IPPROTO_ICMP) ? "ICMP" : "UDP", pkt->vlanid, pkt->tcp_flags); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); scrn_printf( row++, col, "%*s", COLUMN_WIDTH_1, inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_dst_addr.addr.ipv4.s_addr), 0xFFFFFFFF)); scrn_printf( row++, col, "%*s", COLUMN_WIDTH_1, inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_src_addr.addr.ipv4.s_addr), pkt->ip_mask)); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, inet_mtoa(buff, sizeof(buff), &pkt->eth_dst_addr)); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, inet_mtoa(buff, sizeof(buff), &pkt->eth_src_addr)); if (rte_eth_dev_info_get(pid, &dev) < 0) rte_exit(EXIT_FAILURE, "Cannot get device info for port %u", pid); const struct rte_bus *bus = NULL; if (dev.device) bus = rte_bus_find_by_device(dev.device); if (bus && !strcmp(rte_bus_name(bus), "pci")) { char name[RTE_ETH_NAME_MAX_LEN]; char vend[8], device[8]; vend[0] = device[0] = '\0'; sscanf(rte_dev_bus_info(dev.device), "vendor_id=%4s, device_id=%4s", vend, device); rte_eth_dev_get_name_by_port(pid, name); snprintf(buff, sizeof(buff), "%d/%s:%s/%s", rte_dev_numa_node(dev.device), vend, device, rte_dev_name(dev.device)); } else snprintf(buff, sizeof(buff), "-1/0000:0000/00:00.0"); pktgen_display_set_color("stats.bdf"); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); display_cnt++; } /* Display the string for total pkts/s rate of all ports */ col = (COLUMN_WIDTH_1 * display_cnt) + COLUMN_WIDTH_0; pktgen_display_set_color("stats.total.label"); scrn_printf(LINK_STATE_ROW, col, "%*s", COLUMN_WIDTH_3, "----TotalRate----"); scrn_eol(); pktgen_display_set_color(NULL); pktgen.flags &= ~PRINT_LABELS_FLAG; } /* comparator function for qsort ring */ static int cmp_uint64_asc(const void *a, const void *b) { uint64_t va = *(const uint64_t *)a; uint64_t vb = *(const uint64_t *)b; if (va < vb) return -1; if (va > vb) return 1; return 0; } /* returns quantile (0.9, 0.95, 0.99) */ static uint64_t latency_ring_percentile(const latency_ring_t *ring, double q) { if (ring->count == 0) return 0; uint64_t tmp[RING_SIZE]; for (int i = 0; i < ring->count; i++) { tmp[i] = ring->data[i]; } qsort(tmp, ring->count, sizeof(uint64_t), cmp_uint64_asc); // percentile position (es. 0.9 == 90%) double pos = q * (ring->count - 1); int idx = (int)pos; double frac = pos - idx; if (idx + 1 < ring->count) return tmp[idx] + (uint64_t)((tmp[idx + 1] - tmp[idx]) * frac); else return tmp[idx]; } static inline double cycles_to_us(uint64_t cycles, double per_cycle) { return (cycles == 0) ? 0.0 : (per_cycle * (double)cycles) * Million; } /** * * pktgen_page_latency - Display the latency on the screen for all ports. * * DESCRIPTION * Display the port latency on the screen for all ports. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_page_latency(void) { port_info_t *pinfo; latency_t *lat; unsigned int pid, col, row; unsigned sp, nb_pkts; char buff[32]; int display_cnt; double latency, per_cycle; if (pktgen.flags & PRINT_LABELS_FLAG) pktgen_print_static_data(); memset(&pktgen.cumm_rate_totals, 0, sizeof(struct rte_eth_stats)); sp = pktgen.starting_port; display_cnt = 0; per_cycle = (1.0 / pktgen.hz); for (pid = 0; pid < pktgen.nb_ports_per_page; pid++) { pinfo = l2p_get_port_pinfo(pid + sp); if (pinfo == NULL) continue; /* Display the disable string when port is not enabled. */ col = (COLUMN_WIDTH_1 * pid) + COLUMN_WIDTH_0; row = PORT_FLAGS_ROW; /* Display the port number for the column */ snprintf(buff, sizeof(buff), "%d:%s", pid + sp, pktgen_flags_string(pinfo)); pktgen_display_set_color("stats.port.flags"); scrn_printf(row, col, "%*s", COLUMN_WIDTH_1, buff); pktgen_display_set_color(NULL); row = LINK_STATE_ROW; /* Grab the link state of the port and display Duplex/Speed and UP/Down */ pktgen_get_link_status(pinfo); pktgen_link_state(pid, buff, sizeof(buff)); pktgen_display_set_color("stats.port.status"); scrn_printf(row, col, "%*s", COLUMN_WIDTH_1, buff); pktgen_display_set_color(NULL); pktgen_display_set_color("stats.stat.values"); /* Rx/Tx pkts/s rate */ row = LINK_STATE_ROW + 1; snprintf(buff, sizeof(buff), "%'" PRIu64 "/%'" PRIu64, iBitsTotal(pinfo->stats.rate) / Million, oBitsTotal(pinfo->stats.rate) / Million); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); pktgen.cumm_rate_totals.ipackets += pinfo->stats.rate.ipackets; pktgen.cumm_rate_totals.opackets += pinfo->stats.rate.opackets; pktgen.cumm_rate_totals.ibytes += pinfo->stats.rate.ibytes; pktgen.cumm_rate_totals.obytes += pinfo->stats.rate.obytes; pktgen.cumm_rate_totals.ierrors += pinfo->stats.rate.ierrors; pktgen.cumm_rate_totals.oerrors += pinfo->stats.rate.oerrors; pktgen.cumm_rate_totals.imissed += pinfo->stats.rate.imissed; pktgen.cumm_rate_totals.rx_nombuf += pinfo->stats.rate.rx_nombuf; row++; /* Skip Latency header row */ lat = &pinfo->latency; /* Ensure all RX worker stores to latency fields are visible before reading */ rte_smp_rmb(); nb_pkts = (lat->num_latency_pkts == 0) ? 1 : lat->num_latency_pkts; uint64_t avg_cycles = lat->running_cycles / nb_pkts; /* compute locally; do not write back */ snprintf(buff, sizeof(buff), "%'" PRIu64, lat->latency_rate_us); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); snprintf(buff, sizeof(buff), "%'" PRIu16, lat->latency_entropy); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); snprintf(buff, sizeof(buff), "%'" PRIu64, lat->num_latency_pkts); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); snprintf(buff, sizeof(buff), "%'" PRIu64, lat->num_latency_tx_pkts); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); snprintf(buff, sizeof(buff), "%'" PRIu64, lat->num_skipped); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); latency = cycles_to_us(lat->min_cycles, per_cycle); snprintf(buff, sizeof(buff), "%'" PRIu64 "/%'8.2f", lat->min_cycles, latency); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); latency = cycles_to_us(avg_cycles, per_cycle); snprintf(buff, sizeof(buff), "%'" PRIu64 "/%'8.2f", avg_cycles, latency); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); latency = cycles_to_us(lat->max_cycles, per_cycle); snprintf(buff, sizeof(buff), "%'" PRIu64 "/%'8.2f", lat->max_cycles, latency); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); row++; /* Skip Percentiles header */ double q90, q95, q99; q90 = cycles_to_us(latency_ring_percentile(&lat->tail_latencies, 0.90), per_cycle); snprintf(buff, sizeof(buff), "%'" PRIu64 "/%'8.2f", latency_ring_percentile(&lat->tail_latencies, 0.90), q90); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); q95 = cycles_to_us(latency_ring_percentile(&lat->tail_latencies, 0.95), per_cycle); snprintf(buff, sizeof(buff), "%'" PRIu64 "/%'8.2f", latency_ring_percentile(&lat->tail_latencies, 0.95), q95); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); q99 = cycles_to_us(latency_ring_percentile(&lat->tail_latencies, 0.99), per_cycle); snprintf(buff, sizeof(buff), "%'" PRIu64 "/%'8.2f", latency_ring_percentile(&lat->tail_latencies, 0.99), q99); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); row++; /* Skip Jitter header */ snprintf(buff, sizeof(buff), "%'" PRIu64, lat->jitter_threshold_us); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); snprintf(buff, sizeof(buff), "%'" PRIu64 "/%'6.2f", lat->jitter_count, (double)(lat->jitter_count * 100) / nb_pkts); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); display_cnt++; } /* Display the total pkts/s for all ports */ col = (COLUMN_WIDTH_1 * display_cnt) + COLUMN_WIDTH_0; row = LINK_STATE_ROW + 1; snprintf(buff, sizeof(buff), "%'lu/%'lu", pktgen.max_total_ipackets, pktgen.cumm_rate_totals.ipackets); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_3, buff); scrn_eol(); snprintf(buff, sizeof(buff), "%'lu/%'lu", pktgen.max_total_opackets, pktgen.cumm_rate_totals.opackets); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_3, buff); scrn_eol(); snprintf(buff, sizeof(buff), "%lu/%lu", iBitsTotal(pktgen.cumm_rate_totals) / Million, oBitsTotal(pktgen.cumm_rate_totals) / Million); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_3, buff); scrn_eol(); } void pktgen_latency_setup(port_info_t *pinfo) { pkt_seq_t *pkt = &pinfo->seq_pkt[LATENCY_PKT]; rte_memcpy(pkt, &pinfo->seq_pkt[SINGLE_PKT], sizeof(pkt_seq_t)); pkt->pkt_size = LATENCY_PKT_SIZE; pkt->ipProto = PG_IPPROTO_UDP; pkt->ethType = RTE_ETHER_TYPE_IPV4; pkt->dport = LATENCY_DPORT; } ================================================ FILE: app/pktgen-latency.h ================================================ /*- * Copyright(c) <2016-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2016 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_LATENCY_H_ #define _PKTGEN_LATENCY_H_ /** * @file * * One-way latency measurement support for Pktgen. * * Implements timestamped probe-packet injection, latency histogram * collection, jitter tracking, and the latency statistics display page. */ #include #ifdef __cplusplus extern "C" { #endif #define DEFAULT_JITTER_THRESHOLD (50) /**< Default jitter threshold in microseconds */ #define DEFAULT_LATENCY_RATE (10000) /**< Default probe injection rate in microseconds */ #define MAX_LATENCY_RATE (1000000) /**< Maximum allowed probe injection rate in microseconds */ #define DEFAULT_LATENCY_ENTROPY (0) /**< Default entropy seed for source port randomisation */ #define LATENCY_PKT_SIZE RTE_ETHER_MIN_LEN /**< Latency probe packet size (64 B + 4 B FCS) */ #define LATENCY_DPORT 1028 /**< Destination UDP port used for latency probes */ /** * Render the latency statistics display page to the console. */ void pktgen_page_latency(void); /** * Initialise latency measurement state for port @p pinfo to default values. * * @param pinfo * Port information structure to initialise. */ void pktgen_latency_setup(port_info_t *pinfo); /** * Set the latency probe injection rate for port @p pinfo. * * @param pinfo * Port information structure to update. * @param value * Probe injection interval in microseconds (1–MAX_LATENCY_RATE). */ void latency_set_rate(port_info_t *pinfo, uint32_t value); /** * Set the source-port entropy value used for latency probe identification. * * The entropy value controls how many distinct source ports are cycled * (SPORT + (i % entropy)); a value of 0 disables cycling. * * @param pinfo * Port information structure to update. * @param value * Entropy range in [0, 0xFFFF]. */ void latency_set_entropy(port_info_t *pinfo, uint16_t value); #ifdef __cplusplus } #endif #endif /* _PKTGEN_LATENCY_H_ */ ================================================ FILE: app/pktgen-log.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include "pktgen-log.h" #include #include #include #include #include #include #include #include #include "lua_config.h" #include "pktgen-display.h" #include /* Log sizes and data structure */ #define LOG_HISTORY 64 /* log "scrollback buffer" size */ #define LOG_MAX_LINE 1024 /* max. length of a log line */ /* Log message and metadata */ typedef struct log_msg_s { struct timeval tv; /**< Timestamp */ int level; /**< Log level */ char *file; /**< Source file of the caller */ long line; /**< Line number of the caller */ char *func; /**< Function name of the caller */ char msg[LOG_MAX_LINE]; /**< Log message */ } log_msg_t; /* Log history */ typedef struct log_s { log_msg_t msg[LOG_HISTORY]; /**< Log message buffer */ uint16_t head; /**< index of most recent log msg */ uint16_t tail; /**< index of oldest log msg */ uint8_t need_refresh; /**< log page doesn't contain the latest messages */ rte_rwlock_t lock; /**< multi-threaded list lock */ } log_t; log_t log_history; FILE *log_file = NULL; int log_level_screen = LOG_LEVEL_INFO; /* Forward declarations of log entry formatting functions */ static const char *pktgen_format_msg_page(const log_msg_t *log_msg); static const char *pktgen_format_msg_file(const log_msg_t *log_msg); static const char *pktgen_format_msg_stdout(const log_msg_t *log_msg); /* Initialize screen data structures */ void pktgen_init_log(void) { memset(&log_history, 0, sizeof(log_history)); log_history.head = 0; log_history.tail = 0; log_history.need_refresh = 0; } /* Set minimum message level for printing to screen */ void pktgen_log_set_screen_level(int level) { log_level_screen = level; } /* Log the provided message to the log screen and optionally a file. */ void __attribute__((format(printf, 5, 6))) pktgen_log(int level, const char *file, long line, const char *func, const char *fmt, ...) { log_msg_t *curr_msg; va_list args; rte_rwlock_write_lock(&log_history.lock); curr_msg = &log_history.msg[log_history.head]; /* log message metadata */ gettimeofday(&curr_msg->tv, NULL); curr_msg->level = level; if (curr_msg->file != NULL) free(curr_msg->file); curr_msg->file = strdup(file); curr_msg->line = line; if (curr_msg->func != NULL) free(curr_msg->func); curr_msg->func = strdup(func); /* actual log message */ va_start(args, fmt); vsnprintf(curr_msg->msg, LOG_MAX_LINE, fmt, args); va_end(args); if (curr_msg->msg[strlen(curr_msg->msg) - 1] == '\n') curr_msg->msg[strlen(curr_msg->msg) - 1] = '\0'; /* Adjust head and tail indexes: head must point one beyond the last valid * entry, tail must move one entry if head has caught up. * The array acts as a circular buffer, so if either head or tail move * beyond the last array element, they are wrapped around. */ log_history.head = (log_history.head + 1) % LOG_HISTORY; if (log_history.head == log_history.tail) log_history.tail = (log_history.tail + 1) % LOG_HISTORY; /* Log to file if enabled */ if (log_file != NULL) fprintf(log_file, "%s\n", pktgen_format_msg_file(curr_msg)); /* Print message to screen if its level is high enough. */ if (level >= log_level_screen) fprintf(stdout, "%s\n", pktgen_format_msg_stdout(curr_msg)); log_history.need_refresh = 1; rte_rwlock_write_unlock(&log_history.lock); } /* Open file on disk for logging. */ void pktgen_log_set_file(const char *filename) { FILE *fp; /* Clean up if already logging to a file */ if (log_file != NULL) { fchmod(fileno(log_file), 0666); fclose(log_file); log_file = NULL; } /* No filename given: disable logging to disk */ if (filename == NULL) return; fp = fopen(filename, "w"); if (fp == NULL) pktgen_log_warning("Unable to open log file '%s' for writing", filename); /* Unbuffered output if file is successfully opened */ if (fp != NULL) setbuf(fp, NULL); log_file = fp; } /* Display log page on the screen */ void pktgen_page_log(uint32_t print_labels) { /* Maximum number of log lines to display */ #define MAX_PAGE_LINES 28 uint32_t row, curr_msg, output_lines, curr_char; int curr_line; char lines[MAX_PAGE_LINES][LOG_MAX_LINE]; if (!print_labels && !log_history.need_refresh) return; pktgen_display_set_color("top.page"); display_topline("", 0, 0, 0); row = PORT_FLAGS_ROW; pktgen_display_set_color("stats.stat.label"); /* Header line */ scrn_printf(row++, 1, "%1s %8s %-32s %s", "L", "Time", "Function", "Message"); pktgen_display_set_color("stats.stat.values"); curr_line = output_lines = 0; curr_msg = log_history.head; while ((curr_msg != log_history.tail) && (output_lines < MAX_PAGE_LINES)) { /* Go backwards and wrap around */ curr_msg = (curr_msg + LOG_HISTORY - 1) % LOG_HISTORY; snprintf(lines[curr_line], LOG_MAX_LINE, "%s", pktgen_format_msg_page(&log_history.msg[curr_msg])); /* Count number of lines occupied by current log entry. Line wrapping * because of screen width is not counted, \n's embedded in the log * message are counted. */ for (curr_char = 0; lines[curr_line][curr_char] != '\0'; ++curr_char) if (lines[curr_line][curr_char] == '\n') ++output_lines; ++output_lines; /* First line before possible \n's */ ++curr_line; } /* The lines[] array contains the messages to print on the screen, with * the most recent message first. * Iterating backwards gives the messages in chronological order. * curr_line points 1 beyond the last entry in lines[]. */ for (--curr_line; curr_line >= 0; --curr_line) { scrn_printf(row++, 1, "%s", lines[curr_line]); /* Increase row for each embedded \n */ for (curr_char = 0; lines[curr_line][curr_char] != '\0'; ++curr_char) if (lines[curr_line][curr_char] == '\n') ++row; } if (row < MAX_PAGE_LINES) row = MAX_PAGE_LINES; display_dashline(++row); log_history.need_refresh = 0; pktgen_display_set_color(NULL); #undef MAX_PAGE_LINES } /** * * pktgen_format_msg_page - formats the log entry for the log page * * DESCRIPTION * Generates a string representation of the log entry, suitable for the log page. * No effort is made to prettify multi-line messages: if indentation * of multiple lines is required, the log msg itself must contain appropriate * whitespace. * * RETURNS: Pointer to formatted string. The memory associated with the pointer * is managed by this function and must not be free'd by the calling * function. * The memory pointed to may be altered on subsequent calls to this * function. Copy the result if needed. * * SEE ALSO: */ static const char * pktgen_format_msg_page(const log_msg_t *log_msg) { /* Example log line: * I 13:37:05 bar_func This is a message */ static char msg[LOG_MAX_LINE] = {0}; char timestamp[9] = {0}; char func[32]; strftime(timestamp, sizeof(timestamp), "%H:%M:%S", localtime(&log_msg->tv.tv_sec)); if (strlen(log_msg->func) > sizeof(func) - 1) snprintf(func, sizeof(func), "…%s", &log_msg->func[strlen(log_msg->func) - sizeof(func) - 2]); else sprintf(func, "%s", log_msg->func); snprintf(msg, sizeof(msg), "%1s %8s %-*s %s", (log_msg->level == LOG_LEVEL_TRACE) ? "t" : (log_msg->level == LOG_LEVEL_DEBUG) ? "d" : (log_msg->level == LOG_LEVEL_INFO) ? "I" : (log_msg->level == LOG_LEVEL_WARNING) ? "W" : (log_msg->level == LOG_LEVEL_ERROR) ? "E" : (log_msg->level == LOG_LEVEL_PANIC) ? "P" : "?", timestamp, (int)sizeof(func), func, log_msg->msg); return msg; } /** * * pktgen_format_msg_file - formats the log entry for output to disk * * DESCRIPTION * Generates a string representation of the log entry, suitable for writing to * disk. * The output is more verbose than the output of the format log functions for * stdout and page. * No effort is made to prettify multi-line messages: if indentation * of multiple lines is required, the log msg itself must contain appropriate * whitespace. * * RETURNS: Pointer to formatted string. The memory associated with the pointer * is managed by this function and must not be free'd by the calling * function. * The memory pointed to may be altered on subsequent calls to this * function. Copy the result if needed. * * SEE ALSO: */ static const char * pktgen_format_msg_file(const log_msg_t *log_msg) { /* Example log line: * II 2014-03-14 13:37:05.123 [foo.c:42(bar_func)] This is a message */ static char msg[LOG_MAX_LINE] = {0}; char timestamp[32] = {0}; char *file; strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", localtime(&log_msg->tv.tv_sec)); file = strdup(log_msg->file); snprintf(msg, sizeof(msg), "%s %s.%03ld [%s:%ld(%s)] %s", (log_msg->level == LOG_LEVEL_TRACE) ? "tt" : (log_msg->level == LOG_LEVEL_DEBUG) ? "dd" : (log_msg->level == LOG_LEVEL_INFO) ? "II" : (log_msg->level == LOG_LEVEL_WARNING) ? "WW" : (log_msg->level == LOG_LEVEL_ERROR) ? "EE" : (log_msg->level == LOG_LEVEL_PANIC) ? "PP" : "??", timestamp, log_msg->tv.tv_usec / 1000, basename(file), log_msg->line, log_msg->func, log_msg->msg); free(file); return msg; } /** * * pktgen_format_msg_stdout - formats the log entry for output to screen * * DESCRIPTION * Generates a string representation of the log entry, suitable for writing to * the screen. * For info mesaages, just the message is printed. Warnings and more severe * messages get an appropriate label. * No effort is made to prettify multi-line messages: if indentation * of multiple lines is required, the log msg itself must contain appropriate * whitespace. * * RETURNS: Pointer to formatted string. The memory associated with the pointer * is managed by this function and must not be free'd by the calling * function. * The memory pointed to may be altered on subsequent calls to this * function. Copy the result if needed. * * SEE ALSO: */ static const char * pktgen_format_msg_stdout(const log_msg_t *log_msg) { /* Example log line: * This is a message */ static char msg[LOG_MAX_LINE] = {0}; snprintf(msg, sizeof(msg), "%s%s", (log_msg->level <= LOG_LEVEL_INFO) ? "" : (log_msg->level == LOG_LEVEL_WARNING) ? "WARNING: " : (log_msg->level == LOG_LEVEL_ERROR) ? "!ERROR!: " : (log_msg->level == LOG_LEVEL_PANIC) ? "!PANIC!: " : "??? ", log_msg->msg); return msg; } ================================================ FILE: app/pktgen-log.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_LOG_H_ #define _PKTGEN_LOG_H_ #include #ifdef __cplusplus extern "C" { #endif /* Log levels. Each log level has an associated pktgen_log_() * function. */ #define LOG_LEVEL_ALL 0 #define LOG_LEVEL_TRACE 1 #define LOG_LEVEL_DEBUG 2 #define LOG_LEVEL_INFO 3 #define LOG_LEVEL_WARNING 4 #define LOG_LEVEL_ERROR 5 #define LOG_LEVEL_PANIC 6 #define LOG_LEVEL_NONE 7 /* Set default minimum message level to log if one isn't provided at compile * time. All pktgen_log_() calls with a log level lower than the one * specified below won't even be compiled. * More detailed logs have a negative performance impact, which is undesirable * in a production build. */ #ifndef LOG_LEVEL #define LOG_LEVEL LOG_LEVEL_INFO #endif /* Conditionally generate code for pktgen_log_() functions, depending on * the minimum requested log level. */ #if LOG_LEVEL <= LOG_LEVEL_TRACE #define pktgen_log_trace(fmt, ...) \ pktgen_log(LOG_LEVEL_TRACE, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__) #else #define pktgen_log_trace(fmt, ...) /* no-op */ #endif #if LOG_LEVEL <= LOG_LEVEL_DEBUG #define pktgen_log_debug(fmt, ...) \ pktgen_log(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__) #else #define pktgen_log_debug(fmt, ...) /* no-op */ #endif #if LOG_LEVEL <= LOG_LEVEL_INFO #define pktgen_log_info(fmt, ...) \ pktgen_log(LOG_LEVEL_INFO, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__) #else #define pktgen_log_info(fmt, ...) /* no-op */ #endif #if LOG_LEVEL <= LOG_LEVEL_WARNING #define pktgen_log_warning(fmt, ...) \ pktgen_log(LOG_LEVEL_WARNING, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__) #else #define pktgen_log_warning(fmt, ...) /* no-op */ #endif #if LOG_LEVEL <= LOG_LEVEL_ERROR #define pktgen_log_error(fmt, ...) \ pktgen_log(LOG_LEVEL_ERROR, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__) #else #define pktgen_log_error(fmt, ...) /* no-op */ #endif #if LOG_LEVEL <= LOG_LEVEL_PANIC #define pktgen_log_panic(fmt, ...) \ do { \ scrn_destroy(); \ pktgen_log(LOG_LEVEL_PANIC, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \ rte_panic(fmt "\n", ##__VA_ARGS__); \ } while (0) #else #define pktgen_log_panic(fmt, ...) \ do { \ scrn_destroy(); \ rte_panic(fmt, ##__VA_ARGS__); \ } while (0) #endif /* Helper for building log strings. * The macro takes an existing string, a printf-like format string and optional * arguments. It formats the string and appends it to the existing string, while * avoiding possible buffer overruns. */ #define strncatf(dest, fmt, ...) \ do { \ char _buff[1023]; \ snprintf(_buff, sizeof(_buff), fmt, ##__VA_ARGS__); \ strncat(dest, _buff, sizeof(dest) - strlen(dest) - 1); \ } while (0) /* Initialize log data structures */ void pktgen_init_log(void); /** * * pktgen_log_set_screen_level - Set level of messages that are printed to the screen * * DESCRIPTION * Messages of the specified level or higher are printed to the screen * in addition to being logged to the log page and optionally to a file. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_log_set_screen_level(int level); /** * * pktgen_log - printf-like function for logging * * DESCRIPTION * Log the provided message to the log page and to a file if enabled. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_log(int level, const char *file, long line, const char *func, const char *fmt, ...); /** * * pktgen_log_set_file - Start logging to a file * * DESCRIPTION * Writes the log to the provided filename. If the file already exists, it is * truncated. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_log_set_file(const char *filename); /** * * pktgen_page_log - Display the log page. * * DESCRIPTION * Display the log page on the screen. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_page_log(uint32_t print_labels); #ifdef __cplusplus } #endif #endif /* _PKTGEN_LOG_H_ */ ================================================ FILE: app/pktgen-main.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include #include #ifdef LUA_ENABLED #include #endif #include #include "pktgen-main.h" #include "pktgen.h" #ifdef LUA_ENABLED #include "lpktgenlib.h" #include "lauxlib.h" #endif #include "pktgen-cmds.h" #include "pktgen-cpu.h" #include "pktgen-display.h" #include "pktgen-log.h" #include "cli-functions.h" #ifdef LUA_ENABLED /** * * pktgen_get_lua - Get Lua state pointer. * * DESCRIPTION * Get the Lua state pointer value. * * RETURNS: Lua pointer * * SEE ALSO: */ void * pktgen_get_lua(void) { return pktgen.ld->L; } #endif /** * * pktgen_usage - Display the help for the command line. * * DESCRIPTION * Display the help message for the command line. * * RETURNS: N/A * * SEE ALSO: */ static void pktgen_usage(const char *prgname) { printf("Usage: %s [EAL options] -- [-h] [-v] [-P] [-G] [-g host:port] [-T] [-f cmd_file] [-l " "log_file] [-s P:filepath] [-m ]\n" #ifdef LUA_ENABLED " -f filename Command file (.pkt) to execute or a Lua script (.lua) file\n" #else " -f filename Command file (.pkt) to execute\n" #endif " -l filename Write log to filename\n" " -s P:filepath PCAP packet stream file, 'P' is the port number\n" " -P Enable PROMISCUOUS mode on all ports\n" " -g address Optional IP address and port number default is (localhost:0x5606)\n" " If -g is used that enable socket support as a server application\n" " -G Enable socket support using default server values localhost:0x5606 \n" " -N Enable NUMA support\n" " -T Enable the color output\n" " -v Verbose output\n" " -j Enable jumbo frames of 9600 bytes\n" " -c Enable clock_gettime\n" " --txd=N set the number of descriptors in Tx rings to N \n" " --rxd=N set the number of descriptors in Rx rings to N \n" " -m matrix for mapping ports to logical cores\n" " BNF: (or kind of BNF)\n" " := \"\"\" { \",\" } \"\"\"\n" " := \".\" \n" " := \"[\" \":\" \"]\"\n" " := \"\"\n" " := { \"/\" ( | ) }\n" " := { \"/\" ( | ) }\n" " := { \"/\" ( | ) }\n" " := \"-\" { \"/\" }\n" " := +\n" " := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9\n" " 1.0, 2.1, 3.2 - core 1 handles port 0 rx/tx,\n" " core 2 handles port 1 rx/tx\n" " core 3 handles port 2 rx/tx\n" " 1.[0-2], 2.3, ... - core 1 handle ports 0,1,2 rx/tx,\n" " core 2 handle port 3 rx/tx\n" " [0-1].0, [2/4-5].1, ... - cores 0-1 handle port 0 rx/tx,\n" " cores 2,4,5 handle port 1 rx/tx\n" " [1:2].0, [4:6].1, ... - core 1 handles port 0 rx,\n" " core 2 handles port 0 tx,\n" " [1:2-3].0, [4:5-6].1, ... - core 1 handles port 0 rx, cores 2,3 handle port " "0 tx\n" " core 4 handles port 1 rx & core 5,6 handles port " "1 tx\n" " [1-2:3].0, [4-5:6].1, ... - core 1,2 handles port 0 rx, core 3 handles port " "0 tx\n" " core 4,5 handles port 1 rx & core 6 handles port " "1 tx\n" " [1-2:3-5].0, [4-5:6/8].1, ... - core 1,2 handles port 0 rx, core 3,4,5 handles " "port 0 tx\n" " core 4,5 handles port 1 rx & core 6,8 handles " "port 1 tx\n" " BTW: you can use \"{}\" instead of \"[]\" as it does not matter to the syntax.\n" " -h Display the help information\n", prgname); } /** * * pktgen_parse_args - Main parsing routine for the command line. * * DESCRIPTION * Main parsing routine for the command line. * * RETURNS: N/A * * SEE ALSO: */ static int pktgen_parse_args(int argc, char **argv) { int opt, ret; char **argvopt; int option_index; char *prgname = argv[0], *p; char buff[1024] = {0}; // clang-format off static struct option lgopts[] = { {"txd", required_argument, 0, 't'}, {"rxd", required_argument, 0, 'r'}, {NULL, 0, 0, 0} }; // clang-format on uint16_t pid; argvopt = argv; pktgen.hostname = (char *)strdupf(pktgen.hostname, "localhost"); pktgen.socket_port = 0x5606; pktgen.argc = argc; for (opt = 0; opt < argc; opt++) pktgen.argv[opt] = strdup(argv[opt]); pktgen.mbuf_dataroom = RTE_MBUF_DEFAULT_DATAROOM; pktgen.mbuf_headroom = RTE_PKTMBUF_HEADROOM; pktgen.mbuf_buf_size = pktgen.mbuf_dataroom + pktgen.mbuf_headroom; pktgen.verbose = 0; while ((opt = getopt_long(argc, argvopt, "p:m:f:l:s:g:hPNGTvjtrc", lgopts, &option_index)) != EOF) { switch (opt) { case 't': pktgen.nb_txd = atoi(optarg); pktgen_log_info(">>> Tx Descriptor set to %d", pktgen.nb_txd); break; case 'r': pktgen.nb_rxd = atoi(optarg); pktgen_log_info(">>> Rx Descriptor set to %d", pktgen.nb_rxd); break; case 'j': pktgen.flags |= JUMBO_PKTS_FLAG; pktgen.mbuf_dataroom = PG_JUMBO_DATAROOM_SIZE; pktgen.mbuf_buf_size = pktgen.mbuf_dataroom + pktgen.mbuf_headroom; pktgen_log_info("**** Jumbo Frames of %'d enabled.", RTE_ETHER_MAX_JUMBO_FRAME_LEN); break; case 'p': /* Port mask not used anymore */ break; case 'f': /* Command file or Lua script. */ cli_add_cmdfile(optarg); break; case 'l': /* Log file */ pktgen_log_set_file(optarg); break; case 'm': /* Matrix for port mapping. */ if (l2p_parse_mapping_add(optarg)) { pktgen_log_error("too many mapping entries"); pktgen_usage(prgname); return -1; } break; case 's': /* Read a PCAP packet capture file (stream) */ snprintf(buff, sizeof(buff), "%s", optarg); p = strchr(buff, ':'); if (p == NULL) goto pcap_err; *p++ = '\0'; pid = (uint16_t)strtol(buff, NULL, 10); if (pktgen_pcap_add(p, pid) < 0) goto pcap_err; break; case 'P': /* Enable promiscuous mode on the ports */ pktgen.flags |= PROMISCUOUS_ON_FLAG; break; case 'N': /* Enable NUMA support. */ pktgen.flags |= NUMA_SUPPORT_FLAG; break; case 'G': pktgen.flags |= IS_SERVER_FLAG; break; case 'g': /* Define the port number and IP address used for the socket connection. */ pktgen.flags |= IS_SERVER_FLAG; p = strchr(optarg, ':'); if (p == NULL) /* No : symbol means pktgen is a server application. */ pktgen.hostname = (char *)strdupf(pktgen.hostname, optarg); else { char c = *p; *p = '\0'; if (p != optarg) pktgen.hostname = (char *)strdupf(pktgen.hostname, optarg); pktgen.socket_port = strtol(++p, NULL, 0); pktgen_log_info(">>> Socket support %s%c0x%x", pktgen.hostname, c, pktgen.socket_port); } break; case 'T': pktgen.flags |= ENABLE_THEME_FLAG; break; case 'c': enable_clock_gettime(ENABLE_STATE); break; case 'v': pktgen.verbose = 1; break; case 'h': /* print out the help message */ pktgen_usage(prgname); return -1; default: pktgen_usage(prgname); return -1; } } /* Setup the program name */ if (optind >= 0) argv[optind - 1] = prgname; ret = optind - 1; optind = 1; /* reset getopt lib */ if (l2p_parse_mappings() < 0) pktgen_log_error("error or too many mapping entries"); if (pktgen_pcap_open() < 0) pktgen_log_error("error opening PCAP files"); return ret; pcap_err: pktgen_log_error("Invalid PCAP filename (%s) must include port number as P:filename", optarg); pktgen_usage(prgname); return -1; } #define MAX_BACKTRACE 32 static void sig_handler(int v __rte_unused) { void *array[MAX_BACKTRACE]; size_t size; char **strings; size_t i; scrn_setw(1); /* Reset the window size, from possible crash run. */ scrn_printf(100, 1, "\n"); /* Move the cursor to the bottom of the screen again */ printf("\n======"); if (v == SIGSEGV) printf(" Pktgen got a Segment Fault\n"); else if (v == SIGHUP) printf(" Pktgen received a SIGHUP\n"); else if (v == SIGINT) printf(" Pktgen received a SIGINT\n"); else if (v == SIGPIPE) { printf(" Pktgen received a SIGPIPE\n"); return; } else printf(" Pktgen received signal %d\n", v); printf("\n"); size = backtrace(array, MAX_BACKTRACE); strings = backtrace_symbols(array, size); printf("Obtained %zd stack frames.\n", size); for (i = 0; i < size; i++) printf("%s\n", strings[i]); free(strings); cli_destroy(); scrn_destroy(); exit(-1); } #ifdef LUA_ENABLED static int pktgen_lua_dofile(void *ld, const char *filename) { int ret; ret = lua_dofile((luaData_t *)ld, filename); return ret; } #endif /** * * main - Main routine to setup pktgen. * * DESCRIPTION * Main routine to setup pktgen. * * RETURNS: N/A * * SEE ALSO: */ int main(int argc, char **argv) { int32_t ret; struct sigaction sa; sigset_t set; setlocale(LC_ALL, ""); sa.sa_handler = sig_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGHUP, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); /* Block SIGWINCH for all threads, * because we only want it to be * handled by the main thread */ sigemptyset(&set); sigaddset(&set, SIGWINCH); pthread_sigmask(SIG_BLOCK, &set, NULL); scrn_setw(1); /* Reset the window size, from possible crash run. */ scrn_pos(100, 1); /* Move the cursor to the bottom of the screen again */ print_copyright(PKTGEN_VERSION, PKTGEN_VER_CREATED_BY); fflush(stdout); /* call before the rte_eal_init() */ (void)rte_set_application_usage_hook(pktgen_usage); memset(&pktgen, 0, sizeof(pktgen)); pktgen.flags = PRINT_LABELS_FLAG; pktgen.ident = 0x1234; pktgen.nb_rxd = DEFAULT_RX_DESC; pktgen.nb_txd = DEFAULT_TX_DESC; pktgen.nb_ports_per_page = DEFAULT_PORTS_PER_PAGE; l2p_create(); pktgen.portdesc_cnt = get_portdesc(pktgen.portdesc, RTE_MAX_ETHPORTS, 0); /* Initialize the screen and logging */ pktgen_init_log(); pktgen_cpu_init(); /* initialize EAL */ ret = rte_eal_init(argc, argv); if (ret < 0) return -1; argc -= ret; argv += ret; if (pktgen_cli_create()) { cli_destroy(); scrn_destroy(); return -1; } #ifdef LUA_ENABLED lua_newlib_add(pktgen_lua_openlib, 0); /* Open the Lua script handler. */ if ((pktgen.ld = lua_create_instance()) == NULL) { pktgen_log_error("Failed to open Lua pktgen support library"); return -1; } cli_set_lua_callback(pktgen_lua_dofile); cli_set_user_state(pktgen.ld); #endif /* parse application arguments (after the EAL ones) */ ret = pktgen_parse_args(argc, argv); if (ret < 0) { cli_destroy(); scrn_destroy(); return -1; } if (l2p_get_pid_by_lcore(rte_get_main_lcore()) < RTE_MAX_ETHPORTS) { cli_printf("*** Error can not use initial lcore %d for port handling\n", rte_get_main_lcore()); cli_printf(" The initial lcore is %d\n", rte_get_main_lcore()); cli_destroy(); scrn_destroy(); exit(-1); } pktgen.hz = pktgen_get_timer_hz(); /* Get the starting HZ value. */ scrn_create_with_defaults(pktgen.flags & ENABLE_THEME_FLAG); rte_delay_us_sleep(100 * 1000); /* Wait a bit for things to settle. */ if (pktgen.verbose) pktgen_log_info( ">>> Packet Max Burst %d/%d, RX Desc %d, TX Desc %d, mbufs/port %d, mbuf cache %d", MAX_PKT_RX_BURST, MAX_PKT_TX_BURST, pktgen.nb_rxd, pktgen.nb_txd, MAX_MBUFS_PER_PORT(pktgen.nb_rxd, pktgen.nb_txd), MBUF_CACHE_SIZE); /* Configure and initialize the ports */ pktgen_config_ports(); if (pktgen.verbose) { pktgen_log_info(""); pktgen_log_info("=== Display processing on lcore %d", rte_lcore_id()); } /* launch per-lcore init on every lcore except initial and initial + 1 lcores */ ret = rte_eal_mp_remote_launch(pktgen_launch_one_lcore, NULL, SKIP_MAIN); if (ret != 0) pktgen_log_error("Failed to start lcores, return %d", ret); for (uint16_t i = 0; i < 4; i++) printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); fflush(stdout); /* Disable printing log messages of level info and below to screen, */ /* erase the screen and start updating the screen again. */ pktgen_log_set_screen_level(LOG_LEVEL_WARNING); scrn_erase(this_scrn->nrows); scrn_resume(); pktgen_clear_display(); pktgen_timer_setup(); #ifdef LUA_ENABLED if (pktgen.flags & IS_SERVER_FLAG) { pktgen.ld_sock = lua_create_instance(); if (pktgen.ld_sock == NULL) { pktgen_log_error("Failed to open Lua socket server support library"); return -1; } if (lua_start_socket(pktgen.ld_sock, &pktgen.thread, pktgen.hostname, pktgen.socket_port) < 0) { pktgen_log_error("Failed to start Lua socket server thread"); return -1; } } #endif /* Unblock SIGWINCH so main thread * can handle screen resizes */ sigemptyset(&set); sigaddset(&set, SIGWINCH); pthread_sigmask(SIG_UNBLOCK, &set, NULL); /* execute the command files if present */ scrn_pause(); cli_execute_cmdfiles(); scrn_resume(); pktgen_clear_display(); cli_start(NULL); /* Start accepting input from user */ scrn_pause(); scrn_setw(1); /* Reset the window size, from possible crash run. */ /* Move the cursor to the bottom of the screen again */ scrn_printf(this_scrn->nrows + 1, 1, "\n"); pktgen_stop_running(); /* Wait for all of the cores to stop running and exit. */ rte_eal_mp_wait_lcore(); int i = 0; RTE_ETH_FOREACH_DEV(i) { rte_eth_dev_stop(i); rte_delay_us_sleep(100 * 1000); } cli_destroy(); scrn_destroy(); return 0; } /** * * pktgen_stop_running - Stop pktgen to exit in a clean way * * DESCRIPTION * Stop all of the logical core threads to stop pktgen cleanly. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_stop_running(void) { #ifdef LUA_ENABLED lua_execute_close(pktgen.ld); #endif pktgen.timer_running = 0; pktgen.force_quit = 1; } ================================================ FILE: app/pktgen-main.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_MAIN_H_ #define _PKTGEN_MAIN_H_ /** * @file * * Pktgen main loop and lifecycle control functions. */ #include #include #include #ifdef __cplusplus extern "C" { #endif /** * Enter the interactive CLI loop and block until the user quits. * * Runs the CLI read-eval-print loop on the calling lcore, processing * user commands until pktgen_stop_running() signals termination. */ void pktgen_interact(void); /** * Return the Lua state pointer for the active Lua instance. * * @return * Pointer to the lua_State, or NULL if Lua is not enabled. */ void *pktgen_get_lua(void); /** * Signal the main loop to stop and exit gracefully. */ void pktgen_stop_running(void); #ifdef __cplusplus } #endif #endif /* _PKTGEN_MAIN_H_ */ ================================================ FILE: app/pktgen-pcap.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include "pktgen-display.h" #include "pktgen-log.h" #include "pktgen.h" #ifndef MBUF_INVALID_PORT #define MBUF_INVALID_PORT UINT16_MAX #endif static pcap_info_t *pcap_info_list[RTE_MAX_ETHPORTS]; void pktgen_pcap_info(pcap_info_t *pcap, uint16_t port, int flag) { printf("PCAP file for port %d: %s\n", port, pcap->filename); printf(" magic: %08x,", pcap->info.magic_number); printf(" Version: %d.%d,", pcap->info.version_major, pcap->info.version_minor); printf(" Zone: %d,", pcap->info.thiszone); printf(" snaplen: %d,", pcap->info.snaplen); printf(" sigfigs: %d,", pcap->info.sigfigs); printf(" network: %d", pcap->info.network); printf(" Convert Endian: %s\n", pcap->convert ? "Yes" : "No"); if (flag) printf(" Packet count: %d, max size %d\n", pcap->pkt_count, pcap->max_pkt_size); fflush(stdout); } static __inline__ void pcap_convert(pcap_info_t *pcap, pcap_record_hdr_t *pHdr) { if (pcap->convert) { pHdr->incl_len = ntohl(pHdr->incl_len); pHdr->orig_len = ntohl(pHdr->orig_len); pHdr->ts_sec = ntohl(pHdr->ts_sec); pHdr->ts_usec = ntohl(pHdr->ts_usec); } } static void pcap_rewind(pcap_info_t *pcap) { /* Rewind to the beginning */ rewind(pcap->fp); /* Seek past the pcap header */ (void)fseek(pcap->fp, sizeof(pcap_hdr_t), SEEK_SET); } static void pcap_get_info(pcap_info_t *pcap) { pcap_record_hdr_t hdr; if (fread(&pcap->info, 1, sizeof(pcap_hdr_t), pcap->fp) != sizeof(pcap_hdr_t)) rte_exit(EXIT_FAILURE, "%s: failed to read pcap header\n", __func__); /* Make sure we have a valid PCAP file for Big or Little Endian formats. */ if (pcap->info.magic_number == PCAP_MAGIC_NUMBER) pcap->convert = 0; else if (pcap->info.magic_number == ntohl(PCAP_MAGIC_NUMBER)) pcap->convert = 1; else rte_exit(EXIT_FAILURE, "%s: invalid magic number 0x%08x\n", __func__, pcap->info.magic_number); if (pcap->convert) { pcap->info.magic_number = ntohl(pcap->info.magic_number); pcap->info.version_major = ntohs(pcap->info.version_major); pcap->info.version_minor = ntohs(pcap->info.version_minor); pcap->info.thiszone = ntohl(pcap->info.thiszone); pcap->info.sigfigs = ntohl(pcap->info.sigfigs); pcap->info.snaplen = ntohl(pcap->info.snaplen); pcap->info.network = ntohl(pcap->info.network); } pcap->max_pkt_size = 0; pcap->avg_pkt_size = 0; uint64_t total_size = 0; /* count the number of packets and get the largest size packet */ for (;;) { if (fread(&hdr, 1, sizeof(pcap_record_hdr_t), pcap->fp) != sizeof(hdr)) break; /* Convert the packet header to the correct format if needed */ pcap_convert(pcap, &hdr); if (fseek(pcap->fp, hdr.incl_len, SEEK_CUR) < 0) break; pcap->pkt_count++; if (hdr.incl_len > pcap->max_pkt_size) pcap->max_pkt_size = hdr.incl_len; total_size += hdr.incl_len; } printf("PCAP: Max Packet Size: %d\n", pcap->max_pkt_size); pcap->avg_pkt_size = total_size / pcap->pkt_count; printf("PCAP: Avg Packet Size: %d\n", pcap->avg_pkt_size); pcap_rewind(pcap); } static __inline__ void mbuf_iterate_cb(struct rte_mempool *mp, void *opaque, void *obj, unsigned obj_idx __rte_unused) { pcap_info_t *pcap = (pcap_info_t *)opaque; struct rte_mbuf *m = (struct rte_mbuf *)obj; pcap_record_hdr_t hdr = {0}; if (fread(&hdr, 1, sizeof(pcap_record_hdr_t), pcap->fp) != sizeof(hdr)) { pcap_rewind(pcap); if (fread(&hdr, 1, sizeof(pcap_record_hdr_t), pcap->fp) != sizeof(hdr)) rte_exit(EXIT_FAILURE, "%s: failed to read pcap header\n", __func__); } pcap_convert(pcap, &hdr); /* Convert the packet header to the correct format. */ if (fread(rte_pktmbuf_mtod(m, char *), 1, hdr.incl_len, pcap->fp) == 0) rte_exit(EXIT_FAILURE, "%s: failed to read packet data from PCAP file\n", __func__); m->pool = mp; m->next = NULL; m->data_len = hdr.incl_len; m->pkt_len = hdr.incl_len; m->port = 0; m->ol_flags = 0; } int pktgen_pcap_add(char *filename, uint16_t pid) { pcap_info_t *pcap = NULL; char name[64] = {0}; uint16_t sid; if (filename == NULL) rte_exit(EXIT_FAILURE, "%s: PCAP filename is NULL\n", __func__); sid = pg_eth_dev_socket_id(pid); snprintf(name, sizeof(name), "PCAP-Info-%d", pid); pcap = (pcap_info_t *)rte_zmalloc_socket(name, sizeof(pcap_info_t), RTE_CACHE_LINE_SIZE, sid); if (pcap == NULL) rte_exit(EXIT_FAILURE, "%s: rte_zmalloc_socket() failed for pcap_info_t structure\n", __func__); /* Default to little endian format. */ pcap->filename = strdup(filename); pcap_info_list[pid] = pcap; return 0; } int pktgen_pcap_open(void) { pcap_info_t *pcap = NULL; struct rte_mempool *mp; char name[64] = {0}; uint16_t sid; uint32_t pkt_count; for (int pid = 0; pid < RTE_MAX_ETHPORTS; pid++) { if ((pcap = pcap_info_list[pid]) == NULL) continue; pcap = pcap_info_list[pid]; sid = pg_eth_dev_socket_id(pid); /* Read the pcap file trailer. */ pcap->fp = fopen((const char *)pcap->filename, "r"); if (pcap->fp == NULL) rte_exit(EXIT_FAILURE, "%s: failed for (%s)\n", __func__, pcap->filename); pcap_get_info(pcap); pkt_count = pcap->pkt_count; if (pkt_count == 0) { fclose(pcap->fp); rte_exit(EXIT_FAILURE, "%s: PCAP file is empty: %s\n", __func__, pcap->filename); } if (pkt_count < (DEFAULT_TX_DESC * 4)) pkt_count = (DEFAULT_TX_DESC * 4); snprintf(name, sizeof(name), "pcap-%d", pid); uint32_t dataroom = RTE_ALIGN_CEIL(pcap->max_pkt_size + RTE_PKTMBUF_HEADROOM, RTE_CACHE_LINE_SIZE); mp = rte_pktmbuf_pool_create(name, pkt_count, 0, DEFAULT_PRIV_SIZE, dataroom, sid); if (mp == NULL) rte_exit(EXIT_FAILURE, "Cannot create mbuf pool (%s) port %d, nb_mbufs %d, socket_id %d: %s", name, pid, pcap->pkt_count, sid, rte_strerror(rte_errno)); pcap->mp = mp; rte_mempool_obj_iter(mp, mbuf_iterate_cb, pcap); if (l2p_set_pcap_info(pid, pcap) < 0) pktgen_log_error("Error opening PCAP file: %s", pcap->filename); } return 0; } void pktgen_pcap_close(void) { pcap_info_t *pcap = NULL; for (int pid = 0; pid < RTE_MAX_ETHPORTS; pid++) { pcap = pcap_info_list[pid]; if (pcap == NULL) return; if (pcap->filename) free(pcap->filename); if (pcap->fp) fclose(pcap->fp); if (pcap->mp) rte_mempool_free(pcap->mp); rte_free(pcap); } } FILE * pktgen_create_pcap_file(char *filename) { struct pcap_file_header file_header; file_header.magic = 0xa1b2c3d4; file_header.version_major = 2; file_header.version_minor = 4; file_header.thiszone = 0; file_header.sigfigs = 0; file_header.snaplen = 65535; file_header.linktype = 1; // LINKTYPE_ETHERNET printf("Creating PCAP file: %s, %lu, %lu\n", filename, sizeof(struct pcap_file_header), sizeof(struct pcap_pkthdr)); // Open the output file FILE *file = fopen(filename, "wb"); if (!file) return NULL; // Write the file header fwrite(&file_header, sizeof(uint8_t), sizeof(file_header), file); fflush(file); return file; } void pktgen_close_pcap_file(FILE *fp) { if (fp) fclose(fp); } int pktgen_write_mbuf_to_pcap_file(FILE *fp, struct rte_mbuf *mbuf) { // Packet header pcap_record_hdr_t packet_header; size_t size; if (fp == NULL) return 0; packet_header.ts_sec = 0; packet_header.ts_usec = 0; packet_header.incl_len = rte_pktmbuf_pkt_len(mbuf); packet_header.orig_len = rte_pktmbuf_pkt_len(mbuf); // Write the packet header if ((size = fwrite(&packet_header, sizeof(uint8_t), sizeof(packet_header), fp)) != 16) printf("Error writing packet header %ld\n", size); // Write the packet data fwrite(rte_pktmbuf_mtod(mbuf, char *), sizeof(uint8_t), rte_pktmbuf_pkt_len(mbuf), fp); fflush(fp); return 0; } ================================================ FILE: app/pktgen-pcap.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_PCAP_H_ #define _PKTGEN_PCAP_H_ /** * @file * * PCAP file read/write support for Pktgen. * * Provides structures mirroring the libpcap file format, plus functions for * opening, replaying, and writing PCAP files from mbufs. */ #include #ifdef __cplusplus extern "C" { #endif #define PCAP_MAGIC_NUMBER 0xa1b2c3d4 /**< PCAP global header magic (little-endian) */ #define PCAP_MAJOR_VERSION 2 /**< PCAP file format major version */ #define PCAP_MINOR_VERSION 4 /**< PCAP file format minor version */ /** PCAP global file header. */ typedef struct pcap_hdr_s { uint32_t magic_number; /**< magic number */ uint16_t version_major; /**< major version number */ uint16_t version_minor; /**< minor version number */ int32_t thiszone; /**< GMT to local correction */ uint32_t sigfigs; /**< accuracy of timestamps */ uint32_t snaplen; /**< max length of captured packets, in octets */ uint32_t network; /**< data link type */ } pcap_hdr_t; /** PCAP per-packet record header. */ typedef struct pcap_record_hdr_s { uint32_t ts_sec; /**< timestamp seconds */ uint32_t ts_usec; /**< timestamp microseconds */ uint32_t incl_len; /**< number of octets of packet saved in file */ uint32_t orig_len; /**< actual length of packet */ } pcap_record_hdr_t; /** Pktgen PCAP replay state for one port. */ typedef struct pcap_info_s { char *filename; /**< allocated string for filename of pcap */ FILE *fp; /**< file pointer for pcap file */ struct rte_mempool *mp; /**< Mempool for storing packets */ uint32_t convert; /**< Endian flag value if 1 convert to host endian format */ uint32_t max_pkt_size; /**< largest packet found in pcap file */ uint32_t avg_pkt_size; /**< average packet size in pcap file */ uint32_t pkt_count; /**< Number of packets in pcap file */ uint32_t pkt_index; /**< Index of current packet in pcap file */ pcap_hdr_t info; /**< information on the PCAP file */ int32_t pcap_result; /**< PCAP result of filter compile */ struct bpf_program pcap_program; /**< PCAP filter program structure */ } pcap_info_t; /** * Register a PCAP file for replay on a port. * * @param filename Path to the PCAP file. * @param port Port ID to associate with this PCAP. * @return * 0 on success, negative on error. */ int pktgen_pcap_add(char *filename, uint16_t port); /** * Open all registered PCAP files and load packets into mempools. * * @return * 0 on success, negative on error. */ int pktgen_pcap_open(void); /** Close all open PCAP file handles. */ void pktgen_pcap_close(void); /** * Print PCAP file information to the display. * * @param pcap PCAP info structure to display. * @param port Port ID. * @param flag Display verbosity flag. */ void pktgen_pcap_info(pcap_info_t *pcap, uint16_t port, int flag); /** * Create a new PCAP output file for packet capture. * * @param filename Path to the output PCAP file. * @return * Open FILE pointer, or NULL on error. */ FILE *pktgen_create_pcap_file(char *filename); /** * Close and finalise a PCAP output file. * * @param fp File pointer returned by pktgen_create_pcap_file(). */ void pktgen_close_pcap_file(FILE *fp); /** * Append one mbuf as a packet record to an open PCAP file. * * @param fp Open PCAP file handle. * @param mbuf mbuf containing the packet to write. * @return * 0 on success, negative on write error. */ int pktgen_write_mbuf_to_pcap_file(FILE *fp, struct rte_mbuf *mbuf); #ifdef __cplusplus } #endif #endif /* _PKTGEN_PCAP_H_ */ ================================================ FILE: app/pktgen-port-cfg.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include "pg_compat.h" #include "pktgen-cmds.h" #include "pktgen-log.h" #include "l2p.h" #include #include #if defined(RTE_LIBRTE_PMD_BOND) || defined(RTE_NET_BOND) #include #endif #include #include enum { RX_PTHRESH = 8, /**< Default values of RX prefetch threshold reg. */ RX_HTHRESH = 8, /**< Default values of RX host threshold reg. */ RX_WTHRESH = 4, /**< Default values of RX write-back threshold reg. */ TX_PTHRESH = 36, /**< Default values of TX prefetch threshold reg. */ TX_HTHRESH = 0, /**< Default values of TX host threshold reg. */ TX_WTHRESH = 0, /**< Default values of TX write-back threshold reg. */ TX_WTHRESH_1GB = 16, /**< Default value for 1GB ports */ }; /** * An array of drivers that require a pseudo-header calculation before the checksum calculation. * The names used are the ones used by DPDK. */ static const char *DRIVERS_REQUIRING_PHDR[] = { "net_ixgbe", // TODO: Add the others }; static struct rte_eth_conf default_port_conf = { .rxmode = { .mq_mode = RTE_ETH_MQ_RX_RSS, .max_lro_pkt_size = RTE_ETHER_MAX_LEN, .offloads = RTE_ETH_RX_OFFLOAD_CHECKSUM, .mtu = RTE_ETHER_MTU, }, .txmode = { .mq_mode = RTE_ETH_MQ_TX_NONE, }, .rx_adv_conf = { .rss_conf = { .rss_key = NULL, .rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP | RTE_ETH_RSS_SCTP | RTE_ETH_RSS_L2_PAYLOAD, }, }, .intr_conf = { .lsc = 0, }, }; static void dump_device_info(void) { printf("\n%-4s %-16s %-5s %-4s %-22s %-17s %s\n", "Port", "DevName", "Index", "NUMA", "PCI Information", "Src MAC", "Promiscuous"); for (uint16_t i = 0; i < pktgen.nb_ports; i++) { struct rte_eth_dev_info dev; const struct rte_bus *bus = NULL; port_info_t *pinfo; pkt_seq_t *pkt; char buff[128]; if (rte_eth_dev_info_get(i, &dev)) { printf(buff, sizeof(buff), "%6u No device information: %s", i, rte_strerror(rte_errno)); continue; } buff[0] = 0; if (dev.device) bus = rte_bus_find_by_device(dev.device); if (bus && !strcmp(rte_bus_name(bus), "pci")) { char name[RTE_ETH_NAME_MAX_LEN]; char vend[8], device[8]; vend[0] = device[0] = '\0'; sscanf(rte_dev_bus_info(dev.device), "vendor_id=%4s, device_id=%4s", vend, device); rte_eth_dev_get_name_by_port(i, name); snprintf(buff, sizeof(buff), "%s:%s/%s", vend, device, rte_dev_name(dev.device)); } else snprintf(buff, sizeof(buff), "non-PCI device"); pinfo = l2p_get_port_pinfo(i); pkt = &pinfo->seq_pkt[SINGLE_PKT]; printf("%3u %-16s %5d %4d %s %02x:%02x:%02x:%02x:%02x:%02x <%s>\n", i, dev.driver_name, dev.if_index, rte_dev_numa_node(dev.device), buff, pkt->eth_src_addr.addr_bytes[0], pkt->eth_src_addr.addr_bytes[1], pkt->eth_src_addr.addr_bytes[2], pkt->eth_src_addr.addr_bytes[3], pkt->eth_src_addr.addr_bytes[4], pkt->eth_src_addr.addr_bytes[5], (pktgen.flags & PROMISCUOUS_ON_FLAG) ? "Enabled" : "Disabled"); } printf("\n"); } /** * Determines whether the pseudo-header is required when calculating the checksum. * Depends on the original NIC driver (e.g., ixgbe NICs expect the pseudo-header) * See Table 1.133: https://doc.dpdk.org/guides/nics/overview.html */ bool is_cksum_phdr_required(const char *driver_name) { size_t num_drivers = RTE_DIM(DRIVERS_REQUIRING_PHDR); for (size_t i = 0; i < num_drivers; i++) { if (DRIVERS_REQUIRING_PHDR[i] == NULL) break; if (strcmp(driver_name, DRIVERS_REQUIRING_PHDR[i]) == 0) return true; } return false; } static uint32_t eth_dev_get_overhead_len(uint32_t max_rx_pktlen, uint16_t max_mtu) { uint32_t overhead_len; if (max_mtu != UINT16_MAX && max_rx_pktlen > max_mtu) overhead_len = max_rx_pktlen - max_mtu; else overhead_len = RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN; return overhead_len; } static port_info_t * allocate_port_info(uint16_t pid) { port_info_t *pinfo = l2p_get_port_pinfo(pid); int32_t sid = pg_eth_dev_socket_id(pid); pktgen_log_info("Port info setup for port %u", pid); /* If port info is already set ignore */ if (pinfo) { pktgen_log_error("Port info already setup for port %u", pid); goto leave; } /* Allocate each port_info_t structure on the correct NUMA node for the port */ if ((pinfo = rte_zmalloc_socket(NULL, sizeof(port_info_t), RTE_CACHE_LINE_SIZE, sid)) == NULL) goto leave; pinfo->pid = pid; pinfo->max_mtu = RTE_ETHER_MAX_LEN; pinfo->conf = default_port_conf; if (rte_eth_dev_info_get(pid, &pinfo->dev_info) < 0) { pktgen_log_error("Cannot get device info for port %u", pid); goto leave; } for (int qid = 0; qid < l2p_get_txcnt(pid); qid++) { per_queue_t *pq = &pinfo->per_queue[qid]; char buff[64]; snprintf(buff, sizeof(buff), "RxMbufs-%u-%d", pid, qid); pq->rx_pkts = rte_calloc_socket(buff, MAX_PKT_RX_BURST, sizeof(struct rte_mbuf *), RTE_CACHE_LINE_SIZE, sid); if (pq->rx_pkts == NULL) { pktgen_log_error("Cannot allocate RX burst for port %u-%d", pid, qid); goto leave; } } for (int qid = 0; qid < l2p_get_txcnt(pid); qid++) { per_queue_t *pq = &pinfo->per_queue[qid]; char buff[64]; snprintf(buff, sizeof(buff), "TxMbufs-%u-%d", pid, qid); pq->tx_pkts = rte_calloc_socket(buff, MAX_PKT_TX_BURST, sizeof(struct rte_mbuf *), RTE_CACHE_LINE_SIZE, sid); if (pq->tx_pkts == NULL) { pktgen_log_error("Cannot allocate TX burst for port %u-%d", pid, qid); goto leave; } } if (l2p_set_port_pinfo(pid, pinfo)) { pktgen_log_error("Failed to set port info for port %u", pid); goto leave; } pktgen_log_info(" Allocate packet sequence array"); size_t pktsz = RTE_ETHER_MAX_LEN; if (pktgen.flags & JUMBO_PKTS_FLAG) pktsz = RTE_ETHER_MAX_JUMBO_FRAME_LEN; /* allocate the sequence packet array */ pinfo->seq_pkt = rte_zmalloc_socket(NULL, (sizeof(pkt_seq_t) * NUM_TOTAL_PKTS), RTE_CACHE_LINE_SIZE, sid); if (pinfo->seq_pkt == NULL) { pktgen_log_error("Unable to allocate %'ld pkt_seq_t headers", (long int)NUM_TOTAL_PKTS); goto leave; } for (int i = 0; i < NUM_TOTAL_PKTS; i++) { pinfo->seq_pkt[i].hdr = rte_zmalloc_socket(NULL, pktsz, RTE_CACHE_LINE_SIZE, sid); if (pinfo->seq_pkt[i].hdr == NULL) pktgen_log_panic("Unable to allocate %ld pkt_seq_t buffer space", pktsz); pinfo->seq_pkt[i].seq_enabled = 1; pinfo->seq_pkt[i].tcp_flags = DEFAULT_TCP_FLAGS; pinfo->seq_pkt[i].tcp_seq = DEFAULT_TCP_SEQ_NUMBER; pinfo->seq_pkt[i].tcp_ack = DEFAULT_TCP_ACK_NUMBER; } /* Determines if pseudo-header is needed, based on the driver type */ pinfo->cksum_requires_phdr = is_cksum_phdr_required(pinfo->dev_info.driver_name); pktgen_log_info(" Checksum offload Pseudo-header required: %s", pinfo->cksum_requires_phdr ? "Yes" : "No"); return pinfo; leave: if (pinfo) { for (int i = 0; i < MAX_QUEUES_PER_PORT; i++) { per_queue_t *pq = &pinfo->per_queue[i]; rte_free(pq->rx_pkts); rte_free(pq->tx_pkts); } rte_free(pinfo); l2p_set_port_pinfo(pid, NULL); } return NULL; } static void _latency_defaults(port_info_t *pinfo) { latency_t *lat = &pinfo->latency; pktgen_log_info(" Setup latency defaults"); lat->jitter_threshold_us = DEFAULT_JITTER_THRESHOLD; lat->latency_rate_us = DEFAULT_LATENCY_RATE; lat->latency_entropy = DEFAULT_LATENCY_ENTROPY; lat->latency_rate_cycles = pktgen_get_timer_hz() / ((uint64_t)MAX_LATENCY_RATE / lat->latency_rate_us); uint64_t ticks = pktgen_get_timer_hz() / (uint64_t)1000000; lat->jitter_threshold_cycles = lat->jitter_threshold_us * ticks; } static void _fill_pattern_defaults(port_info_t *pinfo) { pktgen_log_info(" Setup fill pattern defaults"); pinfo->fill_pattern_type = ABC_FILL_PATTERN; snprintf(pinfo->user_pattern, sizeof(pinfo->user_pattern), "%s", "0123456789abcdef"); } static void _mtu_defaults(port_info_t *pinfo) { struct rte_eth_dev_info *dinfo = &pinfo->dev_info; struct rte_eth_conf *conf = &pinfo->conf; pktgen_log_info(" Setup MTU defaults and Jumbo Frames are %s", (pktgen.flags & JUMBO_PKTS_FLAG) ? "Enabled" : "Disabled"); if (pinfo->max_mtu < dinfo->min_mtu) pinfo->max_mtu = dinfo->min_mtu; if (pinfo->max_mtu > dinfo->max_mtu) pinfo->max_mtu = dinfo->max_mtu; if (pktgen.flags & JUMBO_PKTS_FLAG) { uint32_t eth_overhead_len; conf->rxmode.max_lro_pkt_size = PG_JUMBO_ETHER_MTU; eth_overhead_len = eth_dev_get_overhead_len(dinfo->max_rx_pktlen, dinfo->max_mtu); pinfo->max_mtu = dinfo->max_mtu - eth_overhead_len; /* device may have higher theoretical MTU e.g. for infiniband */ if (pinfo->max_mtu > PG_JUMBO_ETHER_MTU) pinfo->max_mtu = PG_JUMBO_ETHER_MTU; pktgen_log_info( " Jumbo Frames: Default Max Rx pktlen: %'u, MTU %'u, overhead len: %'u, " "New MTU %'d", dinfo->max_rx_pktlen, dinfo->max_mtu, eth_overhead_len, pinfo->max_mtu); pktgen_log_info(" Note: Using Scatter/Multi-segs offloads"); pktgen_log_info(" Note: Performance may be degraded due to reduced Tx performance"); // FIXME: Tx performance takes a big hit when enabled if (dinfo->rx_offload_capa & RTE_ETH_RX_OFFLOAD_SCATTER) conf->rxmode.offloads |= RTE_ETH_RX_OFFLOAD_SCATTER; if (dinfo->tx_offload_capa & RTE_ETH_TX_OFFLOAD_MULTI_SEGS) conf->txmode.offloads |= RTE_ETH_TX_OFFLOAD_MULTI_SEGS; } conf->rxmode.mtu = pinfo->max_mtu; } static void _rx_offload_defaults(port_info_t *pinfo) { struct rte_eth_dev_info *dinfo = &pinfo->dev_info; struct rte_eth_conf *conf = &pinfo->conf; conf->rx_adv_conf.rss_conf.rss_key = NULL; conf->rx_adv_conf.rss_conf.rss_hf &= dinfo->flow_type_rss_offloads; if (dinfo->max_rx_queues == 1) conf->rxmode.mq_mode = RTE_ETH_MQ_RX_NONE; if (dinfo->max_vfs) { if (conf->rx_adv_conf.rss_conf.rss_hf != 0) conf->rxmode.mq_mode = RTE_ETH_MQ_RX_VMDQ_RSS; } conf->rxmode.offloads &= dinfo->rx_offload_capa; } static void _tx_offload_defaults(port_info_t *pinfo) { struct rte_eth_dev_info *dinfo = &pinfo->dev_info; struct rte_eth_conf *conf = &pinfo->conf; pktgen_log_info(" Setup TX offload defaults"); if (dinfo->tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) conf->txmode.offloads |= RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE; if (dinfo->tx_offload_capa & RTE_ETH_TX_OFFLOAD_TCP_CKSUM) { pktgen_log_info(" Enabling Tx TCP_CKSUM offload"); conf->txmode.offloads |= RTE_ETH_TX_OFFLOAD_TCP_CKSUM; } if (dinfo->tx_offload_capa & RTE_ETH_TX_OFFLOAD_UDP_CKSUM) { pktgen_log_info(" Enabling Tx UDP_CKSUM offload\r\n"); conf->txmode.offloads |= RTE_ETH_TX_OFFLOAD_UDP_CKSUM; } if (dinfo->tx_offload_capa & RTE_ETH_TX_OFFLOAD_IPV4_CKSUM) { pktgen_log_info(" Enabling Tx IPV4_CKSUM offload\r\n"); conf->txmode.offloads |= RTE_ETH_TX_OFFLOAD_IPV4_CKSUM; } } static void _device_configuration(port_info_t *pinfo) { uint16_t pid = pinfo->pid; int ret; pktgen_log_info(" Configure device: RxQueueCnt: %u, TxQueueCnt: %u", l2p_get_rxcnt(pid), l2p_get_txcnt(pid)); ret = rte_eth_dev_configure(pid, l2p_get_rxcnt(pid), l2p_get_txcnt(pid), &pinfo->conf); if (ret < 0) pktgen_log_panic("Cannot configure device: port=%d, Num queues %d,%d", pid, l2p_get_rxcnt(pid), l2p_get_txcnt(pid)); } static void _rxtx_descriptors(port_info_t *pinfo) { uint16_t pid = pinfo->pid; int ret; pktgen_log_info(" Setup number of descriptors RX: %u, TX: %u", pktgen.nb_rxd, pktgen.nb_txd); ret = rte_eth_dev_adjust_nb_rx_tx_desc(pid, &pktgen.nb_rxd, &pktgen.nb_txd); if (ret < 0) pktgen_log_panic("Can't adjust number of descriptors: port=%u:%s", pid, rte_strerror(-ret)); pktgen_log_info(" Updated descriptors RX: %u, TX: %u", pktgen.nb_rxd, pktgen.nb_txd); } static void _src_mac_address(port_info_t *pinfo) { uint16_t pid = pinfo->pid; char buff[64]; int ret = rte_eth_macaddr_get(pid, &pinfo->src_mac); if (ret < 0) pktgen_log_panic("Port %u, Failed to get source MAC address, (%d)%s", pinfo->pid, -ret, rte_strerror(-ret)); else pktgen_log_info(" Source MAC: %s", inet_mtoa(buff, sizeof(buff), &pinfo->src_mac)); } static void _device_ptypes(port_info_t *pinfo) { pktgen_log_info(" Setup up device Ptypes"); int ret = rte_eth_dev_set_ptypes(pinfo->pid, RTE_PTYPE_UNKNOWN, NULL, 0); if (ret < 0) pktgen_log_panic("Port %u, Failed to disable Ptype parsing", pinfo->pid); } static void _device_mtu(port_info_t *pinfo) { struct rte_eth_dev_info *dinfo = &pinfo->dev_info; struct rte_eth_conf *conf = &pinfo->conf; uint32_t max_mtu = RTE_ETHER_MAX_LEN; int ret; if (max_mtu < dinfo->min_mtu) { pktgen_log_warning("Increasing MTU from %u to %u", max_mtu, dinfo->min_mtu); max_mtu = dinfo->min_mtu; } if (max_mtu > dinfo->max_mtu) { pktgen_log_warning("Reducing MTU from %u to %u", max_mtu, dinfo->max_mtu); max_mtu = dinfo->max_mtu; } conf->rxmode.mtu = max_mtu; if ((ret = rte_eth_dev_set_mtu(pinfo->pid, pinfo->max_mtu)) < 0) pktgen_log_panic("Cannot set MTU on port %u, (%d)%s", pinfo->pid, -ret, rte_strerror(-ret)); } static void _rx_queues(port_info_t *pinfo) { struct rte_eth_dev_info *dinfo = &pinfo->dev_info; struct rte_eth_conf *conf = &pinfo->conf; uint16_t sid, pid = pinfo->pid; int ret; l2p_port_t *lport = l2p_get_port(pid); if (lport == NULL) pktgen_log_panic("Failed: l2p_port_t for port %u not found", pid); sid = pg_eth_dev_socket_id(pid); pktgen_log_info(" Number of RX queues %u", l2p_get_rxcnt(pid)); for (int q = 0; q < l2p_get_rxcnt(pid); q++) { struct rte_eth_rxconf rxq_conf; rxq_conf = dinfo->default_rxconf; rxq_conf.offloads = conf->rxmode.offloads; pktgen_log_info(" RX queue %d enabled offloads: 0x%0lx, socket_id %u, mp %p", q, rxq_conf.offloads, sid, lport->rx_mp[q]); ret = rte_eth_rx_queue_setup(pid, q, pktgen.nb_rxd, sid, &rxq_conf, lport->rx_mp[q]); if (ret < 0) pktgen_log_panic("rte_eth_rx_queue_setup: err=%d, port=%d, %s", ret, pid, rte_strerror(-ret)); } } static void _tx_queues(port_info_t *pinfo) { struct rte_eth_dev_info *dinfo = &pinfo->dev_info; struct rte_eth_conf *conf = &pinfo->conf; uint16_t sid, pid = pinfo->pid; int ret; l2p_port_t *lport = l2p_get_port(pid); if (lport == NULL) pktgen_log_panic("Failed: l2p_port_t for port %u not found", pid); sid = pg_eth_dev_socket_id(pid); pktgen_log_info(" Number of TX queues %u", l2p_get_txcnt(pid)); for (int q = 0; q < l2p_get_txcnt(pid); q++) { struct rte_eth_txconf txq_conf; txq_conf = dinfo->default_txconf; txq_conf.offloads = conf->txmode.offloads; pktgen_log_info(" TX queue %d enabled offloads: 0x%0lx", q, txq_conf.offloads); ret = rte_eth_tx_queue_setup(pid, q, pktgen.nb_txd, sid, &txq_conf); if (ret < 0) pktgen_log_panic("rte_eth_tx_queue_setup: err=%d, port=%d, %s", ret, pid, rte_strerror(-ret)); } } static void _promiscuous_mode(port_info_t *pinfo) { /* If enabled, put device in promiscuous mode. */ if (pktgen.flags & PROMISCUOUS_ON_FLAG) { pktgen_log_info(" Enabling promiscuous mode"); if (rte_eth_promiscuous_enable(pinfo->pid)) pktgen_log_info("Enabling promiscuous failed: %s", rte_strerror(-rte_errno)); } } static void _debug_output(port_info_t *pinfo) { if (pktgen.verbose) pktgen_log_info("%*sPort memory used = %6lu KB", 57, " ", (pktgen.mem_used + 1023) / 1024); if (pktgen.verbose) rte_eth_dev_info_dump(stderr, pinfo->pid); } static void _device_start(port_info_t *pinfo) { int ret; pktgen_log_info(" Start network device"); /* Start device */ if ((ret = rte_eth_dev_start(pinfo->pid)) < 0) pktgen_log_panic("rte_eth_dev_start: port=%d, %s", pinfo->pid, rte_strerror(-ret)); } static void _port_defaults(port_info_t *pinfo) { pktgen_log_info(" Setup port defaults"); pktgen_port_defaults(pinfo->pid); } static port_info_t * initialize_port_info(uint16_t pid) { port_info_t *pinfo = l2p_get_port_pinfo(pid); // clang-format off void (*setups[])(port_info_t *) = { _latency_defaults, _fill_pattern_defaults, _mtu_defaults, _rx_offload_defaults, _tx_offload_defaults, _device_configuration, _rxtx_descriptors, _src_mac_address, _device_ptypes, _device_mtu, _rx_queues, _tx_queues, _debug_output, _promiscuous_mode, _port_defaults, _device_start, }; // clang-format on if ((pinfo = allocate_port_info(pid)) == NULL) pktgen_log_panic("Unable to allocate port_info_t for port %u", pid); for (uint16_t i = 0; i < RTE_DIM(setups); i++) { if (setups[i]) setups[i](pinfo); } pktgen_set_port_flags(pinfo, SEND_SINGLE_PKTS); return pinfo; } /** * * pktgen_config_ports - Configure the ports for RX and TX * * DESCRIPTION * Handle setting up the ports in DPDK. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_config_ports(void) { uint16_t pid; port_info_t *pinfo; /* Find out the total number of ports in the system. */ /* We have already block list the ones we needed to in main routine. */ pktgen.nb_ports = rte_eth_dev_count_avail(); if (pktgen.nb_ports == 0) pktgen_log_panic("*** Did not find any ports to use ***"); if (pktgen.nb_ports > RTE_MAX_ETHPORTS) pktgen_log_panic("*** Too many ports in the system %d ***", pktgen.nb_ports); /* Setup the number of ports to display at a time */ pktgen.ending_port = ((pktgen.nb_ports > pktgen.nb_ports_per_page) ? pktgen.nb_ports_per_page : pktgen.nb_ports); RTE_ETH_FOREACH_DEV(pid) { pktgen_log_info("Initialize Port %u ... ", pid); pinfo = initialize_port_info(pid); if (pinfo == NULL) { pktgen_log_info("Failed: port_info_t for port %u initialize\n", pid); return; } } RTE_ETH_FOREACH_DEV(pid) { pktgen_log_info("Initialize Port %u sequence ... ", pid); pinfo = l2p_get_port_pinfo(pid); pktgen_log_info(" Setup sequence defaults"); /* Setup the port and packet defaults */ pktgen_seq_defaults(pid); pktgen_log_info(" Setup range defaults"); pktgen_range_setup(pinfo); pktgen_log_info(" Setup latency defaults"); pktgen_latency_setup(pinfo); pktgen_log_info(" Setup clear stats"); pktgen_clear_stats(pinfo); pktgen_log_info(" Setup random bits"); pktgen_rnd_bits_init(&pinfo->rnd_bitfields); } /* Clear the log information by putting a blank line */ if (pktgen.verbose) dump_device_info(); /* Setup the packet capture per port if needed. */ for (uint16_t sid = 0; sid < coreinfo_socket_cnt(); sid++) pktgen_packet_capture_init(sid); } ================================================ FILE: app/pktgen-port-cfg.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_PORT_CFG_H_ #define _PKTGEN_PORT_CFG_H_ /** * @file * * Per-port configuration, port flags, and key data structures for Pktgen. * * Defines port_info_t (the central per-port state), the SEND_* flag bits * that control TX behaviour, the latency sampling structs, and a set of * diagnostic dump helpers for NIC capabilities. */ #include #include #include #include #include #ifdef TX_DEBUG_PKT_DUMP #include "rte_hexdump.h" #endif #undef BPF_MAJOR_VERSION #include #include "pktgen-seq.h" #include "pktgen-range.h" #include "pktgen-stats.h" #include "pktgen-pcap.h" #include "pktgen-dump.h" #include "pktgen-ether.h" #ifdef __cplusplus extern "C" { #endif #define USER_PATTERN_SIZE 16 /**< Maximum length of the user-defined fill pattern */ #define MAX_LATENCY_ENTRIES \ 50108 /**< Max latency records (limited by latsamp_stats_t.data[] max size) */ // clang-format off enum { /* Per port flag bits */ /* Supported packet modes non-exclusive */ SEND_ARP_REQUEST = (1ULL << 0), /**< Send a ARP request */ SEND_GRATUITOUS_ARP = (1ULL << 1), /**< Send a Gratuitous ARP */ ICMP_ECHO_ENABLE_FLAG = (1ULL << 2), /**< Enable ICMP Echo support */ BONDING_TX_PACKETS = (1ULL << 3), /**< Bonding driver send zero pkts */ /* Receive packet modes */ PROCESS_INPUT_PKTS = (1ULL << 4), /**< Process input packets */ CAPTURE_PKTS = (1ULL << 5), /**< Capture received packets */ SAMPLING_LATENCIES = (1ULL << 6), /**< Sampling latency measurements */ SEND_PING4_REQUEST = (1ULL << 8), /**< Send a IPv4 Ping request */ SEND_PING6_REQUEST = (1ULL << 9), /**< Send a IPv6 Ping request */ /* Exclusive Packet sending modes */ SEND_SINGLE_PKTS = (1ULL << 12), /**< Send single packets */ SEND_PCAP_PKTS = (1ULL << 13), /**< Send a pcap file of packets */ SEND_RANGE_PKTS = (1ULL << 14), /**< Send range of packets */ SEND_SEQ_PKTS = (1ULL << 15), /**< Send sequence of packets */ /* Exclusive Packet type modes */ SEND_RANDOM_PKTS = (1ULL << 16), /**< Send random bitfields in packets */ SEND_VLAN_ID = (1ULL << 17), /**< Send packets with VLAN ID */ SEND_MPLS_LABEL = (1ULL << 18), /**< Send MPLS label */ SEND_Q_IN_Q_IDS = (1ULL << 19), /**< Send packets with Q-in-Q */ SEND_GRE_IPv4_HEADER = (1ULL << 20), /**< Encapsulate IPv4 in GRE */ SEND_GRE_ETHER_HEADER = (1ULL << 21), /**< Encapsulate Ethernet frame in GRE */ SEND_VXLAN_PACKETS = (1ULL << 22), /**< Send VxLAN Packets */ SEND_LATENCY_PKTS = (1ULL << 23), /**< Send latency packets in any mode */ /* Sending flags */ SETUP_TRANSMIT_PKTS = (1ULL << 28), /**< Need to setup transmit packets */ STOP_RECEIVING_PACKETS = (1ULL << 29), /**< Stop receiving packet */ SENDING_PACKETS = (1ULL << 30), /**< sending packets on this port */ SEND_FOREVER = (1ULL << 31), /**< Send packets forever */ SEND_ARP_PING_REQUESTS = (SEND_ARP_REQUEST | SEND_GRATUITOUS_ARP | SEND_PING4_REQUEST | SEND_PING6_REQUEST) }; #define RANDOMIZE_SRC_IP (1ULL << 32) /**< Set the source IP address as random */ #define RANDOMIZE_SRC_PT (1ULL << 33) /**< Set the source port as random */ // clang-format on #define EXCLUSIVE_MODES (SEND_SINGLE_PKTS | SEND_PCAP_PKTS | SEND_RANGE_PKTS | SEND_SEQ_PKTS) #define EXCLUSIVE_PKT_MODES \ (SEND_RANDOM_PKTS | SEND_VLAN_ID | SEND_MPLS_LABEL | SEND_Q_IN_Q_IDS | SEND_GRE_IPv4_HEADER | \ SEND_GRE_ETHER_HEADER | SEND_VXLAN_PACKETS | SEND_LATENCY_PKTS) #define RTE_PMD_PARAM_UNSET -1 /** Payload fill pattern mode. */ typedef enum { ZERO_FILL_PATTERN = 1, /**< Fill payload with zero bytes */ ABC_FILL_PATTERN, /**< Fill payload with repeating 0x00..0xff pattern */ USER_FILL_PATTERN, /**< Fill payload with the user-defined pattern */ NO_FILL_PATTERN, /**< Leave payload bytes unchanged */ } fill_t; /** Function pointer type for a port's TX handler. */ typedef void (*tx_func_t)(struct port_info_s *info, uint16_t qid); #define RING_SIZE 1024 /**< Number of entries in the tail-latency ring buffer */ /** Ring buffer for recording tail-latency samples. */ typedef struct { uint64_t data[RING_SIZE]; /**< Latency sample values (in TSC cycles) */ int head; /**< Index of next write position */ int count; /**< Number of elements currently in the ring */ } latency_ring_t; /** Per-queue latency sample collection buffer. */ typedef struct { uint64_t data[MAX_LATENCY_ENTRIES]; /**< Recorded latency values in cycles */ uint32_t idx; /**< Index to the latencies array */ uint32_t num_samples; /**< Number of latency samples */ uint64_t next; /**< Next latency sample time */ uint64_t reserved[2]; /**< Reserved for future use and align 64 bytes */ } latsamp_stats_t __rte_cache_aligned; /** Per-port latency measurement state and running statistics. */ typedef struct { uint64_t latency_rate_us; /**< Number micro-seconds between injecting packets */ uint64_t jitter_threshold_us; /**< Jitter threshold in micro-seconds */ uint64_t jitter_threshold_cycles; /**< Jitter threshold cycles */ uint64_t latency_rate_cycles; /**< Number of cycles between injections */ uint64_t latency_timo_cycles; /**< Number of cycles to next latency injection */ uint16_t latency_entropy; /**< Entropy value to be used to increment sport */ MARKER stats; /**< Start marker for stats region (used to clear stats) */ uint64_t jitter_count; /**< Number of jitter stats */ uint64_t num_latency_pkts; /**< Total number of latency packets */ uint64_t num_latency_tx_pkts; /**< Total number of TX latency packets */ uint64_t num_skipped; /**< Number of skipped latency packets */ uint64_t running_cycles; /**< Running, Number of cycles per latency packet */ uint64_t prev_cycles; /**< previous cycles cyles time from last latency packet */ uint64_t min_cycles; /**< minimum cycles per latency packet */ uint64_t avg_cycles; /**< average cycles per latency packet */ uint64_t max_cycles; /**< maximum cycles per latency packet */ uint32_t next_index; /**< Next index to use for sending latency packets */ uint32_t expect_index; /**< Expected index for received latency packets */ latency_ring_t tail_latencies; /**< ring buffer for tail latencies */ MARKER end_stats; /**< End marker for stats region (used to clear stats) */ } latency_t; /** Per-queue packet buffer arrays for RX and TX. */ typedef struct per_queue_s { struct rte_mbuf **rx_pkts; /**< Array of pointers to packet buffers for RX */ struct rte_mbuf **tx_pkts; /**< Array of pointers to packet buffers for TX */ } per_queue_t; /** Central per-port state for Pktgen. */ typedef struct port_info_s { struct rte_eth_dev_info dev_info; /**< Device information */ struct rte_eth_conf conf; /**< Configuration settings */ struct rte_eth_link link; /**< Link Information speed and duplex */ struct rte_ether_addr src_mac; /**< Source MAC address of the port */ rte_atomic64_t port_flags; /**< Special send flags for ARP and other */ rte_atomic64_t transmit_count; /**< Packets to transmit loaded into current_tx_count */ rte_atomic64_t current_tx_count; /**< Current number of packets to send */ volatile uint64_t tx_cycles; /**< Number cycles between TX bursts */ pkt_seq_t *seq_pkt; /**< Packet sequence array */ range_info_t range; /**< Range Information */ uint16_t pid; /**< Port ID value */ uint16_t rx_burst; /**< Number of RX burst size */ uint16_t tx_burst; /**< Number of TX burst packets */ uint16_t max_mtu; /**< Maximum MTU size */ uint64_t tx_pps; /**< Transmit packets per seconds */ double tx_rate; /**< Percentage rate for tx packets with fractions */ uint16_t seqIdx; /**< Current Packet sequence index 0 to NUM_SEQ_PKTS */ uint16_t seqCnt; /**< Current packet sequence max count */ uint16_t prime_cnt; /**< Set the number of packets to send in a prime command */ uint16_t vlanid; /**< Set the port VLAN ID value */ uint8_t cos; /**< Set the port 802.1p cos value */ uint8_t tos; /**< Set the port tos value */ uint32_t mpls_entry; /**< Set the port MPLS entry */ uint32_t gre_key; /**< GRE key if used */ per_queue_t per_queue[MAX_QUEUES_PER_PORT]; /**< Per queue info */ FILE *pcap_file; /**< PCAP file handle */ /** Whether the pseudo-header is required when calculating the checksum. * Depends on the original NIC driver (e.g., ixgbe NICs expect the pseudo-header) * See Table 1.133: https://doc.dpdk.org/guides/nics/overview.html */ bool cksum_requires_phdr; struct rnd_bits_s *rnd_bitfields; /**< Random bitfield settings */ char user_pattern[USER_PATTERN_SIZE]; /**< User set pattern values */ fill_t fill_pattern_type; /**< Type of pattern to fill with */ union { uint64_t vxlan; /**< VxLAN 64 bit word */ struct { uint16_t vni_flags; /**< VxLAN Flags */ uint16_t group_id; /**< VxLAN Group Policy ID */ uint32_t vxlan_id; /**< VxLAN VNI */ }; }; port_stats_t stats; /**< Port statistics */ /* Packet dump related */ struct packet { void *data; /**< Packet data */ uint32_t len; /**< Length of data */ } dump_list[MAX_DUMP_PACKETS]; uint8_t dump_head; /**< Index of last packet written to screen */ uint8_t dump_tail; /**< Index of last valid packet in dump_list */ uint8_t dump_count; /**< Number of packets the user requested */ /* Latency sampling data */ /* Depending on MAX_LATENCY_ENTRIES, this could blow up static array memory usage * over the limit allowed by x86_64 architecture */ latency_t latency; /**< Latency information */ latsamp_stats_t latsamp_stats[MAX_QUEUES_PER_PORT]; /**< Per port stats */ uint32_t latsamp_type; /**< Type of lat sampler */ uint32_t latsamp_rate; /**< Sampling rate i.e., samples per second */ uint32_t latsamp_num_samples; /**< Number of samples to collect */ char latsamp_outfile[256]; /**< Path to file for dumping latency samples */ } port_info_t; /** VxLAN tunnel header fields. */ struct vxlan { uint16_t vni_flags; /**< VxLAN Flags */ uint16_t group_id; /**< VxLAN Group Policy ID */ uint32_t vxlan_id; /**< VxLAN VNI */ }; /** * Configure and initialise all Ethernet ports. * * Sets up RX/TX queues, applies offload settings, and populates each * port's port_info_t from the l2p_t mapping. */ void pktgen_config_ports(void); /** * Transmit a burst of packets on a port queue. * * @param pinfo * Per-port state, used to obtain flags and statistics. * @param qid * Queue ID to transmit on. * @param pkts * Array of mbufs to send. * @param nb_pkts * Number of mbufs in @p pkts. */ void tx_send_packets(port_info_t *pinfo, uint16_t qid, struct rte_mbuf **pkts, uint16_t nb_pkts); /** * Atomically subtract a 64-bit value from the tx counter. * * @param v * A pointer to the atomic tx counter. * @param burst * The value to be subtracted from the counter for tx burst size. * @return * The number of packets to burst out */ static inline uint64_t pkt_atomic64_tx_count(rte_atomic64_t *v, int64_t burst) { int success; int64_t tmp2; do { int64_t tmp1 = v->cnt; if (tmp1 == 0) return 0; tmp2 = likely(tmp1 > burst) ? burst : tmp1; success = rte_atomic64_cmpset((volatile uint64_t *)&v->cnt, tmp1, tmp1 - tmp2); } while (success == 0); return tmp2; } /** * Print RX queue configuration to a file stream. * * @param f * Output stream. * @param rx * RX queue configuration to display. */ static inline void rte_eth_rxconf_dump(FILE *f, struct rte_eth_rxconf *rx) { fprintf(f, " RX Conf:\n"); fprintf(f, " pthresh :%5" PRIu16 " | hthresh :%5" PRIu16 " | wthresh :%5" PRIu16 "\n", rx->rx_thresh.pthresh, rx->rx_thresh.hthresh, rx->rx_thresh.wthresh); fprintf(f, " Free Thresh :%5" PRIu16 " | Drop Enable :%5" PRIu16 " | Deferred Start :%5" PRIu16 "\n", rx->rx_free_thresh, rx->rx_drop_en, rx->rx_deferred_start); fprintf(f, " offloads :%016" PRIx64 "\n", rx->offloads); } /** * Print TX queue configuration to a file stream. * * @param f * Output stream. * @param tx * TX queue configuration to display. */ static inline void rte_eth_txconf_dump(FILE *f, struct rte_eth_txconf *tx) { fprintf(f, " TX Conf:\n"); fprintf(f, " pthresh :%5" PRIu16 " | hthresh :%5" PRIu16 " | wthresh :%5" PRIu16 "\n", tx->tx_thresh.pthresh, tx->tx_thresh.hthresh, tx->tx_thresh.wthresh); fprintf(f, " Free Thresh :%5" PRIu16 " | RS Thresh :%5" PRIu16 " | Deferred Start :%5" PRIu16 "\n", tx->tx_free_thresh, tx->tx_rs_thresh, tx->tx_deferred_start); fprintf(f, " offloads :%016" PRIx64 "\n", tx->offloads); } /** * Print descriptor limits for an RX or TX queue. * * @param f * Output stream. * @param lim * Descriptor limit structure to display. * @param tx_flag * Non-zero to label the output as TX, zero for RX. */ static inline void rte_eth_desc_lim_dump(FILE *f, struct rte_eth_desc_lim *lim, int tx_flag) { fprintf(f, " %s: descriptor Limits\n", tx_flag ? "Tx" : "Rx"); fprintf(f, " nb_max :%5" PRIu16 " | nb_min :%5" PRIu16 " | nb_align :%5" PRIu16 "\n", lim->nb_max, lim->nb_min, lim->nb_align); fprintf(f, " nb_seg_max :%5" PRIu16 " | nb_mtu_seg_max :%5" PRIu16 "\n", lim->nb_seg_max, lim->nb_mtu_seg_max); } /** * Print the default RX or TX port configuration. * * @param f * Output stream. * @param conf * Default port configuration to display. * @param tx_flag * Non-zero to label the output as TX, zero for RX. */ static inline void rte_eth_dev_portconf_dump(FILE *f, struct rte_eth_dev_portconf *conf, int tx_flag) { fprintf(f, " %s: Port Config (Default)\n", tx_flag ? "Tx" : "Rx"); fprintf(f, " burst_size :%5" PRIu16 " | ring_size :%5" PRIu16 " | nb_queues :%5" PRIu16 "\n", conf->burst_size, conf->ring_size, conf->nb_queues); } /** * Print switch info for a device. * * @param f * Output stream. * @param sw * Switch info structure to display. */ static inline void rte_eth_switch_info_dump(FILE *f, struct rte_eth_switch_info *sw) { fprintf(f, " Switch Info: %s\n", sw->name); fprintf(f, " domain_id :%5" PRIu16 " | port_id :%5" PRIu16 "\n", sw->domain_id, sw->port_id); } /** * Format the RX offload capability flags as a space-separated string. * * @param rx_capa * Bitmask of RTE_ETH_RX_OFFLOAD_* flags. * @param buf * Output buffer to write the names into. * @param len * Size of @p buf in bytes. * @return * 0 on success, -1 if @p len is zero or the buffer is too small. */ static inline int rte_get_rx_capa_list(uint64_t rx_capa, char *buf, size_t len) { uint32_t i; int ret; #define _(x) #x struct { uint64_t flag; const char *name; } rx_flags[] = {{RTE_ETH_RX_OFFLOAD_VLAN_STRIP, _(VLAN_STRIP)}, {RTE_ETH_RX_OFFLOAD_IPV4_CKSUM, _(IPV4_CKSUM)}, {RTE_ETH_RX_OFFLOAD_UDP_CKSUM, _(UDP_CKSUM)}, {RTE_ETH_RX_OFFLOAD_TCP_CKSUM, _(TCP_CKSUM)}, {RTE_ETH_RX_OFFLOAD_TCP_LRO, _(TCP_LRO)}, {RTE_ETH_RX_OFFLOAD_QINQ_STRIP, _(QINQ_STRIP)}, {RTE_ETH_RX_OFFLOAD_OUTER_IPV4_CKSUM, _(OUTER_IPV4_CKSUM)}, {RTE_ETH_RX_OFFLOAD_MACSEC_STRIP, _(MACSEC_STRIP)}, {RTE_ETH_RX_OFFLOAD_VLAN_FILTER, _(VLAN_FILTER)}, {RTE_ETH_RX_OFFLOAD_VLAN_EXTEND, _(VLAN_EXTEND)}, {RTE_ETH_RX_OFFLOAD_SCATTER, _(SCATTER)}, {RTE_ETH_RX_OFFLOAD_TIMESTAMP, _(TIMESTAMP)}, {RTE_ETH_RX_OFFLOAD_SECURITY, _(SECURITY)}, {RTE_ETH_RX_OFFLOAD_KEEP_CRC, _(KEEP_CRC)}, {RTE_ETH_RX_OFFLOAD_SCTP_CKSUM, _(SCTP_CKSUM)}, {RTE_ETH_RX_OFFLOAD_OUTER_UDP_CKSUM, _(OUTER_UDP_CKSUM)}, {RTE_ETH_RX_OFFLOAD_RSS_HASH, _(RSS_HASH)}, {RTE_ETH_RX_OFFLOAD_BUFFER_SPLIT, _(BUFFER_SPLIT)}}; #undef _ if (len == 0) return -1; buf[0] = '\0'; for (i = 0; i < RTE_DIM(rx_flags); i++) { if ((rx_capa & rx_flags[i].flag) != rx_flags[i].flag) continue; ret = snprintf(buf, len, "%s ", rx_flags[i].name); if (ret < 0) return -1; if ((size_t)ret >= len) return -1; buf += ret; len -= ret; } return 0; } /** * Format the TX offload capability flags as a space-separated string. * * @param tx_capa * Bitmask of RTE_ETH_TX_OFFLOAD_* flags. * @param buf * Output buffer to write the names into. * @param len * Size of @p buf in bytes. * @return * 0 on success, -1 if @p len is zero or the buffer is too small. */ static inline int rte_get_tx_capa_list(uint64_t tx_capa, char *buf, size_t len) { uint32_t i; int ret; #define _(x) #x struct { uint64_t flag; const char *name; } tx_flags[] = { {RTE_ETH_TX_OFFLOAD_VLAN_INSERT, _(VLAN_INSERT)}, {RTE_ETH_TX_OFFLOAD_IPV4_CKSUM, _(IPV4_CKSUM)}, {RTE_ETH_TX_OFFLOAD_UDP_CKSUM, _(UDP_CKSUM)}, {RTE_ETH_TX_OFFLOAD_TCP_CKSUM, _(TCP_CKSUM)}, {RTE_ETH_TX_OFFLOAD_SCTP_CKSUM, _(SCTP_CKSUM)}, {RTE_ETH_TX_OFFLOAD_TCP_TSO, _(TCP_TSO)}, {RTE_ETH_TX_OFFLOAD_UDP_TSO, _(UDP_TSO)}, {RTE_ETH_TX_OFFLOAD_OUTER_IPV4_CKSUM, _(OUTER_IPV4_CKSUM)}, {RTE_ETH_TX_OFFLOAD_QINQ_INSERT, _(QINQ_INSERT)}, {RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO, _(VXLAN_TNL_TSO)}, {RTE_ETH_TX_OFFLOAD_GRE_TNL_TSO, _(GRE_TNL_TSO)}, {RTE_ETH_TX_OFFLOAD_IPIP_TNL_TSO, _(IPIP_TNL_TSO)}, {RTE_ETH_TX_OFFLOAD_GENEVE_TNL_TSO, _(GENEVE_TNL_TSO)}, {RTE_ETH_TX_OFFLOAD_MACSEC_INSERT, _(MACSEC_INSERT)}, {RTE_ETH_TX_OFFLOAD_MT_LOCKFREE, _(MT_LOCKFREE)}, {RTE_ETH_TX_OFFLOAD_MULTI_SEGS, _(MULTI_SEGS)}, {RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE, _(MBUF_FAST_FREE)}, {RTE_ETH_TX_OFFLOAD_SECURITY, _(SECURITY)}, {RTE_ETH_TX_OFFLOAD_UDP_TNL_TSO, _(UDP_TNL_TSO)}, {RTE_ETH_TX_OFFLOAD_IP_TNL_TSO, _(IP_TNL_TSO)}, {RTE_ETH_TX_OFFLOAD_OUTER_UDP_CKSUM, _(OUTER_UDP_CKSUM)}, }; #undef _ if (len == 0) return -1; buf[0] = '\0'; for (i = 0; i < RTE_DIM(tx_flags); i++) { if ((tx_capa & tx_flags[i].flag) != tx_flags[i].flag) continue; ret = snprintf(buf, len, "%s ", tx_flags[i].name); if (ret < 0) return -1; if ((size_t)ret >= len) return -1; buf += ret; len -= ret; } return 0; } /** * Print a comprehensive device information summary to a file stream. * * @param f * Output stream (defaults to stderr if NULL). * @param pid * Port ID whose device info is displayed. */ static inline void rte_eth_dev_info_dump(FILE *f, uint16_t pid) { struct rte_eth_dev_info dev_info, *di = &dev_info; char buf[512]; if (rte_eth_dev_info_get(pid, &dev_info) < 0) { fprintf(f, "Failed to get eth dev info for port %u\n", pid); return; } if (!f) f = stderr; char dev_name[64]; rte_eth_dev_get_name_by_port(pid, dev_name); fprintf(f, "\n** Device Info (%s, if_index:%" PRId32 ", flags %08" PRIu32 ") **\n", dev_name, di->if_index, *di->dev_flags); fprintf(f, " min_rx_bufsize :%5" PRIu32 " max_rx_pktlen :%5" PRIu32 " hash_key_size :%5" PRIu8 "\n", di->min_rx_bufsize, di->max_rx_pktlen, di->hash_key_size); fprintf(f, " max_rx_queues :%5" PRIu16 " max_tx_queues :%5" PRIu16 " max_vfs :%5" PRIu16 "\n", di->max_rx_queues, di->max_tx_queues, di->max_vfs); fprintf(f, " max_mac_addrs :%5" PRIu32 " max_hash_mac_addrs:%5" PRIu32 " max_vmdq_pools:%5" PRIu16 "\n", di->max_mac_addrs, di->max_hash_mac_addrs, di->max_vmdq_pools); fprintf(f, " vmdq_queue_base:%5" PRIu16 " vmdq_queue_num :%5" PRIu16 " vmdq_pool_base:%5" PRIu16 "\n", di->vmdq_queue_base, di->vmdq_queue_num, di->vmdq_pool_base); fprintf(f, " nb_rx_queues :%5" PRIu16 " nb_tx_queues :%5" PRIu16 " speed_capa : %08" PRIx32 "\n", di->nb_rx_queues, di->nb_tx_queues, di->speed_capa); fprintf(f, "\n"); fprintf(f, " flow_type_rss_offloads:%016" PRIx64 " reta_size :%5" PRIu16 "\n", di->flow_type_rss_offloads, di->reta_size); rte_get_rx_capa_list(di->rx_offload_capa, buf, sizeof(buf)); fprintf(f, " rx_offload_capa :%s\n", buf); rte_get_tx_capa_list(di->tx_offload_capa, buf, sizeof(buf)); fprintf(f, " tx_offload_capa :%s\n", buf); fprintf(f, " rx_queue_offload_capa :%016" PRIx64, di->rx_queue_offload_capa); fprintf(f, " tx_queue_offload_capa :%016" PRIx64 "\n", di->tx_queue_offload_capa); fprintf(f, " dev_capa :%016" PRIx64 "\n", di->dev_capa); fprintf(f, "\n"); rte_eth_rxconf_dump(f, &di->default_rxconf); rte_eth_txconf_dump(f, &di->default_txconf); rte_eth_desc_lim_dump(f, &di->rx_desc_lim, 0); rte_eth_desc_lim_dump(f, &di->tx_desc_lim, 1); rte_eth_dev_portconf_dump(f, &di->default_rxportconf, 0); rte_eth_dev_portconf_dump(f, &di->default_txportconf, 1); rte_eth_switch_info_dump(f, &di->switch_info); fprintf(f, "\n"); } #ifdef __cplusplus } #endif #endif /* _PKTGEN_PORT_CFG_H_ */ ================================================ FILE: app/pktgen-random.c ================================================ /*- * Copyright(c) <2016-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2016 by Keith Wiles @ intel.com */ #include "pktgen-random.h" #include #include #include #include #include "lua_config.h" #include "pktgen-display.h" #include "pktgen-log.h" /* Allow PRNG function to be changed at runtime for testing*/ #ifdef TESTING static rnd_func_t _rnd_func = NULL; #endif /* Forward declaration */ static void pktgen_init_default_rnd(void); /** * * pktgen_rnd_bits_init - Initialize random bitfield structures and PRNG * * DESCRIPTION * Initialize the random bitfield structures the PRNG context. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_rnd_bits_init(rnd_bits_t **rnd_bits) { int i; *rnd_bits = (rnd_bits_t *)rte_zmalloc_socket("Random bitfield structure", sizeof(rnd_bits_t), 0, pg_socket_id()); pktgen_display_set_color("stats.stat.values"); /* Initialize mask to all ignore */ for (i = 0; i < MAX_RND_BITFIELDS; ++i) { pktgen_set_random_bitfield(*rnd_bits, i, 0, "????????????????????????????????"); /* 32 ?'s */ pktgen_set_random_bitfield(*rnd_bits, i, 0, ""); } pktgen_init_default_rnd(); } /** * * pktgen_set_random_bitfield - Set random bit specification * * DESCRIPTION * Set random bit specification. This extracts the 0, 1 and random bitmasks from * the provided bitmask template. * * RETURNS: Active specifications * * SEE ALSO: */ uint32_t pktgen_set_random_bitfield(rnd_bits_t *rnd_bits, uint8_t idx, uint8_t offset, const char *mask) { if (idx >= MAX_RND_BITFIELDS) goto leave; size_t mask_len = strlen(mask); if (mask_len > MAX_BITFIELD_SIZE) goto leave; /* Disable spec number idx when no mask is specified */ if (mask_len == 0) { rnd_bits->active_specs &= ~((uint32_t)1 << idx); goto leave; } /* Iterate over specified mask from left to right. The mask components are * shifted left and filled at the right side. * When the complete mask has been processed, for each position exactly 1 * mask component has a 1 bit set. */ BITFIELD_T mask0 = 0, mask1 = 0, maskRnd = 0; uint32_t i; for (i = 0; i < mask_len; ++i) { mask0 <<= 1; mask1 <<= 1; maskRnd <<= 1; switch (mask[i]) { case '0': mask0 += 1; break; case '1': mask1 += 1; break; case '.': /* ignore bit */ break; case 'x': case 'X': maskRnd += 1; break; default: /* print error: "Unknown char in bitfield spec" */ goto leave; } } /* Shift bitmasks to MSB position, so the bitmask starts at the provided * offset. * This way the mask can be used as a full-width BITFIELD_T, even when the * provided mask is shorter than the number of bits a BITFIELD_T can store. */ int pad_len = MAX_BITFIELD_SIZE - mask_len; mask0 <<= pad_len; mask1 <<= pad_len; maskRnd <<= pad_len; rnd_bits->active_specs |= (1 << idx); rnd_bits->specs[idx].offset = offset; /* andMask is used to clear certain bits. Bits corresponding to a 1 bit in * the mask will retain their original value, bits corresponding to a 0 bit * in the mask will be cleared. * * - mask0: all set bits in this mask must be 0 in the result * - maskRnd: all set bits in this mask will be zero'd. This enables merging * of a random value by doing a bitwise OR with the random value. */ rnd_bits->specs[idx].andMask = htonl(~(mask0 | maskRnd)); /* orMask is used to set certain bits. */ rnd_bits->specs[idx].orMask = htonl(mask1); /* rndMask is used to filter a generated random value, so all bits that * should not be random are 0. * The result of this filtering operation can then be bitwise OR-ed with * the original value to merge the random value in. * In the original value, the bits that must be random need to be 0. This is * taken care of by taking the rndMask into account for the andMask. */ rnd_bits->specs[idx].rndMask = htonl(maskRnd); leave: return rnd_bits->active_specs; } /** * * pktgen_rnd_bits_apply - Set random bitfields in packet. * * DESCRIPTION * Set bitfields in packet specified packet to random values according to the * requested bitfield specification. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_rnd_bits_apply(port_info_t *pinfo, struct rte_mbuf **pkts, size_t cnt, rnd_bits_t *rbits) { rnd_bits_t *rnd_bits; size_t mbuf_cnt; uint32_t active_specs; uint32_t *pkt_data; BITFIELD_T rnd_value; bf_spec_t *bf_spec; /* the info pointer could be null. */ rnd_bits = (rbits) ? rbits : pinfo->rnd_bitfields; if ((active_specs = rnd_bits->active_specs) == 0) return; for (mbuf_cnt = 0; mbuf_cnt < cnt; ++mbuf_cnt) { bf_spec = rnd_bits->specs; while (active_specs > 0) { if (likely(active_specs & 1)) { /* Get pointer to byte in mbuf data as uint32_t*, so */ /* the masks can be applied. */ pkt_data = (uint32_t *)(&rte_pktmbuf_mtod(pkts[mbuf_cnt], uint8_t *)[bf_spec->offset]); *pkt_data &= bf_spec->andMask; *pkt_data |= bf_spec->orMask; if (bf_spec->rndMask) { #ifdef TESTING /* Allow PRNG to be set when testing */ rnd_value = _rnd_func ? _rnd_func() : pktgen_default_rnd_func(); #else /* ... but allow inlining for production build */ rnd_value = pktgen_default_rnd_func(); #endif rnd_value &= bf_spec->rndMask; *pkt_data |= rnd_value; } } ++bf_spec; /* prepare to use next bitfield spec */ active_specs >>= 1; /* use next bit in active spec bitfield */ } active_specs = rnd_bits->active_specs; } } /** * * pktgen_page_random_bitfields - Display the random bitfields data page. * * DESCRIPTION * Display the random bitfields data page on the screen. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_page_random_bitfields(uint32_t print_labels, uint16_t pid, rnd_bits_t *rnd_bits) { uint32_t row, bitmask_idx, i, curr_bit; char mask[36]; /* 4*8 bits, 3 delimiter spaces, \0 */ bf_spec_t *curr_spec; if (!print_labels) return; mask[35] = '\0'; mask[8] = mask[17] = mask[26] = ' '; display_topline("", 0, 0, 0); row = PORT_FLAGS_ROW; pktgen_display_set_color("top.ports"); scrn_printf(row++, 3, "Port %d", pid); if (rnd_bits == NULL) { scrn_cprintf(10, this_scrn->ncols, "** Port is not active - no random bitfields set **"); row = 28; goto leave; } pktgen_display_set_color("stats.stat.label"); /* Header line */ scrn_printf(row++, 1, "%8s %8s %8s %s", "Index", "Offset", "Act?", "Mask [0 = 0 bit, 1 = 1 bit, X = random bit, . = ignore]"); pktgen_display_set_color("stats.stat.values"); for (bitmask_idx = 0; bitmask_idx < MAX_RND_BITFIELDS; ++bitmask_idx) { curr_spec = &rnd_bits->specs[bitmask_idx]; memset(mask, 0, sizeof(mask)); memset(mask, ' ', sizeof(mask) - 1); /* Compose human readable bitmask representation */ for (i = 0; i < MAX_BITFIELD_SIZE; ++i) { curr_bit = (uint32_t)1 << (MAX_BITFIELD_SIZE - i - 1); /* + i >> 3 for space delimiter after every 8 bits. * Need to check rndMask before andMask: for random bits, the * andMask is also 0. */ mask[i + (i >> 3)] = ((ntohl(curr_spec->rndMask) & curr_bit) != 0) ? 'X' : ((ntohl(curr_spec->andMask) & curr_bit) == 0) ? '0' : ((ntohl(curr_spec->orMask) & curr_bit) != 0) ? '1' : '.'; } scrn_printf(row++, 1, "%8d %8d %7s %s", bitmask_idx, curr_spec->offset, (rnd_bits->active_specs & (1 << bitmask_idx)) ? "Yes" : "No", mask); } leave: display_dashline(++row); pktgen_display_set_color(NULL); } static void pktgen_init_default_rnd(void) { FILE *dev_random; int ret; if ((dev_random = fopen("/dev/urandom", "r")) == NULL) { pktgen_log_error("Could not open /dev/urandom for reading"); return; } /* Use contents of /dev/urandom as seed for ISAAC */ ret = fread(xor_state, 1, sizeof(xor_state[0]), dev_random); if (ret != sizeof(xor_state[0])) pktgen_log_warning("Could not read enough random data for PRNG seed (%d)", ret); fclose(dev_random); } #ifdef TESTING /** * * pktgen_set_rnd_func - Set function to use as random number generator * * DESCRIPTION * Set function to use as random number generator. * * RETURNS: Previous random number function (or NULL if the default function was * used). * * SEE ALSO: */ rnd_func_t pktgen_set_rnd_func(rnd_func_t rnd_func) { rnd_func_t prev_rnd_func = _rnd_func; _rnd_func = rnd_func; return prev_rnd_func; } #endif ================================================ FILE: app/pktgen-random.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_RANDOM_H_ #define _PKTGEN_RANDOM_H_ /** * @file * * Per-port random bitfield mask engine for Pktgen. * * Allows independent random bits to be applied to arbitrary byte offsets * within each transmitted packet, enabling fuzz-style traffic generation * without rebuilding the full packet template each burst. */ #include #include #include "pktgen-seq.h" #include "xorshift64star.h" /* PRNG function */ #ifdef __cplusplus extern "C" { #endif #define MAX_RND_BITFIELDS 32 /**< Maximum simultaneous random bitfield specs per port */ #define BITFIELD_T uint32_t /**< Underlying integer type for bitfield masks */ #define MAX_BITFIELD_SIZE (sizeof(BITFIELD_T) << 3) /**< Size of BITFIELD_T in bits */ struct port_info_s; /** * Random bitfield specification — describes one randomised bit range in a packet. */ typedef struct bf_spec_s { uint8_t offset; /**< Offset (in bytes) of where to apply the bitmask */ BITFIELD_T andMask; /**< Mask to bitwise AND value with */ BITFIELD_T orMask; /**< Mask to bitwise OR value with */ BITFIELD_T rndMask; /**< Which bits will get a random value */ } bf_spec_t; /** * Collection of all active random bitfield specs for one port. */ typedef struct rnd_bits_s { uint32_t active_specs; /**< Bitmask identifying which spec slots are active */ bf_spec_t specs[MAX_RND_BITFIELDS]; /**< Per-slot bitfield specifications */ } rnd_bits_t; /** * Allocate and zero-initialise a rnd_bits_t for a port. * * @param rnd_bits * Address of the pointer to fill; on success *rnd_bits points to the * newly allocated structure. */ void pktgen_rnd_bits_init(struct rnd_bits_s **rnd_bits); /** * Configure one random bitfield slot. * * @param rnd_bits * Pointer to the rnd_bits_t to modify. * @param idx * Slot index (0 .. MAX_RND_BITFIELDS-1). * @param offset * Byte offset within the packet where the mask is applied. * @param mask * String of '0', '1', and 'X' characters (MSB first) describing * fixed-0, fixed-1, and randomised bit positions respectively. * @return * 0 on success, non-zero on error. */ uint32_t pktgen_set_random_bitfield(struct rnd_bits_s *rnd_bits, uint8_t idx, uint8_t offset, const char *mask); /** * Apply all active random bitfield specs to a burst of packets. * * @param info * Per-port state (used for port-specific context). * @param pkt * Array of mbuf pointers to modify in place. * @param cnt * Number of mbufs in @p pkt. * @param rbits * Random bitfield configuration to apply. */ void pktgen_rnd_bits_apply(struct port_info_s *info, struct rte_mbuf **pkt, size_t cnt, struct rnd_bits_s *rbits); /** * Render the random-bitfields display page to the terminal. * * @param print_labels * Non-zero to (re)print column headers and labels. * @param pid * Port index whose bitfield settings are displayed. * @param rnd_bits * Pointer to the rnd_bits_t to display. */ void pktgen_page_random_bitfields(uint32_t print_labels, uint16_t pid, struct rnd_bits_s *rnd_bits); /** * Generate a 32-bit random value using the built-in xorshift64* PRNG. * * @return * 32-bit pseudo-random number. */ static __inline__ uint32_t pktgen_default_rnd_func(void) { return (uint32_t)xorshift64star(); } #ifdef TESTING /** Function pointer type for a pluggable random-number generator. */ typedef BITFIELD_T (*rnd_func_t)(void); /** * Replace the active PRNG function (test/validation use only). * * @param rnd_func * New PRNG function to install. * @return * Previously installed PRNG function. */ rnd_func_t pktgen_set_rnd_func(rnd_func_t rnd_func); #endif #ifdef __cplusplus } #endif #endif /* _PKTGEN_RANDOM_H_ */ ================================================ FILE: app/pktgen-range.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include "pktgen-display.h" #include "pktgen-log.h" #include "pktgen.h" /** * * pktgen_range_ctor - Construct a range packet in buffer provided. * * DESCRIPTION * Build the special range packet in the buffer provided. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_range_ctor(range_info_t *range, pkt_seq_t *pkt) { if (pkt->ipProto == PG_IPPROTO_TCP) { if (unlikely(range->tcp_seq_inc != 0)) { uint32_t seq = pkt->tcp_seq; seq += range->tcp_seq_inc; if (seq < range->tcp_seq_min) seq = range->tcp_seq_max; if (seq > range->tcp_seq_max) seq = range->tcp_seq_min; pkt->tcp_seq = seq; } else pkt->tcp_seq = range->tcp_seq; if (unlikely(range->tcp_ack_inc != 0)) { uint32_t ack = pkt->tcp_ack; ack += range->tcp_ack_inc; if (ack < range->tcp_ack_min) ack = range->tcp_ack_max; if (ack > range->tcp_ack_max) ack = range->tcp_ack_min; pkt->tcp_ack = ack; } else pkt->tcp_ack = range->tcp_ack; } switch (pkt->ethType) { case RTE_ETHER_TYPE_IPV4: switch (pkt->ipProto) { case PG_IPPROTO_TCP: case PG_IPPROTO_UDP: if (pkt->dport == PG_IPPROTO_L4_GTPU_PORT) { if (unlikely(range->gtpu_teid_inc != 0)) { uint32_t teid = pkt->gtpu_teid; teid += range->gtpu_teid_inc; if (teid < range->gtpu_teid_min) teid = range->gtpu_teid_max; if (teid > range->gtpu_teid_max) teid = range->gtpu_teid_min; pkt->gtpu_teid = teid; } else pkt->gtpu_teid = range->gtpu_teid; } if (unlikely(range->src_port_inc != 0)) { uint32_t sport = pkt->sport; sport += range->src_port_inc; if (sport < range->src_port_min) sport = range->src_port_max; if (sport > range->src_port_max) sport = range->src_port_min; pkt->sport = (uint16_t)sport; } else pkt->sport = range->src_port; if (unlikely(range->dst_port_inc != 0)) { uint32_t dport = pkt->dport; dport += range->dst_port_inc; if (dport < range->dst_port_min) dport = range->dst_port_max; if (dport > range->dst_port_max) dport = range->dst_port_min; pkt->dport = (uint16_t)dport; } else pkt->dport = range->dst_port; if (unlikely(range->ttl_inc != 0)) { uint16_t ttl = pkt->ttl; ttl += range->ttl_inc; if (ttl < range->ttl_min) ttl = range->ttl_max; if (ttl > range->ttl_max) ttl = range->ttl_min; pkt->ttl = (uint8_t)ttl; } else pkt->ttl = range->ttl; if (unlikely(range->src_ip_inc != 0)) { uint32_t p = pkt->ip_src_addr.addr.ipv4.s_addr; p += range->src_ip_inc; if (p < range->src_ip_min) p = range->src_ip_max; else if (p > range->src_ip_max) p = range->src_ip_min; pkt->ip_src_addr.addr.ipv4.s_addr = p; } else pkt->ip_src_addr.addr.ipv4.s_addr = range->src_ip; if (unlikely(range->dst_ip_inc != 0)) { uint32_t p = pkt->ip_dst_addr.addr.ipv4.s_addr; p += range->dst_ip_inc; if (p < range->dst_ip_min) p = range->dst_ip_max; else if (p > range->dst_ip_max) p = range->dst_ip_min; pkt->ip_dst_addr.addr.ipv4.s_addr = p; } else pkt->ip_dst_addr.addr.ipv4.s_addr = range->dst_ip; if (unlikely(range->vlan_id_inc != 0)) { /* Since VLAN is set to MIN_VLAN_ID, check this and skip first increment * to maintain the range sequence in sync with other range fields */ uint32_t p; static uint8_t min_vlan_set = 0; if ((pkt->vlanid == MIN_VLAN_ID) && !min_vlan_set) { p = 0; min_vlan_set = 1; } else p = pkt->vlanid; p += range->vlan_id_inc; if (p < range->vlan_id_min) p = range->vlan_id_max; else if (p > range->vlan_id_max) p = range->vlan_id_min; pkt->vlanid = p; } else pkt->vlanid = range->vlan_id; if (unlikely(range->cos_inc != 0)) { uint32_t p; static uint8_t min_cos_set = 0; if ((pkt->cos == MIN_COS) && !min_cos_set) { p = 0; min_cos_set = 1; } else p = pkt->cos; p += range->cos_inc; if (p < range->cos_min) p = range->cos_max; else if (p > range->cos_max) p = range->cos_min; pkt->cos = p; } else pkt->cos = range->cos; if (unlikely(range->tos_inc != 0)) { uint32_t p; static uint8_t min_tos_set = 0; if ((pkt->tos == MIN_TOS) && !min_tos_set) { p = 0; min_tos_set = 1; } else p = pkt->tos; p += range->tos_inc; if (p < range->tos_min) p = range->tos_max; else if (p > range->tos_max) p = range->tos_min; pkt->tos = p; } else pkt->tos = range->tos; if (unlikely(range->pkt_size_inc != 0)) { uint32_t p = pkt->pkt_size; p += range->pkt_size_inc; if (p < range->pkt_size_min) p = range->pkt_size_max; else if (p > range->pkt_size_max) p = range->pkt_size_min; pkt->pkt_size = p; } else pkt->pkt_size = range->pkt_size; if (unlikely(range->src_mac_inc != 0)) { uint64_t p; inet_mtoh64(&pkt->eth_src_addr, &p); p += range->src_mac_inc; if (p < range->src_mac_min) p = range->src_mac_max; else if (p > range->src_mac_max) p = range->src_mac_min; inet_h64tom(p, &pkt->eth_src_addr); } else inet_h64tom(range->src_mac, &pkt->eth_src_addr); if (unlikely(range->dst_mac_inc != 0)) { uint64_t p; inet_mtoh64(&pkt->eth_dst_addr, &p); p += range->dst_mac_inc; if (p < range->dst_mac_min) p = range->dst_mac_max; else if (p > range->dst_mac_max) p = range->dst_mac_min; inet_h64tom(p, &pkt->eth_dst_addr); } else inet_h64tom(range->dst_mac, &pkt->eth_dst_addr); if (unlikely(range->vxlan_gid_inc != 0)) { uint32_t gid = pkt->group_id; gid += range->vxlan_gid_inc; if (gid < range->vxlan_gid_min) gid = range->vxlan_gid_max; else if (gid > range->vxlan_gid_max) gid = range->vxlan_gid_min; pkt->group_id = gid; } else pkt->group_id = range->vxlan_gid; if (unlikely(range->vxlan_vid_inc != 0)) { uint32_t vid = pkt->vxlan_id; vid += range->vxlan_gid_inc; if (vid < range->vxlan_vid_min) vid = range->vxlan_vid_max; else if (vid > range->vxlan_vid_max) vid = range->vxlan_vid_min; pkt->group_id = vid; } else pkt->vxlan_id = range->vxlan_vid; break; default: pktgen_log_info("IPv4 ipProto %02x", pkt->ipProto); break; } break; case RTE_ETHER_TYPE_IPV6: switch (pkt->ipProto) { case PG_IPPROTO_UDP: case PG_IPPROTO_TCP: if (unlikely(range->src_port_inc != 0)) { uint16_t sport = pkt->sport; sport += range->src_port_inc; if (sport < range->src_port_min) sport = range->src_port_max; if (sport > range->src_port_max) sport = range->src_port_min; pkt->sport = sport; } else pkt->sport = range->src_port; if (unlikely(range->dst_port_inc != 0)) { uint16_t dport = pkt->dport; dport += range->dst_port_inc; if (dport < range->dst_port_min) dport = range->dst_port_max; if (dport > range->dst_port_max) dport = range->dst_port_min; pkt->dport = dport; } else pkt->dport = range->dst_port; if (unlikely(range->hop_limits_inc != 0)) { uint8_t hop_limits = pkt->hop_limits; hop_limits += range->hop_limits_inc; if (hop_limits < range->hop_limits_min) hop_limits = range->hop_limits_max; if (hop_limits > range->hop_limits_max) hop_limits = range->hop_limits_min; pkt->hop_limits = hop_limits; } else pkt->hop_limits = range->hop_limits; if (unlikely(!inet6AddrIsUnspecified(range->src_ipv6_inc))) { uint8_t p[PG_IN6ADDRSZ]; rte_memcpy(p, &pkt->ip_src_addr.addr.ipv6, sizeof(struct rte_ipv6_addr)); inet6AddrAdd(p, range->src_ipv6_inc, p); if (memcmp(p, range->src_ipv6_min, sizeof(struct rte_ipv6_addr)) < 0) rte_memcpy(p, range->src_ipv6_min, sizeof(struct rte_ipv6_addr)); else if (memcmp(p, range->src_ipv6_max, sizeof(struct rte_ipv6_addr)) > 0) rte_memcpy(p, range->src_ipv6_min, sizeof(struct rte_ipv6_addr)); rte_memcpy(&pkt->ip_src_addr.addr.ipv6, p, sizeof(struct rte_ipv6_addr)); } else rte_memcpy(&pkt->ip_src_addr.addr.ipv6, range->src_ipv6, sizeof(struct rte_ipv6_addr)); if (unlikely(!inet6AddrIsUnspecified(range->dst_ipv6_inc))) { uint8_t p[PG_IN6ADDRSZ]; rte_memcpy(p, &pkt->ip_dst_addr.addr.ipv6, sizeof(struct rte_ipv6_addr)); inet6AddrAdd(p, range->dst_ipv6_inc, p); if (memcmp(p, range->dst_ipv6_min, sizeof(struct rte_ipv6_addr)) < 0) rte_memcpy(p, range->dst_ipv6_min, sizeof(struct rte_ipv6_addr)); else if (memcmp(p, range->dst_ipv6_max, sizeof(struct rte_ipv6_addr)) > 0) rte_memcpy(p, range->dst_ipv6_min, sizeof(struct rte_ipv6_addr)); rte_memcpy(&pkt->ip_dst_addr.addr.ipv6, p, sizeof(struct rte_ipv6_addr)); } else rte_memcpy(&pkt->ip_dst_addr.addr.ipv6, range->dst_ipv6, sizeof(struct rte_ipv6_addr)); if (unlikely(range->vlan_id_inc != 0)) { /* Since VLAN is set to MIN_VLAN_ID, check this and skip first increment * to maintain the range sequence in sync with other range fields */ uint32_t p; static uint8_t min_vlan_set = 0; if ((pkt->vlanid == MIN_VLAN_ID) && !min_vlan_set) { p = 0; min_vlan_set = 1; } else p = pkt->vlanid; p += range->vlan_id_inc; if (p < range->vlan_id_min) p = range->vlan_id_max; else if (p > range->vlan_id_max) p = range->vlan_id_min; pkt->vlanid = p; } else pkt->vlanid = range->vlan_id; if (unlikely(range->cos_inc != 0)) { uint32_t p; static uint8_t min_cos_set = 0; if ((pkt->cos == MIN_COS) && !min_cos_set) { p = 0; min_cos_set = 1; } else p = pkt->cos; p += range->cos_inc; if (p < range->cos_min) p = range->cos_max; else if (p > range->cos_max) p = range->cos_min; pkt->cos = p; } else pkt->cos = range->cos; if (unlikely(range->traffic_class_inc != 0)) { uint32_t p; static uint8_t min_traffic_class_set = 0; if ((pkt->traffic_class == MIN_TOS) && !min_traffic_class_set) { p = 0; min_traffic_class_set = 1; } else p = pkt->traffic_class; p += range->traffic_class_inc; if (p < range->traffic_class_min) p = range->traffic_class_max; else if (p > range->traffic_class_max) p = range->traffic_class_min; pkt->traffic_class = p; } else pkt->traffic_class = range->traffic_class; if (unlikely(range->pkt_size_inc != 0)) { uint32_t p = pkt->pkt_size; p += range->pkt_size_inc; if (p < range->pkt_size_min) p = range->pkt_size_max; else if (p > range->pkt_size_max) p = range->pkt_size_min; pkt->pkt_size = p; } else pkt->pkt_size = range->pkt_size; if (unlikely(range->src_mac_inc != 0)) { uint64_t p; inet_mtoh64(&pkt->eth_src_addr, &p); p += range->src_mac_inc; if (p < range->src_mac_min) p = range->src_mac_max; else if (p > range->src_mac_max) p = range->src_mac_min; inet_h64tom(p, &pkt->eth_src_addr); } else inet_h64tom(range->src_mac, &pkt->eth_src_addr); if (unlikely(range->dst_mac_inc != 0)) { uint64_t p; inet_mtoh64(&pkt->eth_dst_addr, &p); p += range->dst_mac_inc; if (p < range->dst_mac_min) p = range->dst_mac_max; else if (p > range->dst_mac_max) p = range->dst_mac_min; inet_h64tom(p, &pkt->eth_dst_addr); } else inet_h64tom(range->dst_mac, &pkt->eth_dst_addr); break; default: pktgen_log_info("IPv6 ipProto %04x", pkt->ipProto); break; } break; default: pktgen_log_info("ethType %04x", pkt->ethType); break; } } /** * * pktgen_print_range - Display the range data page. * * DESCRIPTION * Display the range data page on the screen. * * RETURNS: N/A * * SEE ALSO: */ static void pktgen_print_range(void) { port_info_t *pinfo; range_info_t *range; uint32_t pid, col, sp; char buff[32]; int32_t row; int32_t col_0 = COLUMN_WIDTH_0 + 1, col_1 = COLUMN_WIDTH_1 + 4; struct rte_ether_addr eaddr; char str[64]; display_topline("", pktgen.starting_port, (pktgen.ending_port - 1), pktgen.nb_ports); pktgen_display_set_color("stats.stat.label"); row = PORT_FLAGS_ROW; scrn_printf(row++, 1, "%-*s", col_0, "Port #"); scrn_printf(row++, 1, "%-*s", col_0, "IP.proto"); row++; scrn_printf(row++, 1, "%-*s", col_0, "dst.ip"); scrn_printf(row++, 1, "%-*s", col_0, " min"); scrn_printf(row++, 1, "%-*s", col_0, " max"); scrn_printf(row++, 1, "%-*s", col_0, " inc"); row++; scrn_printf(row++, 1, "%-*s", col_0, "src.ip"); scrn_printf(row++, 1, "%-*s", col_0, " min"); scrn_printf(row++, 1, "%-*s", col_0, " max"); scrn_printf(row++, 1, "%-*s", col_0, " inc"); row++; scrn_printf(row++, 1, "%-*s", col_0, "dst.port :min/max/inc"); scrn_printf(row++, 1, "%-*s", col_0, "src.port :min/max/inc"); scrn_printf(row++, 1, "%-*s", col_0, "ttl :min/max/inc"); scrn_printf(row++, 1, "%-*s", col_0, "vlan.id :min/max/inc"); scrn_printf(row++, 1, "%-*s", col_0, "cos :min/max/inc"); scrn_printf(row++, 1, "%-*s", col_0, "tos :min/max/inc"); scrn_printf(row++, 1, "%-*s", col_0, "pkt.size :min/max/inc"); scrn_printf(row++, 1, "%-*s", col_0, "gtpu.teid:min/max/inc"); scrn_printf(row++, 1, "%-*s", col_0, "vxlan.gid:min/max/inc"); scrn_printf(row++, 1, "%-*s", col_0, " vid:min/max/inc"); row++; scrn_printf(row++, 1, "%-*s", col_0, "tcp.flags"); scrn_printf(row++, 1, "%-*s", col_0, "tcp.seq :min/max/inc"); scrn_printf(row++, 1, "%-*s", col_0, "tcp.ack :min/max/inc"); row++; scrn_printf(row++, 1, "%-*s", col_0, "dst.mac"); scrn_printf(row++, 1, "%-*s", col_0, " min"); scrn_printf(row++, 1, "%-*s", col_0, " max"); scrn_printf(row++, 1, "%-*s", col_0, " inc"); row++; scrn_printf(row++, 1, "%-*s", col_0, "src.mac"); scrn_printf(row++, 1, "%-*s", col_0, " min"); scrn_printf(row++, 1, "%-*s", col_0, " max"); scrn_printf(row++, 1, "%-*s", col_0, " inc"); /* Get the last location to use for the window starting row. */ pktgen.last_row = ++row; display_dashline(pktgen.last_row); /* Display the colon after the row label. */ pktgen_print_div(3, pktgen.last_row - 1, col_0 - 1); sp = pktgen.starting_port; for (pid = 0; pid < pktgen.nb_ports_per_page; pid++) { pinfo = l2p_get_port_pinfo(pid + sp); if (pinfo == NULL) continue; /* Display Port information Src/Dest IP addr, Netmask, Src/Dst MAC addr */ col = (col_1 * pid) + col_0; row = PORT_FLAGS_ROW; pktgen_display_set_color("stats.stat.label"); /* Display the port number for the column */ snprintf(buff, sizeof(buff), "Port-%d", pid + sp); scrn_printf(row++, col, "%*s", col_1, buff); pktgen_display_set_color("stats.stat.values"); range = &pinfo->range; scrn_printf(row++, col, "%*s", col_1, (range->ip_proto == PG_IPPROTO_TCP) ? "TCP" : "UDP"); if (pinfo->seq_pkt[RANGE_PKT].ethType == RTE_ETHER_TYPE_IPV6) { row++; scrn_printf( row++, col, "%*s", col_1, inet_ntop6(buff, sizeof(buff), range->dst_ipv6, PG_PREFIXMAX | ((col_1 - 1) << 8))); scrn_printf(row++, col, "%*s", col_1, inet_ntop6(buff, sizeof(buff), range->dst_ipv6_min, PG_PREFIXMAX | ((col_1 - 1) << 8))); scrn_printf(row++, col, "%*s", col_1, inet_ntop6(buff, sizeof(buff), range->dst_ipv6_max, PG_PREFIXMAX | ((col_1 - 1) << 8))); scrn_printf(row++, col, "%*s", col_1, inet_ntop6(buff, sizeof(buff), range->dst_ipv6_inc, PG_PREFIXMAX | ((col_1 - 1) << 8))); row++; scrn_printf( row++, col, "%*s", col_1, inet_ntop6(buff, sizeof(buff), range->src_ipv6, PG_PREFIXMAX | ((col_1 - 1) << 8))); scrn_printf(row++, col, "%*s", col_1, inet_ntop6(buff, sizeof(buff), range->src_ipv6_min, PG_PREFIXMAX | ((col_1 - 1) << 8))); scrn_printf(row++, col, "%*s", col_1, inet_ntop6(buff, sizeof(buff), range->src_ipv6_max, PG_PREFIXMAX | ((col_1 - 1) << 8))); scrn_printf(row++, col, "%*s", col_1, inet_ntop6(buff, sizeof(buff), range->src_ipv6_inc, PG_PREFIXMAX | ((col_1 - 1) << 8))); } else { row++; scrn_printf(row++, col, "%*s", col_1, inet_ntop4(buff, sizeof(buff), htonl(range->dst_ip), 0xFFFFFFFF)); scrn_printf(row++, col, "%*s", col_1, inet_ntop4(buff, sizeof(buff), htonl(range->dst_ip_min), 0xFFFFFFFF)); scrn_printf(row++, col, "%*s", col_1, inet_ntop4(buff, sizeof(buff), htonl(range->dst_ip_max), 0xFFFFFFFF)); scrn_printf(row++, col, "%*s", col_1, inet_ntop4(buff, sizeof(buff), htonl(range->dst_ip_inc), 0xFFFFFFFF)); row++; scrn_printf(row++, col, "%*s", col_1, inet_ntop4(buff, sizeof(buff), htonl(range->src_ip), 0xFFFFFFFF)); scrn_printf(row++, col, "%*s", col_1, inet_ntop4(buff, sizeof(buff), htonl(range->src_ip_min), 0xFFFFFFFF)); scrn_printf(row++, col, "%*s", col_1, inet_ntop4(buff, sizeof(buff), htonl(range->src_ip_max), 0xFFFFFFFF)); scrn_printf(row++, col, "%*s", col_1, inet_ntop4(buff, sizeof(buff), htonl(range->src_ip_inc), 0xFFFFFFFF)); } row++; snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->dst_port, range->dst_port_min, range->dst_port_max, range->dst_port_inc); scrn_printf(row++, col, "%*s", col_1, str); snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->src_port, range->src_port_min, range->src_port_max, range->src_port_inc); scrn_printf(row++, col, "%*s", col_1, str); pinfo->seq_pkt[RANGE_PKT].ethType == RTE_ETHER_TYPE_IPV6 ? snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->hop_limits, range->hop_limits_min, range->hop_limits_max, range->hop_limits_inc) : snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->ttl, range->ttl_min, range->ttl_max, range->ttl_inc); scrn_printf(row++, col, "%*s", col_1, str); snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->vlan_id, range->vlan_id_min, range->vlan_id_max, range->vlan_id_inc); scrn_printf(row++, col, "%*s", col_1, str); snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->cos, range->cos_min, range->cos_max, range->cos_inc); scrn_printf(row++, col, "%*s", col_1, str); pinfo->seq_pkt[RANGE_PKT].ethType == RTE_ETHER_TYPE_IPV6 ? snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->traffic_class, range->traffic_class_min, range->traffic_class_max, range->traffic_class_inc) : snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->tos, range->tos_min, range->tos_max, range->tos_inc); scrn_printf(row++, col, "%*s", col_1, str); snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->pkt_size + RTE_ETHER_CRC_LEN, range->pkt_size_min + RTE_ETHER_CRC_LEN, range->pkt_size_max + RTE_ETHER_CRC_LEN, range->pkt_size_inc); scrn_printf(row++, col, "%*s", col_1, str); snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->gtpu_teid, range->gtpu_teid_min, range->gtpu_teid_max, range->gtpu_teid_inc); scrn_printf(row++, col, "%*s", col_1, str); snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->vxlan_gid, range->vxlan_gid_min, range->vxlan_gid_max, range->vxlan_gid_inc); scrn_printf(row++, col, "%*s", col_1, str); snprintf(str, sizeof(str), "%5d:%5d/%5d/%5d", range->vxlan_vid, range->vxlan_vid_min, range->vxlan_vid_max, range->vxlan_vid_inc); scrn_printf(row++, col, "%*s", col_1, str); row++; snprintf(str, sizeof(str), "%s%s%s%s%s%s", range->tcp_flags & URG_FLAG ? "U" : ".", range->tcp_flags & ACK_FLAG ? "A" : ".", range->tcp_flags & PSH_FLAG ? "P" : ".", range->tcp_flags & RST_FLAG ? "R" : ".", range->tcp_flags & SYN_FLAG ? "S" : ".", range->tcp_flags & FIN_FLAG ? "F" : "."); scrn_printf(row++, col, "%*s", col_1, str); snprintf(str, sizeof(str), "%x:%x/%x/%x", range->tcp_seq, range->tcp_seq_min, range->tcp_seq_max, range->tcp_seq_inc); scrn_printf(row++, col, "%*s", col_1, str); snprintf(str, sizeof(str), "%x:%x/%x/%x", range->tcp_ack, range->tcp_ack_min, range->tcp_ack_max, range->tcp_ack_inc); scrn_printf(row++, col, "%*s", col_1, str); row++; scrn_printf(row++, col, "%*s", col_1, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac, &eaddr))); scrn_printf(row++, col, "%*s", col_1, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac_min, &eaddr))); scrn_printf(row++, col, "%*s", col_1, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac_max, &eaddr))); scrn_printf(row++, col, "%*s", col_1, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->dst_mac_inc, &eaddr))); row++; scrn_printf(row++, col, "%*s", col_1, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac, &eaddr))); scrn_printf(row++, col, "%*s", col_1, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac_min, &eaddr))); scrn_printf(row++, col, "%*s", col_1, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac_max, &eaddr))); scrn_printf(row++, col, "%*s", col_1, inet_mtoa(buff, sizeof(buff), inet_h64tom(range->src_mac_inc, &eaddr))); } pktgen_display_set_color(NULL); pktgen.flags &= ~PRINT_LABELS_FLAG; } /** * * pktgen_page_range - Display the range data page. * * DESCRIPTION * Display the range data page for a given port. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_page_range(void) { if (pktgen.flags & PRINT_LABELS_FLAG) pktgen_print_range(); } /** * * pktgen_range_setup - Setup the default values for a range port. * * DESCRIPTION * Setup the default range data for a given port. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_range_setup(port_info_t *pinfo) { range_info_t *range = &pinfo->range; range->ip_proto = pinfo->seq_pkt[SINGLE_PKT].ipProto; range->dst_ip = IPv4(192, 168, pinfo->pid + 1, 1); range->dst_ip_min = IPv4(192, 168, pinfo->pid + 1, 1); range->dst_ip_max = IPv4(192, 168, pinfo->pid + 1, 254); range->dst_ip_inc = 0x00000001; range->src_ip = IPv4(192, 168, pinfo->pid, 1); range->src_ip_min = IPv4(192, 168, pinfo->pid, 1); range->src_ip_max = IPv4(192, 168, pinfo->pid, 254); range->src_ip_inc = 0x00000000; range->dst_port = pinfo->seq_pkt[SINGLE_PKT].dport; range->dst_port_inc = 0x0001; range->dst_port_min = 0; range->dst_port_max = 65535; range->src_port = pinfo->seq_pkt[SINGLE_PKT].sport; range->src_port_inc = 0x0001; range->src_port_min = 0; range->src_port_max = 65535; range->ttl = pinfo->seq_pkt[SINGLE_PKT].ttl; range->ttl_inc = 0; range->ttl_min = 0; range->ttl_max = 255; range->vlan_id = pinfo->seq_pkt[SINGLE_PKT].vlanid; range->vlan_id_inc = 0; range->vlan_id_min = MIN_VLAN_ID; range->vlan_id_max = MAX_VLAN_ID; range->cos = pinfo->seq_pkt[SINGLE_PKT].cos; range->cos_inc = 0; range->cos_min = MIN_COS; range->cos_max = MAX_COS; range->tos = pinfo->seq_pkt[SINGLE_PKT].tos; range->tos_inc = 0; range->tos_min = MIN_TOS; range->tos_max = MAX_TOS; range->pkt_size = (RTE_ETHER_MIN_LEN - RTE_ETHER_CRC_LEN); range->pkt_size_inc = 0; range->pkt_size_min = (RTE_ETHER_MIN_LEN - RTE_ETHER_CRC_LEN); range->pkt_size_max = (RTE_ETHER_MAX_LEN - RTE_ETHER_CRC_LEN); range->pkt_size_max = (pktgen.flags & JUMBO_PKTS_FLAG) ? RTE_ETHER_MAX_JUMBO_FRAME_LEN : RTE_ETHER_MAX_LEN; range->pkt_size_max -= RTE_ETHER_CRC_LEN; range->vxlan_gid = pinfo->seq_pkt[SINGLE_PKT].group_id; range->vxlan_gid_inc = 0; range->vxlan_gid_min = 0; range->vxlan_gid_max = 65535; range->vxlan_vid = pinfo->seq_pkt[SINGLE_PKT].vxlan_id; range->vxlan_vid_inc = 0; range->vxlan_vid_min = 0; range->vxlan_vid_max = 65535; /* Should be 24 bits */ range->vni_flags = pinfo->vni_flags; range->tcp_flags = pinfo->seq_pkt[SINGLE_PKT].tcp_flags; range->tcp_seq = pinfo->seq_pkt[SINGLE_PKT].tcp_seq; range->tcp_seq_inc = 0; range->tcp_seq_min = 0; range->tcp_seq_max = MAX_TCP_SEQ_NUMBER; range->tcp_ack = pinfo->seq_pkt[SINGLE_PKT].tcp_ack; range->tcp_ack_inc = 0; range->tcp_ack_min = 0; range->tcp_ack_max = MAX_TCP_ACK_NUMBER; pinfo->seq_pkt[RANGE_PKT].pkt_size = range->pkt_size; inet_mtoh64(&pinfo->seq_pkt[SINGLE_PKT].eth_dst_addr, &range->dst_mac); memset(&range->dst_mac_inc, 0, sizeof(range->dst_mac_inc)); memset(&range->dst_mac_min, 0, sizeof(range->dst_mac_min)); memset(&range->dst_mac_max, 0, sizeof(range->dst_mac_max)); inet_mtoh64(&pinfo->seq_pkt[SINGLE_PKT].eth_src_addr, &range->src_mac); memset(&range->src_mac_inc, 0, sizeof(range->src_mac_inc)); memset(&range->src_mac_min, 0, sizeof(range->src_mac_min)); memset(&range->src_mac_max, 0, sizeof(range->src_mac_max)); } ================================================ FILE: app/pktgen-range.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_RANGE_H_ #define _PKTGEN_RANGE_H_ /** * @file * * Range-mode packet field cycling for Pktgen. * * Defines the range_info_t structure that stores per-port start, min, max, * and increment values for every field that can sweep across a configurable * range during SEND_RANGE_PKTS traffic generation. */ #include #include "pktgen-seq.h" #ifdef __cplusplus extern "C" { #endif typedef struct range_info_s { /** Source IP increment (per-burst step; IPv4 or IPv6 depending on mode) */ union { uint32_t src_ip_inc; uint8_t src_ipv6_inc[PG_IN6ADDRSZ]; }; /** Destination IP increment (per-burst step; IPv4 or IPv6 depending on mode) */ union { uint32_t dst_ip_inc; uint8_t dst_ipv6_inc[PG_IN6ADDRSZ]; }; uint16_t src_port_inc; /**< Source port increment */ uint16_t dst_port_inc; /**< Destination port increment */ uint16_t vlan_id_inc; /**< VLAN id increment */ uint16_t tcp_seq_inc; /**< TCP sequence number increment */ uint16_t tcp_ack_inc; /**< TCP acknowledgement number increment */ /** ToS / traffic-class increment (per-burst step) */ union { uint16_t tos_inc; uint16_t traffic_class_inc; }; uint16_t cos_inc; /**< CoS / priority value increment */ uint16_t pkt_size_inc; /**< Packet size increment */ uint64_t src_mac_inc; /**< Source MAC increment */ uint64_t dst_mac_inc; /**< Destination MAC increment */ /** TTL / hop-limits increment (per-burst step) */ union { uint8_t ttl_inc; uint8_t hop_limits_inc; }; uint8_t pad0[3]; /** Source starting IP address (IPv4 or IPv6 depending on mode) */ union { uint32_t src_ip; uint8_t src_ipv6[PG_IN6ADDRSZ]; }; /** Source IP address minimum */ union { uint32_t src_ip_min; uint8_t src_ipv6_min[PG_IN6ADDRSZ]; }; /** Source IP address maximum */ union { uint32_t src_ip_max; uint8_t src_ipv6_max[PG_IN6ADDRSZ]; }; /** Destination starting IP address (IPv4 or IPv6 depending on mode) */ union { uint32_t dst_ip; uint8_t dst_ipv6[PG_IN6ADDRSZ]; }; /** Destination IP address minimum */ union { uint32_t dst_ip_min; uint8_t dst_ipv6_min[PG_IN6ADDRSZ]; }; /** Destination IP address maximum */ union { uint32_t dst_ip_max; uint8_t dst_ipv6_max[PG_IN6ADDRSZ]; }; uint16_t ip_proto; /**< IP Protocol type TCP or UDP */ uint16_t src_port; /**< Source port starting value */ uint16_t src_port_min; /**< Source port minimum */ uint16_t src_port_max; /**< Source port maximum */ uint16_t dst_port; /**< Destination port starting value */ uint16_t dst_port_min; /**< Destination port minimum */ uint16_t dst_port_max; /**< Destination port maximum */ uint8_t tcp_flags; /**< TCP flags value */ uint32_t tcp_seq; /**< TCP sequence number starting value */ uint32_t tcp_seq_min; /**< TCP sequence number minimum */ uint32_t tcp_seq_max; /**< TCP sequence number maximum */ uint32_t tcp_ack; /**< TCP acknowledgement number starting value */ uint32_t tcp_ack_min; /**< TCP acknowledgement number minimum */ uint32_t tcp_ack_max; /**< TCP acknowledgement number maximum */ uint16_t vlan_id; /**< VLAN id starting value */ uint16_t vlan_id_min; /**< VLAN id minimum */ uint16_t vlan_id_max; /**< VLAN id maximum */ uint8_t cos; /**< CoS / priority value starting */ uint8_t cos_min; /**< CoS / priority value minimum */ uint8_t cos_max; /**< CoS / priority value maximum */ /** ToS / traffic-class starting value */ union { uint8_t tos; uint8_t traffic_class; }; /** ToS / traffic-class minimum */ union { uint8_t tos_min; uint8_t traffic_class_min; }; /** ToS / traffic-class maximum */ union { uint8_t tos_max; uint8_t traffic_class_max; }; uint16_t pkt_size; /**< Packet size starting value (bytes) */ uint16_t pkt_size_min; /**< Packet size minimum (bytes) */ uint16_t pkt_size_max; /**< Packet size maximum (bytes) */ uint64_t dst_mac; /**< Destination starting MAC address */ uint64_t dst_mac_min; /**< Destination minimum MAC address */ uint64_t dst_mac_max; /**< Destination maximum MAC address */ uint64_t src_mac; /**< Source starting MAC address */ uint64_t src_mac_min; /**< Source minimum MAC address */ uint64_t src_mac_max; /**< Source maximum MAC address */ /** TTL / hop-limits starting value */ union { uint8_t ttl; uint8_t hop_limits; }; /** TTL / hop-limits minimum */ union { uint8_t ttl_min; uint8_t hop_limits_min; }; /** TTL / hop-limits maximum */ union { uint8_t ttl_max; uint8_t hop_limits_max; }; uint32_t gtpu_teid; /**< GTP-U TEID starting value */ uint32_t gtpu_teid_inc; /**< GTP-U TEID increment */ uint32_t gtpu_teid_min; /**< GTP-U TEID minimum */ uint32_t gtpu_teid_max; /**< GTP-U TEID maximum */ uint32_t vxlan_gid; /**< VxLAN Group ID starting value */ uint32_t vxlan_gid_inc; /**< VxLAN Group ID increment */ uint32_t vxlan_gid_min; /**< VxLAN Group ID minimum */ uint32_t vxlan_gid_max; /**< VxLAN Group ID maximum */ uint32_t vxlan_vid; /**< VxLAN VLAN ID starting value */ uint32_t vxlan_vid_inc; /**< VxLAN VLAN ID increment */ uint32_t vxlan_vid_min; /**< VxLAN VLAN ID minimum */ uint32_t vxlan_vid_max; /**< VxLAN VLAN ID maximum */ uint32_t vni_flags; /**< VxLAN VNI flags */ } range_info_t; struct port_info_s; /** * Populate a packet sequence template from a range_info_t configuration. * * @param range * Pointer to the range configuration to apply. * @param pkt * Packet sequence entry to update with current range field values. */ void pktgen_range_ctor(range_info_t *range, pkt_seq_t *pkt); /** * Initialise range-mode state for a port. * * Copies the default single-packet template into the range packet slot and * sets up the initial range field values from the port's range_info_t. * * @param info * Per-port state structure to initialise. */ void pktgen_range_setup(struct port_info_s *info); /** * Render the range-mode display page to the terminal. */ void pktgen_page_range(void); #ifdef __cplusplus } #endif #endif /* _PKTGEN_RANGE_H_ */ ================================================ FILE: app/pktgen-seq.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include "pktgen-display.h" #include "pktgen.h" void pktgen_send_seq_pkt(port_info_t *pinfo, uint32_t seq_idx) { (void)pinfo; (void)seq_idx; } /** * * pktgen_page_seq - Display the sequence port data on the screen. * * DESCRIPTION * For a given port display the sequence packet data. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_page_seq(uint32_t pid) { uint32_t i, row, col, sav; port_info_t *pinfo; pkt_seq_t *pkt; char buff[128]; display_topline("", 0, 0, 0); pinfo = l2p_get_port_pinfo(pid); if (pinfo == NULL) return; pktgen_display_set_color("top.ports"); row = PORT_FLAGS_ROW; col = 1; scrn_printf(row, col, "Port: %2d, Sequence Count: %2d of %2d ", pid, pinfo->seqCnt, NUM_SEQ_PKTS); pktgen_display_set_color("stats.stat.label"); scrn_printf(row++, col + 102, "GTP-u"); scrn_printf(row++, col, "%*s%*s%*s%*s%*s%*s%*s%*s%*s%*s%*s", 3, "Seq", 15, "Dst MAC", 15, "Src MAC", 16, "Dst IP", 18, "Src IP", 13, "TTL/Port S/D", 20, "Protocol:VLAN:Flags", 8, "CoS/ToS", 5, "TEID", 17, "Flag/Group/vxlan", 6, "Size"); pktgen_display_set_color("stats.stat.values"); scrn_fgcolor(SCRN_DEFAULT_FG, SCRN_NO_ATTR); sav = row; for (i = 0; i <= NUM_SEQ_PKTS; i++) { if (i >= pinfo->seqCnt) scrn_eol_pos(row++, col); } row = sav; for (i = 0; i < pinfo->seqCnt; i++) { pkt = &pinfo->seq_pkt[i]; col = 1; scrn_printf(row, col, "%2d:", i); col += 3; snprintf(buff, sizeof(buff), "%02x%02x:%02x%02x:%02x%02x", pkt->eth_dst_addr.addr_bytes[0], pkt->eth_dst_addr.addr_bytes[1], pkt->eth_dst_addr.addr_bytes[2], pkt->eth_dst_addr.addr_bytes[3], pkt->eth_dst_addr.addr_bytes[4], pkt->eth_dst_addr.addr_bytes[5]); scrn_printf(row, col, "%*s", 15, buff); col += 15; snprintf(buff, sizeof(buff), "%02x%02x:%02x%02x:%02x%02x", pkt->eth_src_addr.addr_bytes[0], pkt->eth_src_addr.addr_bytes[1], pkt->eth_src_addr.addr_bytes[2], pkt->eth_src_addr.addr_bytes[3], pkt->eth_src_addr.addr_bytes[4], pkt->eth_src_addr.addr_bytes[5]); scrn_printf(row, col, "%*s", 15, buff); col += 15; scrn_printf( row, col, "%*s", 16, inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_dst_addr.addr.ipv4.s_addr), 0xFFFFFFFF)); col += 16; scrn_printf( row, col, "%*s", 18, inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_src_addr.addr.ipv4.s_addr), pkt->ip_mask)); col += 18; snprintf(buff, sizeof(buff), "%d/%d/%d", pkt->ttl, pkt->sport, pkt->dport); scrn_printf(row, col, "%*s", 13, buff); col += 13; snprintf(buff, sizeof(buff), "%s/%s:%04x:%04x", (pkt->ethType == RTE_ETHER_TYPE_IPV4) ? "IPv4" : (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? "IPv6" : "....", (pkt->ipProto == PG_IPPROTO_TCP) ? "TCP" : (pkt->ipProto == PG_IPPROTO_ICMP) ? "ICMP" : "UDP", pkt->vlanid, pkt->tcp_flags); scrn_printf(row, col, "%*s", 20, buff); col += 20; snprintf(buff, sizeof(buff), "%3d/%3d", pkt->cos, pkt->tos); scrn_printf(row, col, "%*s", 8, buff); col += 8; scrn_printf(row, col, "%5d", pkt->gtpu_teid); col += 6; snprintf(buff, sizeof(buff), "%04x/%5d/%5d", pkt->vni_flags, pkt->group_id, pkt->vxlan_id); scrn_printf(row, col, "%*s", 16, buff); col += 16; scrn_printf(row, col, "%6d", pkt->pkt_size + RTE_ETHER_CRC_LEN); row++; } display_dashline(row + 2); pktgen_display_set_color(NULL); } ================================================ FILE: app/pktgen-seq.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_SEQ_H_ #define _PKTGEN_SEQ_H_ /** * @file * * Packet sequence template for Pktgen. * * Defines pkt_seq_t, the per-slot packet template that stores all layer-2 * through layer-7 parameters needed to build a transmit packet. * Each port has an array of NUM_TOTAL_PKTS such templates. */ #include #include #include #include #include "pktgen-constants.h" #ifdef __cplusplus extern "C" { #endif __extension__ typedef void *MARKER[0]; /**< generic marker for a point in a structure */ /** Per-slot packet template containing all fields needed to build a packet. */ typedef struct pkt_seq_s { /* Packet type and information */ struct rte_ether_addr eth_dst_addr; /**< Destination Ethernet address */ struct rte_ether_addr eth_src_addr; /**< Source Ethernet address */ struct cmdline_ipaddr ip_src_addr; /**< Source IPv4 address also used for IPv6 */ struct cmdline_ipaddr ip_dst_addr; /**< Destination IPv4 address */ uint32_t ip_mask; /**< IPv4 Netmask value */ uint16_t sport; /**< Source port value */ uint16_t dport; /**< Destination port value */ uint16_t ethType; /**< IPv4 or IPv6 */ uint16_t ipProto; /**< TCP or UDP or ICMP */ uint16_t vlanid; /**< VLAN ID value if used */ uint8_t cos; /**< 802.1p cos value if used */ union { uint8_t tos; /**< tos value if used */ uint8_t traffic_class; /**< traffic class for IPv6 headers*/ }; uint16_t ether_hdr_size; /**< Size of Ethernet header in packet for VLAN ID */ uint32_t tcp_seq; /**< TCP sequence number */ uint32_t tcp_ack; /**< TCP acknowledge number*/ uint16_t tcp_flags; /**< TCP flags value */ uint32_t mpls_entry; /**< MPLS entry if used */ uint16_t qinq_outerid; /**< Outer VLAN ID if Q-in-Q */ uint16_t qinq_innerid; /**< Inner VLAN ID if Q-in-Q */ uint32_t gre_key; /**< GRE key if used */ uint16_t pkt_size; /**< Size of packet in bytes not counting FCS */ uint8_t seq_enabled; /**< Enable or disable this sequence */ union { uint8_t ttl; /**< TTL value for IPv4 headers */ uint8_t hop_limits; /**< Hop limits for IPv6 headers */ }; uint32_t gtpu_teid; /**< GTP-U TEID, if UDP dport=2152 */ union { uint64_t vxlan; /**< VxLAN 64 bit word */ struct { uint16_t vni_flags; /**< VxLAN Flags */ uint16_t group_id; /**< VxLAN Group Policy ID */ uint32_t vxlan_id; /**< VxLAN VNI */ }; }; uint64_t ol_flags; /**< offload flags */ pkt_hdr_t *hdr; /**< Packet header data */ } pkt_seq_t __rte_cache_aligned; struct port_info_s; /** * Build and transmit the sequence packet at a given slot index. * * @param info Per-port state. * @param seq_idx Sequence slot index (0 .. NUM_SEQ_PKTS-1). */ void pktgen_send_seq_pkt(struct port_info_s *info, uint32_t seq_idx); /** * Render the sequence-mode display page to the terminal. * * @param pid Port ID whose sequence slots are displayed. */ void pktgen_page_seq(uint32_t pid); #ifdef __cplusplus } #endif #endif /* _PKTGEN_SEQ_H_ */ ================================================ FILE: app/pktgen-stats.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include #include #include "pktgen-cmds.h" #include "pktgen-display.h" #include "pktgen.h" #include #include /** * * pktgen_print_static_data - Display the static data on the screen. * * DESCRIPTION * Display a set of port static data on the screen. * * RETURNS: N/A * * SEE ALSO: */ static void pktgen_print_static_data(void) { port_info_t *pinfo; struct rte_eth_dev_info dev = {0}; uint32_t pid, col, row, sp, ip_row; pkt_seq_t *pkt; char buff[INET6_ADDRSTRLEN * 2]; int display_cnt; display_topline("
", pktgen.starting_port, (pktgen.ending_port - 1), pktgen.nb_ports); row = PORT_FLAGS_ROW; pktgen_display_set_color("stats.port.label"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Port:Flags"); row = LINK_STATE_ROW; /* Labels for dynamic fields (update every second) */ pktgen_display_set_color("stats.port.linklbl"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Link State"); pktgen_display_set_color("stats.port.sizes"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Pkts/s Rx:Tx"); pktgen_display_set_color("stats.mac"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "MBits/s Rx:Tx"); pktgen_display_set_color("stats.port.totals"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Max Pkts/s Rx:Tx"); pktgen_display_set_color("stats.port.totlbl"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Total Rx Pkts"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Tx Pkts"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Rx:Tx MBs"); pktgen_display_set_color("stats.port.errlbl"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Errors Rx/Tx/missed"); row = PKT_SIZE_ROW; pktgen_display_set_color("stats.port.sizelbl"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Broadcast"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Multicast"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Rx Sizes 64"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " 65-127"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " 128-255"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " 256-511"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " 512-1023"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " 1024-1522"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Runts/Jumbos"); if (pktgen.flags & TX_DEBUG_FLAG) { pktgen_display_set_color("stats.port.errlbl"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Tx Overrun"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Pkts per Queue"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Cycles per Queue"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Rx Missed"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " mcasts"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " No Mbuf"); } /* Labels for static fields */ pktgen_display_set_color("stats.stat.label"); ip_row = row; scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Rx/Tx queue count"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Tx Count/% Rate"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Pkt Size/Rx:Tx Burst"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Port Src/Dest"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "Type:VLAN ID:Flags"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "IP Destination"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Source"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "MAC Destination"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, " Source"); scrn_printf(row++, 1, "%-*s", COLUMN_WIDTH_0, "NUMA/Vend:ID/PCI"); row++; /* Get the last location to use for the window starting row. */ pktgen.last_row = row; display_dashline(pktgen.last_row); /* Display the colon after the row label. */ pktgen_print_div(PORT_FLAGS_ROW, pktgen.last_row - 1, COLUMN_WIDTH_0 - 1); sp = pktgen.starting_port; display_cnt = 0; for (pid = 0; pid < pktgen.nb_ports_per_page; pid++) { pinfo = l2p_get_port_pinfo(pid + sp); if (pinfo == NULL) break; pktgen_display_set_color("stats.stat.values"); pkt = &pinfo->seq_pkt[SINGLE_PKT]; /* Display Port information Src/Dest IP addr, Netmask, Src/Dst MAC addr */ col = (COLUMN_WIDTH_1 * pid) + COLUMN_WIDTH_0; row = ip_row; pktgen_display_set_color("stats.rate.count"); snprintf(buff, sizeof(buff), "%d/%d", l2p_get_rxcnt(pid), l2p_get_txcnt(pid)); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); pktgen_transmit_count_rate(pid, buff, sizeof(buff)); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); pktgen_display_set_color("stats.stat.values"); snprintf(buff, sizeof(buff), "%d /%3d:%3d", pkt->pkt_size + RTE_ETHER_CRC_LEN, pinfo->rx_burst, pinfo->tx_burst); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); snprintf(buff, sizeof(buff), "%5d/%5d", pkt->sport, pkt->dport); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); snprintf(buff, sizeof(buff), "%s / %s:%04x:%04x", (pkt->ethType == RTE_ETHER_TYPE_IPV4) ? "IPv4" : (pkt->ethType == RTE_ETHER_TYPE_IPV6) ? "IPv6" : (pkt->ethType == RTE_ETHER_TYPE_ARP) ? "ARP" : "Other", (pkt->ipProto == PG_IPPROTO_TCP) ? "TCP" : (pkt->ipProto == PG_IPPROTO_ICMP) ? "ICMP" : (rte_atomic64_read(&pinfo->port_flags) & SEND_VXLAN_PACKETS) ? "VXLAN" : "UDP", pkt->vlanid, pkt->tcp_flags); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); pktgen_display_set_color("stats.ip"); if (pkt->ethType == RTE_ETHER_TYPE_IPV6) { scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, inet_ntop6(buff, sizeof(buff), pkt->ip_dst_addr.addr.ipv6.a, PG_PREFIXMAX | ((COLUMN_WIDTH_1 - 1) << 8))); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, inet_ntop6(buff, sizeof(buff), pkt->ip_src_addr.addr.ipv6.a, pkt->ip_src_addr.prefixlen | ((COLUMN_WIDTH_1 - 1) << 8))); } else { scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_dst_addr.addr.ipv4.s_addr), 0xFFFFFFFF)); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, inet_ntop4(buff, sizeof(buff), htonl(pkt->ip_src_addr.addr.ipv4.s_addr), pkt->ip_mask)); } pktgen_display_set_color("stats.mac"); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, inet_mtoa(buff, sizeof(buff), &pkt->eth_dst_addr)); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, inet_mtoa(buff, sizeof(buff), &pkt->eth_src_addr)); if (rte_eth_dev_info_get(pid, &dev) < 0) rte_exit(EXIT_FAILURE, "Cannot get device info for port %u\n", pid); const struct rte_bus *bus = NULL; if (dev.device) bus = rte_bus_find_by_device(dev.device); if (bus && !strcmp(rte_bus_name(bus), "pci")) { char name[RTE_ETH_NAME_MAX_LEN]; char vend[8], device[8], pci[32]; vend[0] = device[0] = '\0'; sscanf(rte_dev_bus_info(dev.device), "vendor_id=%4s, device_id=%4s", vend, device); rte_eth_dev_get_name_by_port(pid, name); strcpy(pci, rte_dev_name(dev.device)); snprintf(buff, sizeof(buff), "%d/%s:%s/%s", rte_dev_numa_node(dev.device), vend, device, &pci[5]); } else snprintf(buff, sizeof(buff), "-1/0000:00:00.0"); pktgen_display_set_color("stats.stat.values"); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); display_cnt++; } /* Display the string for total pkts/s rate of all ports */ col = (COLUMN_WIDTH_1 * display_cnt) + COLUMN_WIDTH_0; pktgen_display_set_color("stats.total.label"); scrn_printf(LINK_STATE_ROW, col, "%*s", COLUMN_WIDTH_3, "---Total Rate---"); scrn_eol(); pktgen_display_set_color(NULL); pktgen.flags &= ~PRINT_LABELS_FLAG; } /** * * pktgen_get_link_status - Get the port link status. * * DESCRIPTION * Try to get the link status of a port. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_get_link_status(port_info_t *pinfo) { struct rte_eth_link link = (struct rte_eth_link){0}; if (rte_eth_link_get_nowait(pinfo->pid, &link) != 0) return; /* Provide safe defaults only when link is UP but speed unknown */ if (link.link_status == RTE_ETH_LINK_UP && link.link_speed == RTE_ETH_SPEED_NUM_UNKNOWN) { link.link_speed = RTE_ETH_SPEED_NUM_10G; link.link_duplex = RTE_ETH_LINK_FULL_DUPLEX; link.link_autoneg = RTE_ETH_LINK_SPEED_AUTONEG; } /* Change if status toggled or (while up) speed/duplex/autoneg differ */ if (link.link_status != pinfo->link.link_status || (link.link_status == RTE_ETH_LINK_UP && (link.link_speed != pinfo->link.link_speed || link.link_duplex != pinfo->link.link_duplex || link.link_autoneg != pinfo->link.link_autoneg))) { pinfo->link.link_status = link.link_status; pinfo->link.link_speed = link.link_speed; pinfo->link.link_autoneg = link.link_autoneg; pinfo->link.link_duplex = link.link_duplex; if (link.link_status == RTE_ETH_LINK_UP) pktgen_packet_rate(pinfo); else { /* Link down: zero pacing so UI reflects inactive state */ pinfo->tx_cycles = 0; pinfo->tx_pps = 0; } } } /** * * pktgen_page_stats - Display the statistics on the screen for all ports. * * DESCRIPTION * Display the port statistics on the screen for all ports. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_page_stats(void) { port_info_t *pinfo; unsigned int pid, col, row; struct rte_eth_stats *rate, *cumm, *prev; unsigned sp; char buff[32]; int display_cnt; if (pktgen.flags & PRINT_LABELS_FLAG) pktgen_print_static_data(); cumm = &pktgen.cumm_rate_totals; memset(cumm, 0, sizeof(struct rte_eth_stats)); /* Calculate the total values */ RTE_ETH_FOREACH_DEV(pid) { pinfo = l2p_get_port_pinfo(pid); if (pinfo == NULL) break; rate = &pinfo->stats.rate; cumm->ipackets += rate->ipackets; cumm->opackets += rate->opackets; cumm->ibytes += rate->ibytes; cumm->obytes += rate->obytes; cumm->ierrors += rate->ierrors; cumm->oerrors += rate->oerrors; if (cumm->ipackets > pktgen.max_total_ipackets) pktgen.max_total_ipackets = cumm->ipackets; if (cumm->opackets > pktgen.max_total_opackets) pktgen.max_total_opackets = cumm->opackets; cumm->imissed += rate->imissed; cumm->rx_nombuf += rate->rx_nombuf; } sp = pktgen.starting_port; display_cnt = 0; for (pid = 0; pid < pktgen.nb_ports_per_page; pid++) { pinfo = l2p_get_port_pinfo(pid + sp); if (pinfo == NULL) break; col = (COLUMN_WIDTH_1 * pid) + COLUMN_WIDTH_0; /* Display the port number for the column */ row = PORT_FLAGS_ROW; snprintf(buff, sizeof(buff), "%d:%s", pid + sp, pktgen_flags_string(pinfo)); pktgen_display_set_color("stats.port.flags"); scrn_printf(row, col, "%*s", COLUMN_WIDTH_1, buff); /* Grab the link state of the port and display Duplex/Speed and UP/Down */ row = LINK_STATE_ROW; pktgen_get_link_status(pinfo); pktgen_link_state(pid + sp, buff, sizeof(buff)); pktgen_display_set_color("stats.port.status"); scrn_printf(row, col, "%*s", COLUMN_WIDTH_1, buff); rate = &pinfo->stats.rate; prev = &pinfo->stats.prev; /* Rx/Tx pkts/s rate */ row = PKT_RATE_ROW; pktgen_display_set_color("stats.port.sizes"); snprintf(buff, sizeof(buff), "%'" PRIu64 ":%'" PRIu64, rate->ipackets, rate->opackets); scrn_printf(row++, col, "%'*s", COLUMN_WIDTH_1, buff); /* Rx/Tx Mbits per second */ pktgen_display_set_color("stats.port.sizes"); snprintf(buff, sizeof(buff), "%'" PRIu64 ":%'" PRIu64, iBitsTotal(pinfo->stats.rate) / Million, oBitsTotal(pinfo->stats.rate) / Million); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); /* Max Rx/Tx packets */ pktgen_display_set_color("stats.port.sizes"); snprintf(buff, sizeof(buff), "%'" PRIu64 ":%'" PRIu64, pktgen.max_total_ipackets, pktgen.max_total_opackets); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); /* Total Rx/Tx packets and bytes */ pktgen_display_set_color("stats.port.sizes"); scrn_printf(row++, col, "%'*llu", COLUMN_WIDTH_1, pinfo->stats.curr.ipackets); scrn_printf(row++, col, "%'*llu", COLUMN_WIDTH_1, pinfo->stats.curr.opackets); snprintf(buff, sizeof(buff), "%'lu:%'lu", iBitsTotal(pinfo->stats.curr) / Million, oBitsTotal(pinfo->stats.curr) / Million); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); /* Rx/Tx Errors */ pktgen_display_set_color("stats.port.errors"); snprintf(buff, sizeof(buff), "%'" PRIu64 "/%'" PRIu64 "/%'" PRIu64, prev->ierrors, prev->oerrors, prev->imissed); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); /* Packets Sizes */ row = PKT_SIZE_ROW; pktgen_display_set_color("stats.port.sizes"); size_stats_t sizes = pinfo->stats.sizes; scrn_printf(row++, col, "%'*llu", COLUMN_WIDTH_1, sizes.broadcast); scrn_printf(row++, col, "%'*llu", COLUMN_WIDTH_1, sizes.multicast); scrn_printf(row++, col, "%'*llu", COLUMN_WIDTH_1, sizes._64); scrn_printf(row++, col, "%'*llu", COLUMN_WIDTH_1, sizes._65_127); scrn_printf(row++, col, "%'*llu", COLUMN_WIDTH_1, sizes._128_255); scrn_printf(row++, col, "%'*llu", COLUMN_WIDTH_1, sizes._256_511); scrn_printf(row++, col, "%'*llu", COLUMN_WIDTH_1, sizes._512_1023); scrn_printf(row++, col, "%'*llu", COLUMN_WIDTH_1, sizes._1024_1522); snprintf(buff, sizeof(buff), "%'" PRIu64 "/%'" PRIu64, sizes.runt, sizes.jumbo); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); if (pktgen.flags & TX_DEBUG_FLAG) { snprintf(buff, sizeof(buff), "%'" PRIu64, pinfo->tx_pps); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); snprintf(buff, sizeof(buff), "%'" PRIu64, pinfo->tx_cycles); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_1, buff); } pktgen_display_set_color(NULL); display_cnt++; } /* Display the total pkts/s for all ports */ col = (COLUMN_WIDTH_1 * display_cnt) + COLUMN_WIDTH_0; row = PKT_RATE_ROW; pktgen_display_set_color("stats.port.sizes"); snprintf(buff, sizeof(buff), "%'" PRIu64 ":%'" PRIu64, cumm->ipackets, cumm->opackets); scrn_printf(row++, col, "%'*s", COLUMN_WIDTH_3, buff); scrn_eol(); pktgen_display_set_color("stats.port.sizes"); snprintf(buff, sizeof(buff), "%'" PRIu64 ":%'" PRIu64, iBitsTotal(pktgen.cumm_rate_totals) / Million, oBitsTotal(pktgen.cumm_rate_totals) / Million); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_3, buff); scrn_eol(); pktgen_display_set_color("stats.port.sizes"); snprintf(buff, sizeof(buff), "%'" PRIu64 ":%'" PRIu64, pktgen.max_total_ipackets, pktgen.max_total_opackets); scrn_printf(row++, col, "%*s", COLUMN_WIDTH_3, buff); scrn_eol(); pktgen_display_set_color(NULL); } static void process_xstats(port_info_t *pinfo) { xstats_t *xs = &pinfo->stats.xstats; int cnt; if (xs->cnt == 0) { /* Get count */ cnt = rte_eth_xstats_get_names(pinfo->pid, NULL, 0); if (cnt < 0) { printf("Error: Cannot get count of xstats\n"); return; } if (cnt == 0) return; xs->cnt = cnt; xs->names = rte_calloc(NULL, cnt, sizeof(struct rte_eth_xstat_name), 0); xs->xstats = rte_calloc(NULL, cnt, sizeof(struct rte_eth_xstat), 0); xs->prev = rte_calloc(NULL, cnt, sizeof(struct rte_eth_xstat), 0); if (xs->names == NULL || xs->xstats == NULL || xs->prev == NULL) { printf("Cannot allocate memory for xstats\n"); return; } if (rte_eth_xstats_get_names(pinfo->pid, xs->names, xs->cnt) < 0) { printf("Error: Cannot get xstats lookup\n"); return; } } if (rte_eth_xstats_get(pinfo->pid, xs->xstats, xs->cnt) < 0) { printf("Error: Unable to get xstats\n"); return; } for (int i = 0; i < xs->cnt; i++) { struct rte_eth_xstat *cur = &xs->xstats[i]; char *name = xs->names[i].name; uint64_t val = cur->value; if (val == 0) continue; /* Update size stats */ if (strncmp(name, "rx_size_64", strlen("rx_size_64")) == 0) pinfo->stats.sizes._64 = val; else if (strncmp(name, "rx_size_65_127", strlen("rx_size_65_127")) == 0) pinfo->stats.sizes._65_127 = val; else if (strncmp(name, "rx_size_128_255", strlen("rx_size_128_255")) == 0) pinfo->stats.sizes._128_255 = val; else if (strncmp(name, "rx_size_256_511", strlen("rx_size_256_511")) == 0) pinfo->stats.sizes._256_511 = val; else if (strncmp(name, "rx_size_512_1023", strlen("rx_size_512_1023")) == 0) pinfo->stats.sizes._512_1023 = val; else if (strncmp(name, "rx_size_1024_1522", strlen("rx_size_1024_1522")) == 0) pinfo->stats.sizes._1024_1522 = val; else if (strncmp(name, "rx_oversize_errors", strlen("rx_oversize_errors")) == 0) pinfo->stats.sizes.jumbo = val; else if (strncmp(name, "rx_undersized_errors", strlen("rx_undersized_errors")) == 0) pinfo->stats.sizes.runt = val; else if (strncmp(name, "rx_broadcast_packets", strlen("rx_broadcast_packets")) == 0) pinfo->stats.sizes.broadcast = val; else if (strncmp(name, "rx_multicast_packets", strlen("rx_multicast_packets")) == 0) pinfo->stats.sizes.multicast = val; } } /** * * pktgen_process_stats - Process statistics for all ports on timer1 * * DESCRIPTION * When timer1 callback happens then process all of the port statistics. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_process_stats(void) { unsigned int pid; struct rte_eth_stats *curr, *rate, *prev, *base; port_info_t *pinfo; static unsigned int counter = 0; counter++; if (pktgen.flags & BLINK_PORTS_FLAG) { RTE_ETH_FOREACH_DEV(pid) { if ((pktgen.blinklist & (1UL << pid)) == 0) continue; if (counter & 1) rte_eth_led_on(pid); else rte_eth_led_off(pid); } } RTE_ETH_FOREACH_DEV(pid) { pinfo = l2p_get_port_pinfo(pid); if (pinfo == NULL) break; pktgen_get_link_status(pinfo); curr = &pinfo->stats.curr; rate = &pinfo->stats.rate; prev = &pinfo->stats.prev; base = &pinfo->stats.base; memset(curr, 0, sizeof(struct rte_eth_stats)); rte_eth_stats_get(pid, curr); /* Normalize the counters */ curr->ipackets = curr->ipackets - base->ipackets; curr->opackets = curr->opackets - base->opackets; curr->ibytes = curr->ibytes - base->ibytes; curr->obytes = curr->obytes - base->obytes; curr->ierrors = curr->ierrors - base->ierrors; curr->oerrors = curr->oerrors - base->oerrors; curr->imissed = curr->imissed - base->imissed; curr->rx_nombuf = curr->rx_nombuf - base->rx_nombuf; /* Figure out the rate values */ rate->ipackets = (curr->ipackets - prev->ipackets); rate->opackets = (curr->opackets - prev->opackets); rate->ibytes = (curr->ibytes - prev->ibytes); rate->obytes = (curr->obytes - prev->obytes); rate->ierrors = (curr->ierrors - prev->ierrors); rate->oerrors = (curr->oerrors - prev->oerrors); rate->imissed = (curr->imissed - prev->imissed); rate->rx_nombuf = (curr->rx_nombuf - prev->rx_nombuf); /* Save the current values in previous */ memcpy(prev, curr, sizeof(struct rte_eth_stats)); process_xstats(pinfo); /* Snapshot per-queue counters written by worker lcores. * rte_smp_rmb() ensures all worker stores are visible before the copy. * snap_qstats is owned exclusively by this timer thread; the display * reads from snap_qstats so it never races with the worker hot path. */ rte_smp_rmb(); int nq = RTE_MAX(l2p_get_rxcnt(pid), l2p_get_txcnt(pid)); for (int q = 0; q < nq && q < MAX_QUEUES_PER_PORT; q++) pinfo->stats.snap_qstats[q] = pinfo->stats.qstats[q]; } } void pktgen_page_qstats(uint16_t pid) { unsigned int col, row, q, hdr, width; struct rte_eth_stats *s, *r; struct rte_ether_addr ethaddr; char buff[128], mac_buf[32], dev_name[64]; uint64_t ipackets, opackets, errs; port_info_t *pinfo; pinfo = l2p_get_port_pinfo(pid); s = &pinfo->stats.curr; display_topline("", 0, 0, 0); pktgen_display_set_color("stats.port.status"); scrn_puts(" Port %2u", pid); row = 3; col = 1; pktgen_display_set_color("stats.stat.label"); scrn_printf(row + 0, col, "%-*s", COLUMN_WIDTH_0, "PCI Address :"); scrn_printf(row + 1, col, "%-*s", COLUMN_WIDTH_0, "Pkts Rx/Tx :"); scrn_printf(row + 2, col, "%-*s", COLUMN_WIDTH_0, "Rx Errors/Missed:"); scrn_printf(row + 3, col, "%-*s", COLUMN_WIDTH_0, "Rate Rx/Tx :"); scrn_printf(row + 4, col, "%-*s", COLUMN_WIDTH_0, "MAC Address :"); scrn_printf(row + 5, col, "%-*s", COLUMN_WIDTH_0, "Link Status :"); col = COLUMN_WIDTH_0; width = COLUMN_WIDTH_1 + 8; pktgen_display_set_color("stats.stat.values"); rte_eth_dev_get_name_by_port(pid, dev_name); snprintf(buff, sizeof(buff), "%s", dev_name); scrn_printf(row + 0, col, "%*s", width, buff); snprintf(buff, sizeof(buff), "%'lu/%'lu", s->ipackets, s->opackets); scrn_printf(row + 1, col, "%*s", width, buff); snprintf(buff, sizeof(buff), "%'lu/%'lu", s->ierrors, s->imissed); scrn_printf(row + 2, col, "%*s", width, buff); r = &pinfo->stats.rate; snprintf(buff, sizeof(buff), "%'lu/%'lu", r->ipackets, r->opackets); scrn_printf(row + 3, col, "%*s", width, buff); rte_eth_macaddr_get(pid, ðaddr); rte_ether_format_addr(mac_buf, sizeof(mac_buf), ðaddr); snprintf(buff, sizeof(buff), "%s", mac_buf); scrn_printf(row + 4, col, "%*s", width, buff); pktgen_link_state(pid, buff, sizeof(buff)); pktgen_display_set_color("stats.port.status"); scrn_printf(row + 5, col, "%*s", width, buff); row += 6; ipackets = opackets = errs = 0; hdr = 0; int nq = RTE_MAX(l2p_get_rxcnt(pid), l2p_get_txcnt(pid)); for (q = 0; q < (unsigned int)nq; q++) { uint64_t rxpkts, txpkts, errors; qstats_t *qs = &pinfo->stats.snap_qstats[q]; qstats_t *prev_qs = &pinfo->stats.prev_qstats[q]; if (!hdr) { hdr = 1; row++; pktgen_display_set_color("stats.port.status"); scrn_printf(row++, 1, "%-8s: %14s %14s %14s", "Rate/sec", "ipackets", "opackets", "errors"); pktgen_display_set_color("stats.stat.values"); } rxpkts = qs->q_ipackets - prev_qs->q_ipackets; txpkts = qs->q_opackets - prev_qs->q_opackets; errors = qs->q_errors - prev_qs->q_errors; *prev_qs = *qs; scrn_printf(row++, 1, " Q %2d : %'14lu %'14lu %'14lu", q, rxpkts, txpkts, errors); ipackets += rxpkts; opackets += txpkts; errs += errors; } scrn_printf(row++, 1, " %-7s: %'14lu %'14lu %'14lu", "Totals", ipackets, opackets, errs); pktgen_display_set_color(NULL); display_dashline(row + 2); scrn_eol(); } static void _xstats_display(uint16_t port_id) { port_info_t *pinfo; xstats_t *xs; if (!rte_eth_dev_is_valid_port(port_id)) { printf("Error: Invalid port number %i\n", port_id); return; } pinfo = l2p_get_port_pinfo(port_id); if (pinfo == NULL) return; xs = &pinfo->stats.xstats; /* Display xstats */ int idx = 0; for (int idx_xstat = 0; idx_xstat < xs->cnt; idx_xstat++) { uint64_t value; value = xs->xstats[idx_xstat].value - xs->prev[idx_xstat].value; if (xs->xstats[idx_xstat].value) { if (idx == 0) { printf(" "); scrn_eol(); } else if ((idx & 1) == 0) { printf("\n "); scrn_eol(); } idx++; pktgen_display_set_color("stats.port.status"); printf("%-32s", xs->names[idx_xstat].name); pktgen_display_set_color("stats.port.label"); printf("| "); pktgen_display_set_color("stats.port.rate"); printf("%'15" PRIu64, value); pktgen_display_set_color("stats.port.label"); printf(" | "); scrn_eol(); } } rte_memcpy(xs->prev, xs->xstats, sizeof(struct rte_eth_xstat) * xs->cnt); printf("\n"); scrn_eol(); printf("\n"); } void pktgen_page_xstats(uint16_t pid) { display_topline("", 0, 0, 0); pktgen_display_set_color("stats.port.status"); scrn_printf(3, 1, " %-32s| %15s | %-32s| %15s |\n", "XStat Name", "Per/Second", "XStat Name", "Per/Second"); pktgen_display_set_color("top.page"); printf("Port %3d ", pid); pktgen_display_set_color("stats.port.status"); for (int k = 0; k < 100; k++) printf("="); printf("\n"); pktgen_display_set_color("stats.stat.label"); _xstats_display(pid); pktgen_display_set_color(NULL); scrn_eol(); } ================================================ FILE: app/pktgen-stats.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_STATS_H_ #define _PKTGEN_STATS_H_ /** * @file * * Per-port statistics structures and display functions for Pktgen. * * Defines queue-level, size-histogram, extended, and aggregate port * statistics structs, and declares the functions that collect and display them. */ #include #ifdef __cplusplus extern "C" { #endif #define MAX_QUEUES_PER_PORT 32 /**< Maximum number of RX/TX queues per port */ /** Per-queue packet counters. */ typedef struct qstats_s { uint64_t q_ipackets; /**< Number of input packets */ uint64_t q_opackets; /**< Number of output packets */ uint64_t q_errors; /**< Number of error packets */ } qstats_t; /** Packet size histogram counters. */ typedef struct size_stats_s { uint64_t _64; /**< Number of 64 byte packets */ uint64_t _65_127; /**< Number of 65-127 byte packets */ uint64_t _128_255; /**< Number of 128-255 byte packets */ uint64_t _256_511; /**< Number of 256-511 byte packets */ uint64_t _512_1023; /**< Number of 512-1023 byte packets */ uint64_t _1024_1522; /**< Number of 1024-1522 byte packets */ uint64_t broadcast; /**< Number of broadcast packets */ uint64_t multicast; /**< Number of multicast packets */ uint64_t jumbo; /**< Number of Jumbo frames */ uint64_t runt; /**< Number of Runt frames */ uint64_t unknown; /**< Number of unknown sizes */ } size_stats_t; /** Extended NIC statistics snapshot. */ typedef struct xstats_s { struct rte_eth_xstat_name *names; /**< Array of extended stat name strings */ struct rte_eth_xstat *xstats; /**< Current extended stat values */ struct rte_eth_xstat *prev; /**< Previous extended stat values (for rate calc) */ int cnt; /**< Number of extended stats entries */ } xstats_t; /** Aggregate per-port statistics (current, previous, rate, and base). */ typedef struct port_stats_s { struct rte_eth_stats curr; /**< current port statistics */ struct rte_eth_stats prev; /**< previous port statistics */ struct rte_eth_stats rate; /**< current packet rate statistics */ struct rte_eth_stats base; /**< base port statistics for normalization */ xstats_t xstats; /**< Extended statistics */ size_stats_t sizes; /**< Packet size counters */ qstats_t qstats[MAX_QUEUES_PER_PORT]; /**< Hot-path: written only by worker lcores */ qstats_t snap_qstats[MAX_QUEUES_PER_PORT]; /**< Snapshot: written only by timer thread */ qstats_t prev_qstats[MAX_QUEUES_PER_PORT]; /**< Previous snapshot for rate calculation */ } port_stats_t; struct port_info_s; /** Query and update the Ethernet link status for a port. */ void pktgen_get_link_status(struct port_info_s *info); /** Collect and compute per-port packet rates on a timer tick. */ void pktgen_process_stats(void); /** Render the main statistics display page. */ void pktgen_page_stats(void); /** * Render the per-queue statistics page for a port. * * @param pid Port ID to display. */ void pktgen_page_qstats(uint16_t pid); /** * Render the extended NIC statistics page for a port. * * @param pid Port ID to display. */ void pktgen_page_xstats(uint16_t pid); #ifdef __cplusplus } #endif #endif /* _PKTGEN_STATS_H_ */ ================================================ FILE: app/pktgen-sys.c ================================================ /*- * Copyright(c) <2020-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2018 by Keith Wiles @ intel.com */ #include #include "pktgen-display.h" #include "pktgen-cpu.h" #include "pktgen-sys.h" #include "coreinfo.h" /** * * pktgen_page_system - Show the system page for pktgen. * * DESCRIPTION * Display the pktgen system page. (Not used) * * RETURNS: N/A * * SEE ALSO: */ void pktgen_page_system(void) { uint32_t i, row, col, cnt, nb_sockets, nb_cores, nb_threads; static int counter = 0; char buff[2048]; pktgen_display_set_color("top.page"); display_topline("", 0, 0, 0); cnt = coreinfo_lcore_cnt(); if ((cnt == 0) || (pktgen.lscpu == NULL)) pktgen_cpu_init(); nb_sockets = coreinfo_socket_cnt(); nb_cores = coreinfo_core_cnt(); nb_threads = coreinfo_thread_cnt(); if ((counter++ & 3) != 0) return; row = 3; col = 1; pktgen_display_set_color("stats.total.data"); scrn_printf(row++, 1, "Number of sockets %d, cores/socket %d, threads/core %d, total %d", nb_sockets, nb_cores, nb_threads, cnt); pktgen_display_set_color("stats.dyn.label"); sprintf(buff, "Socket : "); for (i = 0; i < nb_sockets; i++) strncatf(buff, "%4d ", i); scrn_printf(row, col + 2, "%s", buff); scrn_printf(0, 0, "Port description"); pktgen_display_set_color("stats.stat.label"); row++; buff[0] = '\0'; for (i = 0; i < nb_cores; i++) { strncatf(buff, " Core %3d : [%2d,%2d] ", i, sct(0, i, 0), sct(0, i, 1)); if (nb_sockets > 1) strncatf(buff, "[%2d,%2d] ", sct(1, i, 0), sct(1, i, 1)); if (nb_sockets > 2) strncatf(buff, "[%2d,%2d] ", sct(2, i, 0), sct(2, i, 1)); if (nb_sockets > 3) strncatf(buff, "[%2d,%2d] ", sct(3, i, 0), sct(3, i, 1)); strncatf(buff, "\n"); } scrn_printf(row, 1, "%s", buff); pktgen_display_set_color("stats.bdf"); col = 13 + (nb_sockets * 10) + 1; for (i = 0; i < pktgen.portdesc_cnt; i++) scrn_printf(row + i, col, "%s", pktgen.portdesc[i]); row += RTE_MAX(nb_cores, pktgen.portdesc_cnt); scrn_pos(row, 1); pktgen_display_set_color("stats.stat.values"); row += pktgen.nb_ports + 4; if (pktgen.flags & PRINT_LABELS_FLAG) { pktgen.last_row = row + pktgen.nb_ports; display_dashline(pktgen.last_row); scrn_setw(pktgen.last_row); scrn_printf(100, 1, ""); /* Put cursor on the last row. */ } pktgen_display_set_color(NULL); pktgen.flags &= ~PRINT_LABELS_FLAG; } ================================================ FILE: app/pktgen-sys.h ================================================ /*- * Copyright(c) <2020-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2018 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_SYS_H_ #define _PKTGEN_SYS_H_ /** * @file * * System information display page for Pktgen. */ #ifdef __cplusplus extern "C" { #endif /** * Render the system information display page to the console. */ void pktgen_page_system(void); #ifdef __cplusplus } #endif #endif /* _PKTGEN_SYS_H_ */ ================================================ FILE: app/pktgen-tcp.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include "pktgen.h" #include "pktgen-tcp.h" /** * * pktgen_tcp_hdr_ctor - TCP header constructor routine. * * DESCRIPTION * Construct a TCP header in the packet buffer provided. * * RETURNS: N/A * * SEE ALSO: */ void * pktgen_tcp_hdr_ctor(pkt_seq_t *pkt, void *hdr, int type, bool cksum_offload, bool cksum_requires_phdr) { uint16_t tlen; if (type == RTE_ETHER_TYPE_IPV4) { struct rte_ipv4_hdr *ipv4 = (struct rte_ipv4_hdr *)hdr; struct rte_tcp_hdr *tcp = (struct rte_tcp_hdr *)&ipv4[1]; /* Create the TCP header */ ipv4->src_addr = htonl(pkt->ip_src_addr.addr.ipv4.s_addr); ipv4->dst_addr = htonl(pkt->ip_dst_addr.addr.ipv4.s_addr); ipv4->version_ihl = (IPv4_VERSION << 4) | (sizeof(struct rte_ipv4_hdr) / 4); tlen = pkt->pkt_size - pkt->ether_hdr_size; ipv4->total_length = htons(tlen); ipv4->next_proto_id = pkt->ipProto; tcp->src_port = htons(pkt->sport); tcp->dst_port = htons(pkt->dport); tcp->sent_seq = htonl(pkt->tcp_seq); tcp->recv_ack = htonl(pkt->tcp_ack); tcp->data_off = ((sizeof(struct rte_tcp_hdr) / sizeof(uint32_t)) << 4); /* Offset in words */ tcp->tcp_flags = pkt->tcp_flags; tcp->rx_win = htons(DEFAULT_WND_SIZE); tcp->tcp_urp = 0; tcp->cksum = 0; if (!cksum_offload) tcp->cksum = rte_ipv4_udptcp_cksum(ipv4, (const void *)tcp); else if (cksum_offload && cksum_requires_phdr) tcp->cksum = rte_ipv4_phdr_cksum(ipv4, 0); } else { struct rte_ipv6_hdr *ipv6 = (struct rte_ipv6_hdr *)hdr; struct rte_tcp_hdr *tcp = (struct rte_tcp_hdr *)&ipv6[1]; /* Create the pseudo header and TCP information */ memset(&ipv6->dst_addr, 0, sizeof(struct rte_ipv6_addr)); memset(&ipv6->src_addr, 0, sizeof(struct rte_ipv6_addr)); rte_memcpy(&ipv6->dst_addr, &pkt->ip_dst_addr.addr.ipv6, sizeof(struct rte_ipv6_addr)); rte_memcpy(&ipv6->src_addr, &pkt->ip_src_addr.addr.ipv6, sizeof(struct rte_ipv6_addr)); tlen = pkt->pkt_size - (pkt->ether_hdr_size + sizeof(struct rte_ipv6_hdr)); ipv6->payload_len = htons(tlen); ipv6->proto = pkt->ipProto; tcp->src_port = htons(pkt->sport); tcp->dst_port = htons(pkt->dport); tcp->sent_seq = htonl(pkt->tcp_seq); tcp->recv_ack = htonl(pkt->tcp_ack); tcp->data_off = ((sizeof(struct rte_tcp_hdr) / sizeof(uint32_t)) << 4); /* Offset in words */ tcp->tcp_flags = pkt->tcp_flags; tcp->rx_win = htons(DEFAULT_WND_SIZE); tcp->tcp_urp = 0; tcp->cksum = 0; if (!cksum_offload) tcp->cksum = rte_ipv6_udptcp_cksum(ipv6, (const void *)tcp); } /* In this case we return the original value to allow IP ctor to work */ return hdr; } ================================================ FILE: app/pktgen-tcp.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_TCP_H_ #define _PKTGEN_TCP_H_ /** * @file * * TCP header construction for Pktgen transmit packets. */ #include #include "pktgen-seq.h" #ifdef __cplusplus extern "C" { #endif /** * Construct a TCP header in the packet buffer. * * @param pkt * Packet sequence entry providing source/destination ports and TCP flags. * @param hdr * Pointer to the start of the TCP header region in the packet buffer. * @param type * EtherType / address family identifier (e.g. RTE_ETHER_TYPE_IPV4). * @param cksum_offload * When true, set the checksum to 0 and rely on hardware offload; * when false, compute the checksum in software. * @param cksum_requires_phdr * When true, include the IPv4/IPv6 pseudo-header in the checksum seed. * @return * Pointer to the byte immediately following the completed TCP header. */ void *pktgen_tcp_hdr_ctor(pkt_seq_t *pkt, void *hdr, int type, bool cksum_offload, bool cksum_requires_phdr); #ifdef __cplusplus } #endif #endif /* _PKTGEN_TCP_H_ */ ================================================ FILE: app/pktgen-txbuff.h ================================================ /*- * Copyright(c) <2023-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) 2010-2017 Intel Corporation * * Taken from: dpdk.org (https://dpdk.org/) and modified */ #ifndef _PKTGEN_TXBUFF_H_ #define _PKTGEN_TXBUFF_H_ /** * @file * * Software TX coalescing buffer for Pktgen. * * Provides a small per-port/queue packet buffer that collects mbufs and * flushes them to the NIC in a single rte_eth_tx_burst() call, reducing * PCIe overhead for bursty traffic patterns. */ #include #ifdef __cplusplus extern "C" { #endif /** Callback invoked when packets remain unsent after a flush attempt. */ typedef void (*tx_buffer_error_fn)(struct rte_mbuf **unsent, uint16_t count, void *userdata); /** * Packet coalescing buffer used by tx_buffer() and tx_buffer_flush(). */ struct eth_tx_buffer { uint16_t size; /**< Maximum number of packets the buffer can hold */ uint16_t length; /**< Current number of packets held in the buffer */ uint16_t pid; /**< Port ID to transmit on */ uint16_t qid; /**< Queue ID to transmit on */ struct rte_mbuf *pkts[]; /**< Pending packets to be sent on flush or when full */ }; /** * Calculate the size of the Tx buffer. * * @param sz * Number of stored packets. */ #define TX_BUFFER_SIZE(sz) (sizeof(struct eth_tx_buffer) + (sz) * sizeof(struct rte_mbuf *)) /** * Send any packets queued up for transmission on a port and HW queue * * This causes an explicit flush of packets previously buffered via the * tx_buffer() function. It returns the number of packets successfully * sent to the NIC. * * @param buffer * Buffer of packets to be transmit. * @return * The number of packets successfully sent to the Ethernet device. */ static inline uint16_t tx_buffer_flush(struct eth_tx_buffer *buffer) { uint16_t sent, tot_sent = 0; uint16_t to_send = buffer->length; struct rte_mbuf **pkts = buffer->pkts; if (to_send == 0) return 0; buffer->length = 0; do { sent = rte_eth_tx_burst(buffer->pid, buffer->qid, pkts, to_send); to_send -= sent; pkts += sent; tot_sent += sent; } while (to_send > 0); return tot_sent; } /** * Buffer a single packet for future transmission on a port and queue * * This function takes a single mbuf/packet and buffers it for later * transmission on the particular port and queue specified. Once the buffer is * full of packets, an attempt will be made to transmit all the buffered * packets. In case of error, where not all packets can be transmitted, a * callback is called with the unsent packets as a parameter. If no callback * is explicitly set up, the unsent packets are just freed back to the owning * mempool. The function returns the number of packets actually sent i.e. * 0 if no buffer flush occurred, otherwise the number of packets successfully * flushed * * @param buffer * Buffer used to collect packets to be sent. * @param tx_pkt * Pointer to the packet mbuf to be sent. * @return * 0 = packet has been buffered for later transmission * N > 0 = packet has been buffered, and the buffer was subsequently flushed, * causing N packets to be sent, and the error callback to be called for * the rest. */ static __rte_always_inline uint16_t tx_buffer(struct eth_tx_buffer *buffer, struct rte_mbuf *tx_pkt) { buffer->pkts[buffer->length++] = tx_pkt; if (buffer->length < buffer->size) return 0; return tx_buffer_flush(buffer); } /** * Buffer a vector of packets for future transmission on a port and queue * * This function takes a vector of mbufs/packets and buffers it for later * transmission on the particular port and queue specified. Once the buffer is * full of packets, an attempt will be made to transmit all the buffered * packets. In case of error, where not all packets can be transmitted, a * callback is called with the unsent packets as a parameter. If no callback * is explicitly set up, the unsent packets are just freed back to the owning * mempool. The function returns the number of packets actually sent i.e. * 0 if no buffer flush occurred, otherwise the number of packets successfully * flushed * * @param buffer * Buffer used to collect packets to be sent. * @param tx_pkt * Pointer to the vector of mbufs to be sent. * @param nb_pkts * Number of packets to be sent in the vector. * @return * 0 = packet has been buffered for later transmission * N > 0 = packet has been buffered, and the buffer was subsequently flushed, * causing N packets to be sent, and the error callback to be called for * the rest. */ static __rte_always_inline uint16_t tx_buffer_bulk(struct eth_tx_buffer *buffer, struct rte_mbuf **tx_pkt, uint16_t nb_pkts) { for (uint16_t i = 0; i < nb_pkts; i++) { tx_buffer(buffer, *tx_pkt); tx_pkt++; } return nb_pkts; } /** * Initialize default values for buffered transmitting * * @param buffer * Tx buffer to be initialized. * @param size * Buffer size * @param pid * Port ID * @param qid * Queue ID * @return * 0 if no error */ static __inline__ int tx_buffer_init(struct eth_tx_buffer *buffer, uint16_t size, uint16_t pid, uint16_t qid) { int ret = 0; if (buffer == NULL) { printf("Cannot initialize NULL buffer\n"); return -EINVAL; } buffer->size = size; buffer->pid = pid; buffer->qid = qid; return ret; } #ifdef __cplusplus } #endif #endif /* _PKTGEN_TXBUFF_H_ */ ================================================ FILE: app/pktgen-udp.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include "lua_config.h" #include "pktgen.h" #include "pktgen-udp.h" /** * * pktgen_udp_hdr_ctor - UDP header constructor routine. * * DESCRIPTION * Construct the UDP header in a packer buffer. * * RETURNS: next header location * * SEE ALSO: */ void * pktgen_udp_hdr_ctor(pkt_seq_t *pkt, void *hdr, int type, bool cksum_offload, bool cksum_requires_phdr) { uint16_t tlen; if (type == RTE_ETHER_TYPE_IPV4) { struct rte_ipv4_hdr *ipv4 = hdr; struct rte_udp_hdr *udp = (struct rte_udp_hdr *)&ipv4[1]; /* Create the UDP header */ ipv4->src_addr = htonl(pkt->ip_src_addr.addr.ipv4.s_addr); ipv4->dst_addr = htonl(pkt->ip_dst_addr.addr.ipv4.s_addr); ipv4->version_ihl = (IPv4_VERSION << 4) | (sizeof(struct rte_ipv4_hdr) / 4); tlen = pkt->pkt_size - pkt->ether_hdr_size; ipv4->total_length = htons(tlen); ipv4->next_proto_id = pkt->ipProto; tlen = pkt->pkt_size - (pkt->ether_hdr_size + sizeof(struct rte_ipv4_hdr)); udp->dgram_len = htons(tlen); udp->src_port = htons(pkt->sport); udp->dst_port = htons(pkt->dport); if (pkt->dport == VXLAN_PORT_ID) { struct vxlan *vxlan = (struct vxlan *)&udp[1]; vxlan->vni_flags = htons(pkt->vni_flags); vxlan->group_id = htons(pkt->group_id); vxlan->vxlan_id = htonl(pkt->vxlan_id) << 8; } udp->dgram_cksum = 0; if (!cksum_offload) { udp->dgram_cksum = rte_ipv4_udptcp_cksum(ipv4, (const void *)udp); if (udp->dgram_cksum == 0) udp->dgram_cksum = 0xFFFF; } else if (cksum_offload && cksum_requires_phdr) udp->dgram_cksum = rte_ipv4_phdr_cksum(ipv4, 0); } else { struct rte_ipv6_hdr *ipv6 = hdr; struct rte_udp_hdr *udp = (struct rte_udp_hdr *)&ipv6[1]; /* Create the pseudo header and TCP information */ memset(&ipv6->dst_addr, 0, sizeof(struct rte_ipv6_addr)); memset(&ipv6->src_addr, 0, sizeof(struct rte_ipv6_addr)); rte_memcpy(&ipv6->dst_addr, &pkt->ip_dst_addr.addr.ipv6, sizeof(struct rte_ipv6_addr)); rte_memcpy(&ipv6->src_addr, &pkt->ip_src_addr.addr.ipv6, sizeof(struct rte_ipv6_addr)); tlen = pkt->pkt_size - (pkt->ether_hdr_size + sizeof(struct rte_ipv6_hdr)); ipv6->payload_len = htons(tlen); ipv6->proto = pkt->ipProto; udp->dgram_len = htons(tlen); udp->src_port = htons(pkt->sport); udp->dst_port = htons(pkt->dport); udp->dgram_cksum = 0; if (!cksum_offload) { udp->dgram_cksum = rte_ipv6_udptcp_cksum(ipv6, (const void *)udp); if (udp->dgram_cksum == 0) udp->dgram_cksum = 0xFFFF; } } /* Return the original pointer for IP ctor */ return hdr; } ================================================ FILE: app/pktgen-udp.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_UDP_H_ #define _PKTGEN_UDP_H_ /** * @file * * UDP header construction for Pktgen transmit packets. */ #include #include "pktgen-seq.h" #ifdef __cplusplus extern "C" { #endif #define VXLAN_PORT_ID 4789 /**< Well-known UDP port number for VxLAN encapsulation */ /** * Construct a UDP header in the packet buffer. * * @param pkt * Packet sequence entry providing source/destination ports and payload length. * @param hdr * Pointer to the start of the UDP header region in the packet buffer. * @param type * EtherType / address family identifier (e.g. RTE_ETHER_TYPE_IPV4). * @param cksum_offload * When true, set the checksum to 0 and rely on hardware offload; * when false, compute the checksum in software. * @param cksum_requires_phdr * When true, include the IPv4/IPv6 pseudo-header in the checksum seed. * @return * Pointer to the byte immediately following the completed UDP header. */ void *pktgen_udp_hdr_ctor(pkt_seq_t *pkt, void *hdr, int type, bool cksum_offload, bool cksum_requires_phdr); #ifdef __cplusplus } #endif #endif /* _PKTGEN_UDP_H_ */ ================================================ FILE: app/pktgen-version.h ================================================ /*- * Copyright(c) <2020-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2020 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_VERSION_H_ #define _PKTGEN_VERSION_H_ #ifdef __cplusplus extern "C" { #endif #define PKTGEN_VER_PREFIX "Pktgen " #define PKTGEN_VER_CREATED_BY "Keith Wiles" #define PKTGEN_VERSION PKTGEN_VER_PREFIX __PROJECT_VERSION #ifdef __cplusplus } #endif #endif /* PKTGEN_VERSION_H_ */ ================================================ FILE: app/pktgen-vlan.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include "lua_config.h" #include "pktgen.h" #include "pktgen-arp.h" #include "pktgen-ipv4.h" #include "pktgen-ipv6.h" #include "pktgen-vlan.h" /** * * pktgen_process_vlan - Process a VLAN packet * * DESCRIPTION * Process a input VLAN packet. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_process_vlan(struct rte_mbuf *m, uint32_t pid, uint32_t qid) { pktType_e pType; struct rte_ether_hdr *eth; struct rte_vlan_hdr *rte_vlan_hdr; eth = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); /* Now dealing with the inner header */ rte_vlan_hdr = (struct rte_vlan_hdr *)(eth + 1); pType = ntohs(rte_vlan_hdr->eth_proto); /* No support for nested tunnel */ switch ((int)pType) { case RTE_ETHER_TYPE_ARP: pktgen_process_arp(m, pid, qid, 1); break; case RTE_ETHER_TYPE_IPV4: pktgen_process_ping4(m, pid, qid, 1); break; case RTE_ETHER_TYPE_IPV6: pktgen_process_ping6(m, pid, qid, 1); break; case UNKNOWN_PACKET: /* FALL THRU */ default: break; } } ================================================ FILE: app/pktgen-vlan.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_VLAN_H_ #define _PKTGEN_VLAN_H_ /** * @file * * VLAN packet processing routines for Pktgen. */ #include #include #ifdef __cplusplus extern "C" { #endif /** * Process a received VLAN-tagged packet and update port statistics. * * @param m * Pointer to the received mbuf. * @param pid * Port index on which the packet was received. * @param qid * Queue index on which the packet was received. */ void pktgen_process_vlan(struct rte_mbuf *m, uint32_t pid, uint32_t qid); #ifdef __cplusplus } #endif #endif /* _PKTGEN_VLAN_H_ */ ================================================ FILE: app/pktgen.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include #include #include #include #include #include #include #include #include "pktgen.h" #include "pktgen-tcp.h" #include "pktgen-ipv4.h" #include "pktgen-ipv6.h" #include "pktgen-udp.h" #include "pktgen-gre.h" #include "pktgen-arp.h" #include "pktgen-vlan.h" #include "pktgen-cpu.h" #include "pktgen-display.h" #include "pktgen-random.h" #include "pktgen-log.h" #include "pktgen-gtpu.h" #include "pktgen-sys.h" #include #include /* Allocated the pktgen structure for global use */ pktgen_t pktgen; static inline double next_poisson_time(double rateParameter) { return -logf(1.0f - ((double)random()) / (double)(RAND_MAX)) / rateParameter; } /** * * wire_size - Calculate the wire size of the data in bits to be sent. * * DESCRIPTION * Calculate the number of bytes/bits in a burst of traffic. * * RETURNS: Number of bytes in a burst of packets. * * SEE ALSO: */ static uint64_t pktgen_wire_size(port_info_t *pinfo) { uint64_t i, size = 0; if (pktgen_tst_port_flags(pinfo, SEND_PCAP_PKTS)) { pcap_info_t *pcap = l2p_get_pcap(pinfo->pid); size = WIRE_SIZE(pcap->avg_pkt_size, uint64_t); } else { if (unlikely(pinfo->seqCnt > 0)) { for (i = 0; i < pinfo->seqCnt; i++) size += WIRE_SIZE(pinfo->seq_pkt[i].pkt_size, uint64_t); size = size / pinfo->seqCnt; /* Calculate the average sized packet */ } else size = WIRE_SIZE(pinfo->seq_pkt[SINGLE_PKT].pkt_size, uint64_t); } return (size * 8); } /** * * pktgen_packet_rate - Calculate the transmit rate. * * DESCRIPTION * Calculate the number of cycles to wait between sending bursts of traffic. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_packet_rate(port_info_t *port) { uint64_t link_speed, pps, cpb, txcnt; if (!port) return; // link speed in Megabits per second and tx_rate in percentage if (port->link.link_speed == 0 || port->tx_rate == 0) { port->tx_cycles = 0; port->tx_pps = 0; return; } // total link speed in bits per second link_speed = (uint64_t)port->link.link_speed * Million; txcnt = l2p_get_txcnt(port->pid); // packets per second per thread based on requested (tx_rate/txcnt) pps = (((link_speed / pktgen_wire_size(port)) * (port->tx_rate / txcnt)) / 100); pps = ((pps > 0) ? pps : 1); // Do all multiplications first to reduce rounding errors. // Add pps/2 to do rounding instead of truncation. cpb = (pps / 2 + (uint64_t)txcnt * port->tx_burst * rte_get_timer_hz()) / pps; port->tx_cycles = cpb; port->tx_pps = pps; } /** * * pktgen_fill_pattern - Create the fill pattern in a packet buffer. * * DESCRIPTION * Create a fill pattern based on the arguments for the packet data. * * RETURNS: N/A * * SEE ALSO: */ static inline void pktgen_fill_pattern(uint8_t *p, uint16_t len, uint32_t type, char *user) { uint32_t i; switch (type) { case USER_FILL_PATTERN: memset(p, 0, len); for (i = 0; i < len; i++) p[i] = user[i & (USER_PATTERN_SIZE - 1)]; break; case NO_FILL_PATTERN: break; case ZERO_FILL_PATTERN: memset(p, 0, len); break; default: case ABC_FILL_PATTERN: /* Byte wide ASCII pattern */ for (i = 0; i < len; i++) p[i] = "abcdefghijklmnopqrstuvwxyz012345"[i & 0x1f]; break; } } /** * * pktgen_find_matching_ipsrc - Find the matching IP source address * * DESCRIPTION * locate and return the pkt_seq_t pointer to the match IP address. * * RETURNS: index of sequence packets or -1 if no match found. * * SEE ALSO: */ int pktgen_find_matching_ipsrc(port_info_t *pinfo, uint32_t addr) { int i, ret = -1; uint32_t mask; addr = ntohl(addr); /* Search the sequence packets for a match */ for (i = 0; i < pinfo->seqCnt; i++) if (addr == pinfo->seq_pkt[i].ip_src_addr.addr.ipv4.s_addr) { ret = i; break; } mask = size_to_mask(pinfo->seq_pkt[SINGLE_PKT].ip_src_addr.prefixlen); /* Now try to match the single packet address */ if (ret == -1 || (addr & mask) == (pinfo->seq_pkt[SINGLE_PKT].ip_dst_addr.addr.ipv4.s_addr * mask)) ret = SINGLE_PKT; return ret; } /** * * pktgen_find_matching_ipdst - Find the matching IP destination address * * DESCRIPTION * locate and return the pkt_seq_t pointer to the match IP address. * * RETURNS: index of sequence packets or -1 if no match found. * * SEE ALSO: */ int pktgen_find_matching_ipdst(port_info_t *pinfo, uint32_t addr) { int i, ret = -1; addr = ntohl(addr); /* Search the sequence packets for a match */ for (i = 0; i < pinfo->seqCnt; i++) if (addr == pinfo->seq_pkt[i].ip_dst_addr.addr.ipv4.s_addr) { ret = i; break; } /* Now try to match the single packet address */ if (ret == -1 && addr == pinfo->seq_pkt[SINGLE_PKT].ip_dst_addr.addr.ipv4.s_addr) ret = SINGLE_PKT; /* Now try to match the range packet address */ if (ret == -1 && addr == pinfo->seq_pkt[RANGE_PKT].ip_dst_addr.addr.ipv4.s_addr) ret = RANGE_PKT; return ret; } static inline tstamp_t * pktgen_tstamp_pointer(port_info_t *pinfo __rte_unused, char *p) { int offset = 0; offset += sizeof(struct rte_ether_hdr); offset += sizeof(struct rte_ipv4_hdr); offset += sizeof(struct rte_udp_hdr); offset = (offset + sizeof(uint64_t)) & ~(sizeof(uint64_t) - 1); return (tstamp_t *)(p + offset); } static inline void pktgen_tstamp_inject(port_info_t *pinfo, uint16_t qid) { pkt_seq_t *pkt = &pinfo->seq_pkt[LATENCY_PKT]; rte_mbuf_t *mbuf; l2p_port_t *port; uint16_t sent; uint64_t curr_ts = pktgen_get_time(); latency_t *lat = &pinfo->latency; if (curr_ts >= lat->latency_timo_cycles) { lat->latency_timo_cycles = curr_ts + lat->latency_rate_cycles; port = l2p_get_port(pinfo->pid); if (rte_mempool_get(port->sp_mp[qid], (void **)&mbuf) == 0) { uint16_t pktsize = pkt->pkt_size; uint16_t to_send; mbuf->pkt_len = pktsize; mbuf->data_len = pktsize; /* IPv4 Header constructor */ pktgen_packet_ctor(pinfo, LATENCY_PKT, -2); rte_memcpy(rte_pktmbuf_mtod(mbuf, uint8_t *), (uint8_t *)pkt->hdr, pktsize); to_send = 1; do { sent = rte_eth_tx_burst(pinfo->pid, qid, &mbuf, to_send); to_send -= sent; } while (to_send > 0); lat->num_latency_tx_pkts++; } else printf("*** No more latency buffers\n"); } } void tx_send_packets(port_info_t *pinfo, uint16_t qid, struct rte_mbuf **pkts, uint16_t nb_pkts) { if (nb_pkts) { uint16_t sent, to_send = nb_pkts; qstats_t *qs = &pinfo->stats.qstats[qid]; qs->q_opackets += nb_pkts; if (pktgen_tst_port_flags(pinfo, SEND_RANDOM_PKTS)) pktgen_rnd_bits_apply(pinfo, pkts, to_send, NULL); do { sent = rte_eth_tx_burst(pinfo->pid, qid, pkts, to_send); to_send -= sent; pkts += sent; } while (to_send > 0); if (qid == 0 && pktgen_tst_port_flags(pinfo, SEND_LATENCY_PKTS)) pktgen_tstamp_inject(pinfo, qid); } } /* Inserts a new value into the ring of latencies */ static inline void latency_ring_insert(latency_ring_t *ring, uint64_t value) { ring->data[ring->head] = value; ring->head = (ring->head + 1) % RING_SIZE; if (ring->count < RING_SIZE) ring->count++; } static inline void pktgen_tstamp_check(port_info_t *pinfo, struct rte_mbuf **pkts, uint16_t nb_pkts) { int lid = rte_lcore_id(); int qid = l2p_get_rxqid(lid); uint64_t cycles, jitter; latency_t *lat = &pinfo->latency; for (int i = 0; i < nb_pkts; i++) { tstamp_t *tstamp = pktgen_tstamp_pointer(pinfo, rte_pktmbuf_mtod(pkts[i], char *)); if (tstamp->magic != TSTAMP_MAGIC) continue; cycles = (pktgen_get_time() - tstamp->timestamp); tstamp->magic = 0UL; /* clear timestamp magic cookie */ if (tstamp->index != lat->expect_index) { lat->expect_index = tstamp->index + 1; lat->num_skipped++; continue; /* Skip this latency packet */ } lat->expect_index++; lat->num_latency_pkts++; lat->running_cycles += cycles; latency_ring_insert(&lat->tail_latencies, cycles); if (lat->min_cycles == 0 || cycles < lat->min_cycles) lat->min_cycles = cycles; if (lat->max_cycles == 0 || cycles > lat->max_cycles) lat->max_cycles = cycles; jitter = (cycles > lat->prev_cycles) ? cycles - lat->prev_cycles : lat->prev_cycles - cycles; if (jitter > lat->jitter_threshold_cycles) lat->jitter_count++; lat->prev_cycles = cycles; if (pktgen_tst_port_flags(pinfo, SAMPLING_LATENCIES)) { /* Record latency if it's time for sampling (seperately per queue) */ latsamp_stats_t *stats = &pinfo->latsamp_stats[qid]; uint64_t now = pktgen_get_time(); uint64_t hz = rte_get_tsc_hz(); if (stats->next == 0 || now >= stats->next) { if (stats->idx < stats->num_samples) { stats->data[stats->idx] = (cycles * Billion) / hz; stats->idx++; } /* Calculate next sampling point */ if (pinfo->latsamp_type == LATSAMPLER_POISSON) { double next_possion_time_ns = next_poisson_time(pinfo->latsamp_rate); stats->next = (now + next_possion_time_ns) * (double)hz; } else // LATSAMPLER_SIMPLE or LATSAMPLER_UNSPEC stats->next = (now + hz) / pinfo->latsamp_rate; } } } } /** * * pktgen_tx_flush - Flush Tx buffers from ring. * * DESCRIPTION * Flush TX buffers from ring. * * RETURNS: N/A * * SEE ALSO: */ static inline void pktgen_tx_flush(port_info_t *pinfo, uint16_t qid) { rte_eth_tx_done_cleanup(pinfo->pid, qid, 0); } /** * * pktgen_exit_cleanup - Clean up the data and other items * * DESCRIPTION * Clean up the data. * * RETURNS: N/A * * SEE ALSO: */ static inline void pktgen_exit_cleanup(uint8_t lid __rte_unused) { } /** * * pktgen_packet_ctor - Construct a complete packet with all headers and data. * * DESCRIPTION * Construct a packet type based on the arguments passed with all headers. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_packet_ctor(port_info_t *pinfo, int32_t seq_idx, int32_t type) { pkt_seq_t *pkt = &pinfo->seq_pkt[seq_idx]; uint16_t sport_entropy = 0; struct rte_ether_hdr *eth = (struct rte_ether_hdr *)&pkt->hdr->eth; char *l3_hdr = (char *)ð[1]; /* Pointer to l3 hdr location for GRE header */ uint16_t pktsz = (pktgen.flags & JUMBO_PKTS_FLAG) ? RTE_ETHER_MAX_JUMBO_FRAME_LEN : RTE_ETHER_MAX_LEN; struct rte_eth_dev_info dev_info = {0}; int ret; ret = rte_eth_dev_info_get(pinfo->pid, &dev_info); if (ret < 0) printf("Error during getting device (port %u) info: %s\n", pinfo->pid, strerror(-ret)); /* Fill in the pattern for data space. */ pktgen_fill_pattern((uint8_t *)pkt->hdr, pktsz, pinfo->fill_pattern_type, pinfo->user_pattern); if (seq_idx == LATENCY_PKT) { latency_t *lat = &pinfo->latency; tstamp_t *tstamp; tstamp = pktgen_tstamp_pointer(pinfo, (char *)pkt->hdr); tstamp->magic = TSTAMP_MAGIC; tstamp->timestamp = pktgen_get_time(); tstamp->index = lat->next_index++; if (lat->latency_entropy) sport_entropy = (uint16_t)(pkt->sport + (tstamp->index % lat->latency_entropy)); } /* * Randomizes the source IP address and port. Only randomizes if in the "single packet" setting * and not processing input packets. * For details, see https://github.com/pktgen/Pktgen-DPDK/pull/342 */ if (pktgen_tst_port_flags(pinfo, SEND_SINGLE_PKTS) && !pktgen_tst_port_flags(pinfo, PROCESS_INPUT_PKTS)) { if (pktgen_tst_port_flags(pinfo, RANDOMIZE_SRC_IP)) pkt->ip_src_addr.addr.ipv4.s_addr = pktgen_default_rnd_func(); if (pktgen_tst_port_flags(pinfo, RANDOMIZE_SRC_PT)) pkt->sport = (pktgen_default_rnd_func() % 65535) + 1; /* Avoid port 0, the only invalid port */ } /* Add GRE header and adjust rte_ether_hdr pointer if requested */ if (pktgen_tst_port_flags(pinfo, SEND_GRE_IPv4_HEADER)) l3_hdr = pktgen_gre_hdr_ctor(pinfo, pkt, (greIp_t *)l3_hdr); else if (pktgen_tst_port_flags(pinfo, SEND_GRE_ETHER_HEADER)) l3_hdr = pktgen_gre_ether_hdr_ctor(pinfo, pkt, (greEther_t *)l3_hdr); else l3_hdr = pktgen_ether_hdr_ctor(pinfo, pkt); uint64_t offload_capa = dev_info.tx_offload_capa; if (likely(pkt->ethType == RTE_ETHER_TYPE_IPV4)) { bool ipv4_cksum_offload = offload_capa & RTE_ETH_TX_OFFLOAD_IPV4_CKSUM; if (likely(pkt->ipProto == PG_IPPROTO_TCP)) { bool tcp_cksum_offload = offload_capa & RTE_ETH_TX_OFFLOAD_TCP_CKSUM; if (pkt->dport != PG_IPPROTO_L4_GTPU_PORT) { /* Construct the TCP header */ pktgen_tcp_hdr_ctor(pkt, l3_hdr, RTE_ETHER_TYPE_IPV4, tcp_cksum_offload, pinfo->cksum_requires_phdr); /* IPv4 Header constructor */ pktgen_ipv4_ctor(pkt, l3_hdr, ipv4_cksum_offload); } else { /* Construct the GTP-U header */ pktgen_gtpu_hdr_ctor(pkt, l3_hdr, pkt->ipProto, GTPu_VERSION | GTPu_PT_FLAG, 0, 0, 0); /* Construct the TCP header */ pktgen_tcp_hdr_ctor(pkt, l3_hdr, RTE_ETHER_TYPE_IPV4, tcp_cksum_offload, pinfo->cksum_requires_phdr); if (sport_entropy != 0) { struct rte_ipv4_hdr *ipv4 = (struct rte_ipv4_hdr *)l3_hdr; struct rte_tcp_hdr *tcp = (struct rte_tcp_hdr *)&ipv4[1]; tcp->src_port = htons(sport_entropy & 0xFFFF); } /* IPv4 Header constructor */ pktgen_ipv4_ctor(pkt, l3_hdr, ipv4_cksum_offload); } } else if (pkt->ipProto == PG_IPPROTO_UDP) { bool udp_cksum_offload = offload_capa & RTE_ETH_TX_OFFLOAD_UDP_CKSUM; if (pktgen_tst_port_flags(pinfo, SEND_VXLAN_PACKETS)) { /* Construct the UDP header */ pkt->dport = VXLAN_PORT_ID; pktgen_udp_hdr_ctor(pkt, l3_hdr, RTE_ETHER_TYPE_IPV4, udp_cksum_offload, pinfo->cksum_requires_phdr); /* IPv4 Header constructor */ pktgen_ipv4_ctor(pkt, l3_hdr, ipv4_cksum_offload); } else if (pkt->dport != PG_IPPROTO_L4_GTPU_PORT) { /* Construct the UDP header */ pktgen_udp_hdr_ctor(pkt, l3_hdr, RTE_ETHER_TYPE_IPV4, udp_cksum_offload, pinfo->cksum_requires_phdr); /* IPv4 Header constructor */ pktgen_ipv4_ctor(pkt, l3_hdr, ipv4_cksum_offload); } else { /* Construct the GTP-U header */ pktgen_gtpu_hdr_ctor(pkt, l3_hdr, pkt->ipProto, GTPu_VERSION | GTPu_PT_FLAG, 0, 0, 0); /* Construct the UDP header */ pktgen_udp_hdr_ctor(pkt, l3_hdr, RTE_ETHER_TYPE_IPV4, udp_cksum_offload, pinfo->cksum_requires_phdr); if (sport_entropy != 0) { struct rte_ipv4_hdr *ipv4 = (struct rte_ipv4_hdr *)l3_hdr; struct rte_udp_hdr *udp = (struct rte_udp_hdr *)&ipv4[1]; udp->src_port = htons(sport_entropy & 0xFFFF); } /* IPv4 Header constructor */ pktgen_ipv4_ctor(pkt, l3_hdr, ipv4_cksum_offload); } } else if (pkt->ipProto == PG_IPPROTO_ICMP) { struct rte_ipv4_hdr *ipv4; struct rte_udp_hdr *udp; struct rte_icmp_hdr *icmp; uint16_t tlen; /* Start from Ethernet header */ ipv4 = (struct rte_ipv4_hdr *)l3_hdr; udp = (struct rte_udp_hdr *)&ipv4[1]; /* Create the ICMP header */ ipv4->src_addr = htonl(pkt->ip_src_addr.addr.ipv4.s_addr); ipv4->dst_addr = htonl(pkt->ip_dst_addr.addr.ipv4.s_addr); tlen = pkt->pkt_size - (pkt->ether_hdr_size + sizeof(struct rte_ipv4_hdr)); ipv4->total_length = htons(tlen); ipv4->next_proto_id = pkt->ipProto; icmp = (struct rte_icmp_hdr *)&udp[1]; icmp->icmp_code = 0; if ((type == -1) || (type == ICMP4_TIMESTAMP)) { union icmp_data *data = (union icmp_data *)&udp[1]; icmp->icmp_type = ICMP4_TIMESTAMP; data->timestamp.ident = 0x1234; data->timestamp.seq = 0x5678; data->timestamp.originate = 0x80004321; data->timestamp.receive = 0; data->timestamp.transmit = 0; } else if (type == ICMP4_ECHO) { union icmp_data *data = (union icmp_data *)&udp[1]; icmp->icmp_type = ICMP4_ECHO; data->echo.ident = 0x1234; data->echo.seq = 0x5678; data->echo.data = 0; } icmp->icmp_cksum = 0; /* ICMP4_TIMESTAMP_SIZE */ tlen = pkt->pkt_size - (pkt->ether_hdr_size + sizeof(struct rte_ipv4_hdr)); icmp->icmp_cksum = rte_raw_cksum(icmp, tlen); if (icmp->icmp_cksum == 0) icmp->icmp_cksum = 0xFFFF; /* IPv4 Header constructor */ pktgen_ipv4_ctor(pkt, l3_hdr, ipv4_cksum_offload); } } else if (pkt->ethType == RTE_ETHER_TYPE_IPV6) { if (pkt->ipProto == PG_IPPROTO_TCP) { bool tcp_cksum_offload = offload_capa & RTE_ETH_TX_OFFLOAD_TCP_CKSUM; /* Construct the TCP header */ pktgen_tcp_hdr_ctor(pkt, l3_hdr, RTE_ETHER_TYPE_IPV6, tcp_cksum_offload, pinfo->cksum_requires_phdr); if (sport_entropy != 0) { struct rte_ipv6_hdr *ipv6 = (struct rte_ipv6_hdr *)l3_hdr; struct rte_tcp_hdr *tcp = (struct rte_tcp_hdr *)&ipv6[1]; tcp->src_port = htons(sport_entropy & 0xFFFF); } /* IPv6 Header constructor */ pktgen_ipv6_ctor(pkt, l3_hdr); } else if (pkt->ipProto == PG_IPPROTO_UDP) { bool udp_cksum_offload = offload_capa & RTE_ETH_TX_OFFLOAD_UDP_CKSUM; /* Construct the UDP header */ pktgen_udp_hdr_ctor(pkt, l3_hdr, RTE_ETHER_TYPE_IPV6, udp_cksum_offload, pinfo->cksum_requires_phdr); if (sport_entropy != 0) { struct rte_ipv6_hdr *ipv6 = (struct rte_ipv6_hdr *)l3_hdr; struct rte_udp_hdr *udp = (struct rte_udp_hdr *)&ipv6[1]; udp->src_port = htons(sport_entropy & 0xFFFF); } /* IPv6 Header constructor */ pktgen_ipv6_ctor(pkt, l3_hdr); } } else if (pkt->ethType == RTE_ETHER_TYPE_ARP) { /* Start from Ethernet header */ struct rte_arp_hdr *arp = (struct rte_arp_hdr *)l3_hdr; arp->arp_hardware = htons(1); arp->arp_protocol = htons(RTE_ETHER_TYPE_IPV4); arp->arp_hlen = RTE_ETHER_ADDR_LEN; arp->arp_plen = 4; /* make request/reply operation selectable by user */ arp->arp_opcode = htons(2); rte_ether_addr_copy(&pkt->eth_src_addr, &arp->arp_data.arp_sha); *((uint32_t *)&arp->arp_data.arp_sha) = htonl(pkt->ip_src_addr.addr.ipv4.s_addr); rte_ether_addr_copy(&pkt->eth_dst_addr, &arp->arp_data.arp_tha); *((uint32_t *)((void *)&arp->arp_data + offsetof(struct rte_arp_ipv4, arp_tip))) = htonl(pkt->ip_dst_addr.addr.ipv4.s_addr); } else pktgen_log_error("Unknown EtherType 0x%04x", pkt->ethType); } /** * * pktgen_packet_type - Examine a packet and return the type of packet * * DESCRIPTION * Examine a packet and return the type of packet. * the packet. * * RETURNS: N/A * * SEE ALSO: */ static inline pktType_e pktgen_packet_type(struct rte_mbuf *m) { pktType_e ret; struct rte_ether_hdr *eth; eth = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); ret = ntohs(eth->ether_type); return ret; } /** * * pktgen_packet_classify - Examine a packet and classify it for statistics * * DESCRIPTION * Examine a packet and determine its type along with counting statistics around * the packet. * * RETURNS: N/A * * SEE ALSO: */ static inline void pktgen_packet_classify(struct rte_mbuf *m, int pid, int qid) { port_info_t *pinfo = l2p_get_port_pinfo(pid); if (unlikely(pktgen_tst_port_flags(pinfo, PROCESS_INPUT_PKTS))) { pktType_e pType = pktgen_packet_type(m); switch ((int)pType) { case RTE_ETHER_TYPE_ARP: pktgen_process_arp(m, pid, qid, 0); break; case RTE_ETHER_TYPE_IPV4: pktgen_process_ping4(m, pid, qid, 0); break; case RTE_ETHER_TYPE_IPV6: pktgen_process_ping6(m, pid, qid, 0); break; case RTE_ETHER_TYPE_VLAN: pktgen_process_vlan(m, pid, qid); break; case UNKNOWN_PACKET: /* FALL THRU */ default: break; } } } /** * * pktgen_packet_classify_buld - Classify a set of packets in one call. * * DESCRIPTION * Classify a list of packets and to improve classify performance. * * RETURNS: N/A * * SEE ALSO: */ #define PREFETCH_OFFSET 3 static inline void pktgen_packet_classify_bulk(struct rte_mbuf **pkts, int nb_rx, int pid, int qid) { int j, i; /* Prefetch first packets */ for (j = 0; j < PREFETCH_OFFSET && j < nb_rx; j++) rte_prefetch0(rte_pktmbuf_mtod(pkts[j], void *)); /* Prefetch and handle already prefetched packets */ for (i = 0; i < (nb_rx - PREFETCH_OFFSET); i++) { rte_prefetch0(rte_pktmbuf_mtod(pkts[j], void *)); j++; pktgen_packet_classify(pkts[i], pid, qid); } /* Handle remaining prefetched packets */ for (; i < nb_rx; i++) pktgen_packet_classify(pkts[i], pid, qid); } /** * * pktgen_send_special - Send a special packet to the given port. * * DESCRIPTION * Create a special packet in the buffer provided. * * RETURNS: N/A * * SEE ALSO: */ static void pktgen_send_special(port_info_t *pinfo) { if (!pktgen_tst_port_flags(pinfo, SEND_ARP_PING_REQUESTS)) return; /* Send packets attached to the sequence packets. */ for (uint32_t s = 0; s < pinfo->seqCnt; s++) { if (unlikely(pktgen_tst_port_flags(pinfo, SEND_GRATUITOUS_ARP))) pktgen_send_arp(pinfo->pid, GRATUITOUS_ARP, s); else if (unlikely(pktgen_tst_port_flags(pinfo, SEND_ARP_REQUEST))) pktgen_send_arp(pinfo->pid, 0, s); if (unlikely(pktgen_tst_port_flags(pinfo, SEND_PING4_REQUEST))) pktgen_send_ping4(pinfo->pid, s); #ifdef INCLUDE_PING6 if (unlikely(pktgen_tst_port_flags(pinfo, SEND_PING6_REQUEST))) pktgen_send_ping6(pinfo->pid, s); #endif } /* Send the requests from the Single packet setup. */ if (unlikely(pktgen_tst_port_flags(pinfo, SEND_GRATUITOUS_ARP))) pktgen_send_arp(pinfo->pid, GRATUITOUS_ARP, SINGLE_PKT); else if (unlikely(pktgen_tst_port_flags(pinfo, SEND_ARP_REQUEST))) pktgen_send_arp(pinfo->pid, 0, SINGLE_PKT); if (unlikely(pktgen_tst_port_flags(pinfo, SEND_PING4_REQUEST))) pktgen_send_ping4(pinfo->pid, SINGLE_PKT); #ifdef INCLUDE_PING6 if (unlikely(pktgen_tst_port_flags(pinfo, SEND_PING6_REQUEST))) pktgen_send_ping6(pinfo->pid, SINGLE_PKT); #endif pktgen_clr_port_flags(pinfo, SEND_ARP_PING_REQUESTS); } struct pkt_setup_s { int32_t seq_idx; port_info_t *pinfo; }; static inline void mempool_setup_cb(struct rte_mempool *mp __rte_unused, void *opaque, void *obj, unsigned obj_idx __rte_unused) { struct rte_mbuf *m = (struct rte_mbuf *)obj; struct pkt_setup_s *s = (struct pkt_setup_s *)opaque; struct rte_eth_dev_info dev_info = {0}; port_info_t *pinfo = s->pinfo; int32_t idx, seq_idx = s->seq_idx; pkt_seq_t *pkt; int ret; ret = rte_eth_dev_info_get(pinfo->pid, &dev_info); if (ret != 0) printf("Error during getting device (port %u) info: %s\n", pinfo->pid, strerror(-ret)); idx = seq_idx; if (pktgen_tst_port_flags(pinfo, SEND_SEQ_PKTS)) { idx = pinfo->seqIdx; /* move to the next packet in the sequence. */ if (unlikely(++pinfo->seqIdx >= pinfo->seqCnt)) pinfo->seqIdx = 0; } pkt = &pinfo->seq_pkt[idx]; if (idx == RANGE_PKT) pktgen_range_ctor(&pinfo->range, pkt); pktgen_packet_ctor(pinfo, idx, -1); rte_memcpy(rte_pktmbuf_mtod(m, uint8_t *), (uint8_t *)pkt->hdr, pkt->pkt_size); m->pkt_len = pkt->pkt_size; m->data_len = pkt->pkt_size; m->l2_len = pkt->ether_hdr_size; m->l3_len = sizeof(struct rte_ipv4_hdr); switch (pkt->ethType) { case RTE_ETHER_TYPE_IPV4: if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_IPV4_CKSUM) pkt->ol_flags = RTE_MBUF_F_TX_IP_CKSUM | RTE_MBUF_F_TX_IPV4; break; case RTE_ETHER_TYPE_IPV6: pkt->ol_flags = RTE_MBUF_F_TX_IP_CKSUM | RTE_MBUF_F_TX_IPV6; break; case RTE_ETHER_TYPE_VLAN: if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_VLAN_INSERT) { /* TODO */ } break; default: break; } switch (pkt->ipProto) { case PG_IPPROTO_UDP: if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_UDP_CKSUM) pkt->ol_flags |= RTE_MBUF_F_TX_UDP_CKSUM; break; case PG_IPPROTO_TCP: if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_TCP_CKSUM) pkt->ol_flags |= RTE_MBUF_F_TX_TCP_CKSUM; break; default: break; } m->ol_flags = pkt->ol_flags; } /** * * pktgen_setup_packets - Setup the default packets to be sent. * * DESCRIPTION * Construct the default set of packets for a given port. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_setup_packets(uint16_t pid) { l2p_port_t *port = l2p_get_port(pid); port_info_t *pinfo = l2p_get_port_pinfo(pid); if (port == NULL) rte_exit(EXIT_FAILURE, "Invalid l2p port for %d\n", pid); if (pktgen_tst_port_flags(pinfo, SETUP_TRANSMIT_PKTS)) { pktgen_clr_port_flags(pinfo, SETUP_TRANSMIT_PKTS); if (!pktgen_tst_port_flags(pinfo, SEND_PCAP_PKTS)) { struct pkt_setup_s s; int32_t idx = SINGLE_PKT; if (pktgen_tst_port_flags(pinfo, SEND_RANGE_PKTS)) { idx = RANGE_PKT; } else if (pktgen_tst_port_flags(pinfo, SEND_SEQ_PKTS)) idx = FIRST_SEQ_PKT; else if (pktgen_tst_port_flags(pinfo, (SEND_SINGLE_PKTS | SEND_RANDOM_PKTS))) idx = SINGLE_PKT; s.pinfo = pinfo; s.seq_idx = idx; for (uint16_t q = 0; q < l2p_get_txcnt(pid); q++) { struct rte_mempool *tx_mp = l2p_get_tx_mp(pid, q); if (unlikely(tx_mp == NULL)) rte_exit(EXIT_FAILURE, "Invalid TX mempool for port %d qid %u\n", pid, q); rte_mempool_obj_iter(tx_mp, mempool_setup_cb, &s); } } } } /** * * pktgen_send_pkts - Send a set of packet buffers to a given port. * * DESCRIPTION * Transmit a set of packets mbufs to a given port. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_send_pkts(port_info_t *pinfo, uint16_t qid, struct rte_mempool *mp) { uint64_t txCnt; struct rte_mbuf **pkts = pinfo->per_queue[qid].tx_pkts; if (!pktgen_tst_port_flags(pinfo, SEND_FOREVER)) { txCnt = pkt_atomic64_tx_count(&pinfo->current_tx_count, pinfo->tx_burst); if (txCnt == 0) { pktgen_clr_port_flags(pinfo, SENDING_PACKETS); return; } if (txCnt > pinfo->tx_burst) txCnt = pinfo->tx_burst; } else txCnt = pinfo->tx_burst; if (rte_mempool_get_bulk(mp, (void **)pkts, txCnt) == 0) tx_send_packets(pinfo, qid, pkts, txCnt); } /** * * pktgen_main_transmit - Determine the next packet format to transmit. * * DESCRIPTION * Determine the next packet format to transmit for a given port. * * RETURNS: N/A * * SEE ALSO: */ static inline void pktgen_main_transmit(port_info_t *pinfo, uint16_t qid) { uint16_t pid = pinfo->pid; /* Transmit ARP/Ping packets if needed */ pktgen_send_special(pinfo); /* When not transmitting on this port then continue. */ if (pktgen_tst_port_flags(pinfo, SENDING_PACKETS)) { struct rte_mempool *mp = l2p_get_tx_mp(pid, qid); if (pktgen_tst_port_flags(pinfo, SEND_PCAP_PKTS)) mp = l2p_get_pcap_mp(pid); pktgen_send_pkts(pinfo, qid, mp); } } /** * * pktgen_main_receive - Main receive routine for packets of a port. * * DESCRIPTION * Handle the main receive set of packets on a given port plus handle all of the * input processing if required. * * RETURNS: N/A * * SEE ALSO: */ static inline void pktgen_main_receive(port_info_t *pinfo, uint16_t qid) { struct rte_mbuf **pkts = pinfo->per_queue[qid].rx_pkts; qstats_t *qs = &pinfo->stats.qstats[qid]; uint16_t nb_rx, nb_pkts = pinfo->rx_burst, pid; if (unlikely(pktgen_tst_port_flags(pinfo, STOP_RECEIVING_PACKETS))) return; pid = pinfo->pid; /* Read packets from RX queues and free the mbufs */ if (likely((nb_rx = rte_eth_rx_burst(pid, qid, pkts, nb_pkts)) > 0)) { qs->q_ipackets += nb_rx; if (pktgen_tst_port_flags(pinfo, SEND_LATENCY_PKTS)) pktgen_tstamp_check(pinfo, pkts, nb_rx); /* classify the packets and update counters */ pktgen_packet_classify_bulk(pkts, nb_rx, pid, qid); if (unlikely(pinfo->dump_count > 0)) pktgen_packet_dump_bulk(pkts, nb_rx, pid); if (unlikely(pktgen_tst_port_flags(pinfo, CAPTURE_PKTS))) { capture_t *capture = &pktgen.capture[pg_socket_id()]; if (unlikely(capture->port == pid)) pktgen_packet_capture_bulk(pkts, nb_rx, capture); } rte_pktmbuf_free_bulk(pkts, nb_rx); } } /** * * pktgen_main_rxtx_loop - Single thread loop for tx/rx packets * * DESCRIPTION * Handle sending and receiving packets from a given set of ports. This is the * main loop or thread started on a single core. * * RETURNS: N/A * * SEE ALSO: */ static void pktgen_main_rxtx_loop(void) { port_info_t *pinfo; uint64_t curr_tsc, tx_next_cycle, tx_bond_cycle; uint16_t pid, rx_qid, tx_qid, lid = rte_lcore_id(); if (lid == rte_get_main_lcore()) { printf("Using %d initial lcore for Rx/Tx\n", lid); rte_exit(0, "using initial lcore for port"); } pinfo = l2p_get_pinfo_by_lcore(lid); curr_tsc = pktgen_get_time(); tx_next_cycle = curr_tsc; tx_bond_cycle = curr_tsc + (pktgen_get_timer_hz() / 10); pid = pinfo->pid; rx_qid = l2p_get_rxqid(lid); tx_qid = l2p_get_txqid(lid); printf("RX/TX lid %3d, pid %2d, qids %2d/%2d RX-MP %-16s @ %p TX-MP %-16s @ %p\n", lid, pinfo->pid, rx_qid, tx_qid, l2p_get_rx_mp(pinfo->pid, rx_qid)->name, l2p_get_rx_mp(pinfo->pid, rx_qid), l2p_get_tx_mp(pinfo->pid, tx_qid)->name, l2p_get_tx_mp(pinfo->pid, tx_qid)); while (pktgen.force_quit == 0) { /* Process RX */ pktgen_main_receive(pinfo, rx_qid); curr_tsc = pktgen_get_time(); /* Determine when is the next time to send packets */ const int64_t max_tx_lag = DEFAULT_MAX_TX_LAG; // Allow some lag, ideally make this configurable. int64_t dt = curr_tsc - tx_next_cycle; if (dt >= 0) { tx_next_cycle = curr_tsc + pinfo->tx_cycles - (dt <= max_tx_lag ? dt : 0); // Process TX pktgen_main_transmit(pinfo, tx_qid); } if (curr_tsc >= tx_bond_cycle) { tx_bond_cycle = curr_tsc + (pktgen_get_timer_hz() / 10); if (pktgen_tst_port_flags(pinfo, BONDING_TX_PACKETS)) rte_eth_tx_burst(pid, tx_qid, NULL, 0); } } pktgen_log_debug("Exit %d", lid); pktgen_exit_cleanup(lid); } /** * * pktgen_main_tx_loop - Main transmit loop for a core, no receive packet handling * * DESCRIPTION * When Tx and Rx are split across two cores this routing handles the tx packets. * * RETURNS: N/A * * SEE ALSO: */ static void pktgen_main_tx_loop(void) { uint16_t tx_qid, lid = rte_lcore_id(); port_info_t *pinfo = l2p_get_pinfo_by_lcore(lid); uint64_t curr_tsc, tx_next_cycle, tx_bond_cycle; if (lid == rte_get_main_lcore()) { printf("Using %d initial lcore for Rx/Tx\n", lid); rte_exit(0, "Invalid initial lcore assigned to a port"); } curr_tsc = pktgen_get_time(); tx_next_cycle = curr_tsc + pinfo->tx_cycles; tx_bond_cycle = curr_tsc + pktgen_get_timer_hz() / 10; tx_qid = l2p_get_txqid(lid); printf("TX lid %3d, pid %2d, qid %2d, TX-MP %-16s @ %p\n", lid, pinfo->pid, tx_qid, l2p_get_tx_mp(pinfo->pid, tx_qid)->name, l2p_get_tx_mp(pinfo->pid, tx_qid)); while (unlikely(pktgen.force_quit == 0)) { curr_tsc = pktgen_get_time(); /* Determine when is the next time to send packets */ const int64_t max_tx_lag = DEFAULT_MAX_TX_LAG; // Allow some lag, ideally make this configurable. int64_t dt = curr_tsc - tx_next_cycle; if (dt >= 0) { tx_next_cycle = curr_tsc + pinfo->tx_cycles - (dt <= max_tx_lag ? dt : 0); // Process TX pktgen_main_transmit(pinfo, tx_qid); } if (unlikely(curr_tsc >= tx_bond_cycle)) { tx_bond_cycle = curr_tsc + pktgen_get_timer_hz() / 10; if (pktgen_tst_port_flags(pinfo, BONDING_TX_PACKETS)) rte_eth_tx_burst(pinfo->pid, tx_qid, NULL, 0); } } pktgen_log_debug("Exit %d", lid); pktgen_exit_cleanup(lid); } /** * * pktgen_main_rx_loop - Handle only the rx packets for a set of ports. * * DESCRIPTION * When Tx and Rx processing is split between two ports this routine handles * only the receive packets. * * RETURNS: N/A * * SEE ALSO: */ static void pktgen_main_rx_loop(void) { port_info_t *pinfo; uint16_t lid = rte_lcore_id(), rx_qid = l2p_get_rxqid(lid); if (lid == rte_get_main_lcore()) { printf("Using %d initial lcore for Rx/Tx\n", lid); rte_exit(0, "using initial lcore for ports"); } pinfo = l2p_get_pinfo_by_lcore(lid); rx_qid = l2p_get_rxqid(lid); printf("RX lid %3d, pid %2d, qid %2d, RX-MP %-16s @ %p\n", lid, pinfo->pid, rx_qid, l2p_get_rx_mp(pinfo->pid, rx_qid)->name, l2p_get_rx_mp(pinfo->pid, rx_qid)); while (pktgen.force_quit == 0) pktgen_main_receive(pinfo, rx_qid); pktgen_log_debug("Exit %d", lid); pktgen_exit_cleanup(lid); } /** * * pktgen_launch_one_lcore - Launch a single logical core thread. * * DESCRIPTION * Help launching a single thread on one logical core. * * RETURNS: N/A * * SEE ALSO: */ int pktgen_launch_one_lcore(void *arg __rte_unused) { uint16_t pid, lid = rte_lcore_id(); if ((pid = l2p_get_pid_by_lcore(lid)) >= RTE_MAX_ETHPORTS) { pktgen_log_info("*** Logical core %3d has no work, skipping launch", lid); return 0; } switch (l2p_get_type(lid)) { case LCORE_MODE_RX: pktgen_main_rx_loop(); break; case LCORE_MODE_TX: pktgen_main_tx_loop(); break; case LCORE_MODE_BOTH: pktgen_main_rxtx_loop(); break; default: rte_exit(EXIT_FAILURE, "Invalid logical core mode %d\n", l2p_get_type(lid)); } return 0; } static void _page_display(void) { static unsigned int counter = 0; pktgen_display_set_color("top.spinner"); scrn_printf(1, 1, "%c", "-\\|/"[(counter++ & 3)]); pktgen_display_set_color(NULL); if ((pktgen.flags & PAGE_MASK_BITS) == 0) pktgen.flags |= MAIN_PAGE_FLAG; if (pktgen.flags & MAIN_PAGE_FLAG) pktgen_page_stats(); else if (pktgen.flags & SYSTEM_PAGE_FLAG) pktgen_page_system(); else if (pktgen.flags & RANGE_PAGE_FLAG) pktgen_page_range(); else if (pktgen.flags & CPU_PAGE_FLAG) pktgen_page_cpu(); else if (pktgen.flags & SEQUENCE_PAGE_FLAG) pktgen_page_seq(pktgen.curr_port); else if (pktgen.flags & RND_BITFIELD_PAGE_FLAG) { port_info_t *pinfo = l2p_get_port_pinfo(pktgen.curr_port); pktgen_page_random_bitfields(pktgen.flags & PRINT_LABELS_FLAG, pktgen.curr_port, pinfo->rnd_bitfields); } else if (pktgen.flags & LOG_PAGE_FLAG) pktgen_page_log(pktgen.flags & PRINT_LABELS_FLAG); else if (pktgen.flags & LATENCY_PAGE_FLAG) pktgen_page_latency(); else if (pktgen.flags & QSTATS_PAGE_FLAG) pktgen_page_qstats(pktgen.curr_port); else if (pktgen.flags & XSTATS_PAGE_FLAG) pktgen_page_xstats(pktgen.curr_port); else pktgen_page_stats(); } /** * * pktgen_page_display - Display the correct page based on timer callback. * * DESCRIPTION * When timer is active update or display the correct page of data. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_page_display(void) { static unsigned int update_display = 1; /* Leave if the screen is paused */ if (scrn_is_paused()) return; scrn_save(); if (pktgen.flags & UPDATE_DISPLAY_FLAG) { pktgen.flags &= ~UPDATE_DISPLAY_FLAG; update_display = 1; } update_display--; if (update_display == 0) { update_display = UPDATE_DISPLAY_TICK_INTERVAL; _page_display(); if (pktgen.flags & PRINT_LABELS_FLAG) pktgen.flags &= ~PRINT_LABELS_FLAG; } scrn_restore(); pktgen_print_packet_dump(); } static void * _timer_thread(void *arg) { uint64_t process, page, prev; this_scrn = arg; pktgen.stats_timeout = pktgen.hz; pktgen.page_timeout = UPDATE_DISPLAY_TICK_RATE; page = prev = pktgen_get_time(); process = page + pktgen.stats_timeout; page += pktgen.page_timeout; pktgen.timer_running = 1; while (pktgen.timer_running) { uint64_t curr; curr = pktgen_get_time(); if (curr >= process) { process = curr + pktgen.stats_timeout; pktgen_process_stats(); prev = curr; } if (curr >= page) { page = curr + pktgen.page_timeout; pktgen_page_display(); } rte_pause(); } return NULL; } /** * * pktgen_timer_setup - Set up the timer callback routines. * * DESCRIPTION * Setup the two timers to be used for display and calculating statistics. * * RETURNS: N/A * * SEE ALSO: */ void pktgen_timer_setup(void) { rte_cpuset_t cpuset_data; rte_cpuset_t *cpuset = &cpuset_data; pthread_t tid; CPU_ZERO(cpuset); pthread_create(&tid, NULL, _timer_thread, this_scrn); CPU_SET(rte_get_main_lcore(), cpuset); pthread_setaffinity_np(tid, sizeof(cpuset), cpuset); } ================================================ FILE: app/pktgen.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PKTGEN_H_ #define _PKTGEN_H_ /** * @file * * Core pktgen header: global state, port-flag manipulation, and master control macros. * * Includes all DPDK and pktgen sub-headers, declares the global pktgen_t struct, * defines the per-port flag bits, packet-slot indices, display enumerations, and * provides inline helpers for atomic port-flag operations and time acquisition. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef LUA_ENABLED #include "lua_config.h" #include "lauxlib.h" #endif #include "pktgen-port-cfg.h" #include "pktgen-capture.h" #include "pktgen-log.h" #include "pktgen-latency.h" #include "pktgen-random.h" #include "pktgen-seq.h" #include "pktgen-version.h" #include #ifdef LUA_ENABLED #include #endif #ifdef __cplusplus extern "C" { #endif #define MAX_MATRIX_ENTRIES 128 /**< Maximum number of lcore/port mapping entries */ #define MAX_STRING 256 /**< Maximum length of a mapping string */ #define Million (uint64_t)(1000000UL) /**< One million (10^6) */ #define Billion (uint64_t)(1000000000UL) /**< One billion (10^9) */ /** Compute total input bits including Ethernet physical overhead. */ #define iBitsTotal(_x) (uint64_t)(((_x.ipackets * PKT_OVERHEAD_SIZE) + _x.ibytes) * 8) /** Compute total output bits including Ethernet physical overhead. */ #define oBitsTotal(_x) (uint64_t)(((_x.opackets * PKT_OVERHEAD_SIZE) + _x.obytes) * 8) /** Execute @p _exp in a do-while(0) to allow use in if-else without braces. */ #define _do(_exp) \ do { \ _exp; \ } while ((0)) #ifndef RTE_ETH_FOREACH_DEV #define RTE_ETH_FOREACH_DEV(p) for (_p = 0; _p < pktgen.nb_ports; _p++) #endif /** * Iterate over all active ports and execute @p _action for each. * * Declares local variables @c pid and @c pinfo; @p _action may reference * both. Ports whose seq_pkt array is NULL are skipped. */ #define forall_ports(_action) \ do { \ uint16_t pid; \ \ RTE_ETH_FOREACH_DEV(pid) \ { \ port_info_t *pinfo; \ \ pinfo = l2p_get_port_pinfo(pid); \ if (pinfo->seq_pkt == NULL) \ continue; \ _action; \ } \ } while ((0)) /** * Iterate over ports selected by a portlist bitmask and execute @p _action. * * Declares local variables @c pid and @c pinfo; @p _action may reference * both. Ports not in @p _portlist or with a NULL seq_pkt are skipped. * * @param _portlist Portlist bitmask (uint64_t array). * @param _action Statement to execute for each selected port. */ #define foreach_port(_portlist, _action) \ do { \ uint64_t *_pl = (uint64_t *)&_portlist; \ uint16_t pid, idx, bit; \ \ RTE_ETH_FOREACH_DEV(pid) \ { \ port_info_t *pinfo; \ \ idx = (pid / (sizeof(uint64_t) * 8)); \ bit = (pid - (idx * (sizeof(uint64_t) * 8))); \ if ((_pl[idx] & (1LL << bit)) == 0) \ continue; \ pinfo = l2p_get_port_pinfo(pid); \ if (pinfo->seq_pkt == NULL) \ continue; \ _action; \ } \ } while ((0)) /** Packet processing outcome code returned by the RX handler. */ typedef enum { PACKET_CONSUMED = 0, /**< Packet was handled internally by pktgen */ UNKNOWN_PACKET = 0xEEEE, /**< Packet type was not recognised */ DROP_PACKET = 0xFFFE, /**< Packet should be dropped */ FREE_PACKET = 0xFFFF /**< Packet buffer should be freed */ } pktType_e; enum { MAX_SCRN_ROWS = 44, /**< Maximum number of terminal rows used */ MAX_SCRN_COLS = 132, /**< Maximum number of terminal columns used */ COLUMN_WIDTH_0 = 22, /**< Width of display column 0 */ COLUMN_WIDTH_1 = 24, /**< Width of display column 1 */ COLUMN_WIDTH_3 = 24, /**< Width of display column 3 */ DEFAULT_MAX_TX_LAG = 20000, /**< Max TX lag cycles; ideally make configurable */ /* Row locations for start of data */ PAGE_TITLE_ROWS = 1, /**< Number of rows for the page title */ PORT_FLAGS_ROWS = 1, /**< Number of rows for port flags */ LINK_STATE_ROWS = 1, /**< Number of rows for link state */ PKT_RATE_ROWS = 7, /**< Number of rows for packet rate stats */ PKT_SIZE_ROWS = 10, /**< Number of rows for packet size stats */ PKT_TOTALS_ROWS = 7, /**< Number of rows for packet total counters */ IP_ADDR_ROWS = 12, /**< Number of rows for IP address display */ PAGE_TITLE_ROW = 1, /**< Row for page title */ PORT_FLAGS_ROW = (PAGE_TITLE_ROW + PAGE_TITLE_ROWS), /**< Row for port flags */ LINK_STATE_ROW = (PORT_FLAGS_ROW + PORT_FLAGS_ROWS), /**< Row for link state */ PKT_RATE_ROW = (LINK_STATE_ROW + LINK_STATE_ROWS), /**< Row for packet rates */ PKT_SIZE_ROW = (PKT_RATE_ROW + PKT_RATE_ROWS), /**< Row for packet sizes */ PKT_TOTALS_ROW = (PKT_SIZE_ROW + PKT_SIZE_ROWS), /**< Row for packet totals */ IP_ADDR_ROW = (PKT_TOTALS_ROW + PKT_TOTALS_ROWS), /**< Row for IP addresses */ DEFAULT_NETMASK = 0xFFFFFF00, /**< Default network mask (/24) */ DEFAULT_IP_ADDR = (192 << 24) | (168 << 16), /**< Default source IP (192.168.0.0) */ DEFAULT_TX_COUNT = 0, /**< Default TX count (0 = forever) */ DEFAULT_TX_RATE = 100, /**< Default TX rate (100%) */ DEFAULT_PRIME_COUNT = 1, /**< Default prime burst count */ DEFAULT_SRC_PORT = 1234, /**< Default source L4 port */ DEFAULT_DST_PORT = 5678, /**< Default destination L4 port */ DEFAULT_TTL = 64, /**< Default IP TTL */ DEFAULT_TCP_SEQ_NUMBER = 0x12378, /**< Default TCP initial sequence number */ MAX_TCP_SEQ_NUMBER = UINT32_MAX / 8, /**< Maximum TCP sequence number */ DEFAULT_TCP_ACK_NUMBER = 0x12390, /**< Default TCP initial ack number */ MAX_TCP_ACK_NUMBER = UINT32_MAX / 8, /**< Maximum TCP acknowledgement number */ DEFAULT_TCP_FLAGS = ACK_FLAG, /**< Default TCP flags (ACK) */ DEFAULT_WND_SIZE = 8192, /**< Default TCP window size */ MIN_VLAN_ID = 1, /**< Minimum valid VLAN ID */ MAX_VLAN_ID = 4095, /**< Maximum valid VLAN ID */ DEFAULT_VLAN_ID = MIN_VLAN_ID, /**< Default VLAN ID */ MIN_COS = 0, /**< Minimum CoS value */ MAX_COS = 7, /**< Maximum CoS value */ DEFAULT_COS = MIN_COS, /**< Default CoS value */ MIN_TOS = 0, /**< Minimum ToS value */ MAX_TOS = 255, /**< Maximum ToS value */ DEFAULT_TOS = MIN_TOS, /**< Default ToS value */ MAX_ETHER_TYPE_SIZE = 0x600, /**< Maximum Ethernet type field value */ OVERHEAD_FUDGE_VALUE = 50, /**< Fudge factor for overhead estimates */ DEFAULT_PORTS_PER_PAGE = 4, /**< Default number of ports shown per display page */ VLAN_TAG_SIZE = 4, /**< Size of an 802.1Q VLAN tag in bytes */ MAX_PRIME_COUNT = 4, /**< Maximum prime-burst packet count */ NUM_SEQ_PKTS = 16, /**< Number of sequence packet slots per port */ FIRST_SEQ_PKT = 0, /**< First sequence slot index */ SINGLE_PKT = (FIRST_SEQ_PKT + NUM_SEQ_PKTS), /**< Single-packet template (slot 16) */ SPECIAL_PKT = (SINGLE_PKT + 1), /**< Scratch/special packet (slot 17) */ RANGE_PKT = (SPECIAL_PKT + 1), /**< Range-mode template (slot 18) */ LATENCY_PKT = (RANGE_PKT + 1), /**< Latency probe packet (slot 19) */ NUM_TOTAL_PKTS = (LATENCY_PKT + 1), /**< Total per-port packet slots */ INTER_FRAME_GAP = 12, /**< in bytes */ START_FRAME_DELIMITER = 1, /**< Starting frame delimiter bytes */ PKT_PREAMBLE_SIZE = 7, /**< in bytes */ /* total number of bytes in frame overhead, includes the FCS checksum byte count */ PKT_OVERHEAD_SIZE = (INTER_FRAME_GAP + START_FRAME_DELIMITER + PKT_PREAMBLE_SIZE + RTE_ETHER_CRC_LEN), MIN_v6_PKT_SIZE = 78, /**< does include FCS bytes */ MAX_RX_QUEUES = 16, /**< RX Queues per port */ MAX_TX_QUEUES = 16, /**< TX Queues per port */ PCAP_PAGE_SIZE = 25 /**< Size of the PCAP display page */ }; /** Compute the wire size of a packet including all physical overhead. */ #define WIRE_SIZE(pkt_size, t) (t)(pkt_size + PKT_OVERHEAD_SIZE) /** Convenience typedef for struct rte_mbuf. */ typedef struct rte_mbuf rte_mbuf_t; /** Union allowing an Ethernet address to be accessed as a 64-bit integer. */ typedef union { struct rte_ether_addr addr; /**< Ethernet address structure */ uint64_t u64; /**< Raw 64-bit representation */ } ethaddr_t; /** Global Pktgen application state: settings, port references, and display parameters. */ typedef struct pktgen_s { int verbose; /**< Verbose flag */ int32_t argc; /**< Number of arguments */ uint32_t blinklist; /**< Port list for blinking the led */ uint32_t flags; /**< Flag values */ volatile int force_quit; /**< Flag to force quit */ struct cmdline *cl; /**< Command Line information pointer */ char *argv[64]; /**< Argument list */ char *hostname; /**< hostname */ int32_t socket_port; /**< port number */ volatile uint8_t timer_running; /**< flag to denote timer is running */ uint16_t ident; /**< IPv4 ident value */ uint16_t last_row; /**< last static row of the screen */ uint16_t nb_ports; /**< Number of ports in the system */ uint8_t starting_port; /**< Starting port to display */ uint8_t ending_port; /**< Ending port to display */ uint8_t nb_ports_per_page; /**< Number of ports to display per page */ uint16_t mbuf_dataroom; /**< Size of data room in mbuf */ uint16_t mbuf_buf_size; /**< MBUF default buf size */ uint16_t mbuf_headroom; /**< Size of headroom in mbuf */ uint16_t nb_rxd; /**< Number of receive descriptors */ uint16_t nb_txd; /**< Number of transmit descriptors */ uint16_t curr_port; /**< Current Port number */ uint64_t hz; /**< Number of cycles per seconds */ uint64_t page_timeout; /**< Timeout for page update */ uint64_t stats_timeout; /**< Timeout for stats update */ uint64_t max_total_ipackets; /**< Total Max seen input packet rate */ uint64_t max_total_opackets; /**< Total Max seen output packet rate */ uint64_t counter; /**< A debug counter */ uint64_t mem_used; /**< Display memory used counters per ports */ uint64_t total_mem_used; /**< Display memory used for all ports */ struct rte_eth_stats cumm_rate_totals; /**< port rates total values */ #ifdef LUA_ENABLED luaData_t *ld; /**< General Lua Data pointer */ luaData_t *ld_sock; /**< Info for Lua Socket */ pthread_t thread; /**< Thread structure for Lua server */ #endif uint8_t *portdesc[RTE_MAX_ETHPORTS]; /**< Port descriptions from lspci */ uint32_t portdesc_cnt; /**< Number of ports in portdesc array */ lscpu_t *lscpu; /**< CPU information */ capture_t capture[RTE_MAX_NUMA_NODES]; /**< Packet capture, 1 struct per socket */ } pktgen_t; enum { /* Pktgen flags bits */ PRINT_LABELS_FLAG = (1 << 0), /**< Print constant labels on stats display */ MAC_FROM_ARP_FLAG = (1 << 1), /**< Configure the SRC MAC from a ARP request */ PROMISCUOUS_ON_FLAG = (1 << 2), /**< Enable promiscuous mode */ NUMA_SUPPORT_FLAG = (1 << 3), /**< Enable NUMA support */ IS_SERVER_FLAG = (1 << 4), /**< Pktgen is a Server */ RESERVED_05 = (1 << 5), /**< Reserved */ LUA_SHELL_FLAG = (1 << 6), /**< Enable Lua Shell */ TX_DEBUG_FLAG = (1 << 7), /**< TX Debug output */ RESERVED_8 = (1 << 8), /**< Reserved */ RESERVED_9 = (1 << 9), /**< Reserved */ BLINK_PORTS_FLAG = (1 << 10), /**< Blink the port leds */ ENABLE_THEME_FLAG = (1 << 11), /**< Enable theme or color support */ CLOCK_GETTIME_FLAG = (1 << 12), /**< Enable clock_gettime() instead of rdtsc() */ JUMBO_PKTS_FLAG = (1 << 13), /**< Enable Jumbo frames */ RESERVED_14 = (1 << 14), /**< Reserved */ MAIN_PAGE_FLAG = (1 << 15), /**< Display the main page */ CPU_PAGE_FLAG = (1 << 16), /**< Display the CPU page */ SEQUENCE_PAGE_FLAG = (1 << 17), /**< Display the Packet sequence page */ RANGE_PAGE_FLAG = (1 << 18), /**< Display the range page */ RESERVED_19 = (1 << 19), /**< Reserved */ SYSTEM_PAGE_FLAG = (1 << 20), /**< Display the System page */ RND_BITFIELD_PAGE_FLAG = (1 << 21), /**< Display the random bitfield page */ LOG_PAGE_FLAG = (1 << 22), /**< Display the message log page */ LATENCY_PAGE_FLAG = (1 << 23), /**< Display latency page */ QSTATS_PAGE_FLAG = (1 << 24), /**< Display the port queue stats */ XSTATS_PAGE_FLAG = (1 << 25), /**< Display the physical port stats */ RESERVED_27 = (1 << 27), /**< Reserved */ RESERVED_28 = (1 << 28), /**< Reserved */ RESERVED_29 = (1 << 29), /**< Reserved */ RESERVED_30 = (1 << 30), /**< Reserved */ UPDATE_DISPLAY_FLAG = (1 << 31) /**< Trigger a display refresh */ }; #define UPDATE_DISPLAY_TICK_INTERVAL 4 /**< Display stat refresh checks per second */ #define UPDATE_DISPLAY_TICK_RATE (pktgen.hz / UPDATE_DISPLAY_TICK_INTERVAL) /**< Tick rate */ /** Bitmask of all page-select flags used to clear the current page before switching. */ #define PAGE_MASK_BITS \ (MAIN_PAGE_FLAG | CPU_PAGE_FLAG | SEQUENCE_PAGE_FLAG | RANGE_PAGE_FLAG | SYSTEM_PAGE_FLAG | \ RND_BITFIELD_PAGE_FLAG | LOG_PAGE_FLAG | LATENCY_PAGE_FLAG | XSTATS_PAGE_FLAG | \ QSTATS_PAGE_FLAG) /** The global pktgen application state singleton. */ extern pktgen_t pktgen; /** Redraw the currently active display page. */ void pktgen_page_display(void); /** * Construct a packet template in a sequence slot. * * @param pinfo Per-port state. * @param seq_idx Packet slot index (0 .. NUM_TOTAL_PKTS-1). * @param type Protocol type override (-1 to use existing). */ void pktgen_packet_ctor(port_info_t *pinfo, int32_t seq_idx, int32_t type); /** * Recalculate the inter-burst TX cycle count for a port's target rate. * * @param pinfo Per-port state. */ void pktgen_packet_rate(port_info_t *pinfo); /** * Test whether an IPv4 address matches the port's source IP. * * @return Non-zero if @p addr matches. */ int pktgen_find_matching_ipsrc(port_info_t *pinfo, uint32_t addr); /** * Test whether an IPv4 address matches the port's destination IP. * * @return Non-zero if @p addr matches. */ int pktgen_find_matching_ipdst(port_info_t *pinfo, uint32_t addr); /** * Worker function launched on each lcore. * * @param arg Unused (required by rte_eal_remote_launch prototype). * @return 0 on clean exit. */ int pktgen_launch_one_lcore(void *arg); /** Start the interactive CLI input loop on the main lcore. */ void pktgen_input_start(void); /** Dump the per-port timer statistics to the log. */ void stat_timer_dump(void); /** Clear the accumulated per-port timer statistics. */ void stat_timer_clear(void); /** Set up the periodic stat-update and display-refresh timers. */ void pktgen_timer_setup(void); /** * Rebuild packet templates for all queues on a port. * * @param pid Port ID. */ void pktgen_setup_packets(uint16_t pid); /** * Return the current time value using the configured time source. * * Returns nanoseconds via clock_gettime(CLOCK_REALTIME) when * CLOCK_GETTIME_FLAG is set, otherwise returns rte_rdtsc() cycles. * * @return * Current time in nanoseconds or TSC cycles. */ static inline uint64_t pktgen_get_time(void) { if (pktgen.flags & CLOCK_GETTIME_FLAG) { struct timespec tp; if (clock_gettime(CLOCK_REALTIME, &tp) < 0) return rte_rdtsc(); return rte_timespec_to_ns(&tp); } else return rte_rdtsc(); } /** * Return the timer frequency for the configured time source. * * @return * 1,000,000,000 when using clock_gettime(), otherwise rte_get_timer_hz(). */ static inline uint64_t pktgen_get_timer_hz(void) { if (pktgen.flags & CLOCK_GETTIME_FLAG) { struct timespec tp = {.tv_nsec = 0, .tv_sec = 1}; return rte_timespec_to_ns(&tp); } else return rte_get_timer_hz(); } /** Latency probe packet payload header. */ typedef struct { uint32_t magic; /**< Magic value (TSTAMP_MAGIC) for probe identification */ uint32_t index; /**< Sequence index used to match TX/RX probes */ uint64_t timestamp; /**< TSC cycle count at transmit time */ } tstamp_t; #define TSTAMP_MAGIC 0xf00dcafe /**< Magic value identifying a latency probe packet */ /** * Atomically OR a set of flags into a port's port_flags atomic. * * @param pinfo Per-port state. * @param flags Bitmask of SEND_* / port-flag bits to set. */ static __inline__ void pktgen_set_port_flags(port_info_t *pinfo, uint64_t flags) { uint64_t val; do { val = rte_atomic64_read(&pinfo->port_flags); } while (!rte_atomic64_cmpset((volatile uint64_t *)&pinfo->port_flags.cnt, val, (val | flags))); } /** * Atomically clear a set of flags from a port's port_flags atomic. * * @param pinfo Per-port state. * @param flags Bitmask of SEND_* / port-flag bits to clear. */ static __inline__ void pktgen_clr_port_flags(port_info_t *pinfo, uint64_t flags) { uint64_t val; do { val = rte_atomic64_read(&pinfo->port_flags); } while ( !rte_atomic64_cmpset((volatile uint64_t *)&pinfo->port_flags.cnt, val, (val & ~flags))); } /** * Test whether any of the given flag bits are set in a port's port_flags. * * @param pinfo Per-port state. * @param flags Bitmask to test. * @return * Non-zero if any bit in @p flags is currently set. */ static __inline__ int pktgen_tst_port_flags(port_info_t *pinfo, uint64_t flags) { return ((rte_atomic64_read(&pinfo->port_flags) & flags) ? 1 : 0); } /** Enable/disable state values for CLI and command arguments. */ enum { DISABLE_STATE = 0, /**< Disabled */ ENABLE_STATE = 1 /**< Enabled */ }; /** * Parse an on/off/enable/disable/start state string. * * @param state "on", "enable", or "start" → ENABLE_STATE; else → DISABLE_STATE. * @return * ENABLE_STATE or DISABLE_STATE. */ static __inline__ uint32_t estate(const char *state) { return (!strcasecmp(state, "on") || !strcasecmp(state, "enable") || !strcasecmp(state, "start")) ? ENABLE_STATE : DISABLE_STATE; } /** Latency sampler algorithm selection. */ enum { LATSAMPLER_UNSPEC, /**< Unspecified (use default) */ LATSAMPLER_SIMPLE, /**< Uniform random sampling */ LATSAMPLER_POISSON /**< Poisson-rate sampling */ }; /** * Return the pktgen version string. * * @return * Pointer to a static buffer containing the version string. */ static inline const char * pktgen_version(void) { static char pkt_version[64]; if (pkt_version[0] != 0) return pkt_version; snprintf(pkt_version, sizeof(pkt_version), "%s", PKTGEN_VERSION); return pkt_version; } /** * Free an existing string and duplicate a new one. * * @param str Existing string to free (may be NULL). * @param new String to duplicate (may be NULL, returns NULL in that case). * @return * Newly duplicated string, or NULL if @p new is NULL. */ static __inline__ char * strdupf(char *str, const char *new) { if (str) free(str); return (new == NULL) ? NULL : strdup(new); } /** * Execute a shell command and display its output line-by-line. * * @param cmd * Shell command string to execute via popen(). * @param display * Callback invoked for each output line; receives the line and an * accumulating counter, returns the updated counter. * @return * Final value returned by @p display, or -1 on popen() failure. */ static __inline__ int do_command(const char *cmd, int (*display)(char *, int)) { FILE *f; int i; char *line = NULL; size_t line_size = 0; f = popen(cmd, "r"); if (f == NULL) { pktgen_log_error("Unable to run '%s' command", cmd); return -1; } i = 0; while (getline(&line, &line_size, f) > 0) i = display(line, i); if (f) pclose(f); if (line) free(line); return i; } #ifndef MEMPOOL_F_DMA #define MEMPOOL_F_DMA 0 #endif #ifdef __cplusplus } #endif #endif /* _PKTGEN_H_ */ ================================================ FILE: app/xorshift64star.c ================================================ #include "xorshift64star.h" // The state of the "randomization engine". // Defined here so that it can be shared between files. uint64_t xor_state[1] = {1}; ================================================ FILE: app/xorshift64star.h ================================================ /* * Marsaglia, George (July 2003). "Xorshift RNGs". * Journal of Statistical Software. * * SPDX-License-Identifier: BSD-3-Clause */ /* https://en.wikipedia.org/wiki/Xorshift */ #ifndef _XORSHIFT64STAR_H_ #define _XORSHIFT64STAR_H_ /** * @file * * Xorshift64* fast pseudo-random number generator. * * Based on Marsaglia (2003) "Xorshift RNGs", Journal of Statistical Software. * See https://en.wikipedia.org/wiki/Xorshift for the algorithm description. * The state must be seeded with a non-zero value before the first call. */ #include #ifdef __cplusplus extern "C" { #endif /** Generator state (must be seeded with a non-zero value before first use). */ extern uint64_t xor_state[1]; /** * Generate the next 64-bit pseudo-random value using the xorshift64* algorithm. * * Updates @c xor_state[0] in place and returns the scrambled output. * * @return * 64-bit pseudo-random number. */ static inline uint64_t xorshift64star(void) { uint64_t x = xor_state[0]; /* The state must be seeded with a nonzero value. */ x ^= x >> 12; // a x ^= x << 25; // b x ^= x >> 27; // c xor_state[0] = x; return x * 0x2545F4914F6CDD1D; } #ifdef __cplusplus } #endif #endif /* _XORSHIFT64STAR_H_ */ ================================================ FILE: cfg/2-ports.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '03:00.0', '03:00.1', ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '1,2-7,8-13', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'allowlist': ( '03:00.0', '03:00.1', ), 'opts': ( '-v', '-T', '-P', '-G', #'-c', #'-j', ), 'map': ( '[2-4:5-7].0', '[8-10:11-13].1', ), 'theme': 'themes/black-yellow.theme', #'loadfile': '2-ports' } ================================================ FILE: cfg/2-vf-ports.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '82:01.0', '82:01.1', ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-18', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'allowlist': ( '82:01.0', '82:01.1', ), 'opts': ( '-v', '-T', '-P', '-G', #'-c', #'-j', ), 'map': ( '[15:16].0', '[17:18].1', ), 'theme': 'themes/black-yellow.theme', 'loadfile': '2-ports' } ================================================ FILE: cfg/bond.cfg ================================================ description = 'Pktgen setup to use the bonding PMD driver, 2 bonds, 4 ports per bond.' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'uio': 'igb_uio' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-18', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'vdev': ( 'net_bonding0,mode=4,xmit_policy=l23,worker=0000:81:00.0,worker=0000:81:00.1,worker=0000:81:00.2,worker=0000:81:00.3', 'net_bonding1,mode=4,xmit_policy=l23,worker=0000:83:00.0,worker=0000:83:00.1,worker=0000:83:00.2,worker=0000:83:00.3', ), 'blocklist': ( # '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'opts': ( '-T', '-P' ), 'map': ( '[15:16].8', '[17:18].9' ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/client_memif.cfg ================================================ description = 'Client side of the MEMIF PMD' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '-l 17,18', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg2', 'vdev': '=net_memif0,role=worker', 'blocklist': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'opts': ( '-T', '-P', ), 'map': ( '18.0' ), 'theme': 'themes/black-white.theme', } ================================================ FILE: cfg/client_mif.cfg ================================================ description = 'Client side of the MIF PMD' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '17,18', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg2', 'vdev': 'net_mif0,iftype=client,rxring=2048,txring=2048,scfg=0_8192_1960_d,cache=256', 'blocklist': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'opts': ( '-T', '-P', ), 'map': '18.0', 'theme': 'themes/black-white.theme', } ================================================ FILE: cfg/cnet-fwd-2.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '03:00.0', '82:00.0' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '2,4-7,14-18', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'allowlist': ( '03:00.0', '82:00.0' ), 'opts': ( '-T', '-P', '-G', ), 'map': ( '[4-5:6-7].0', '[14-15:16-18].1', ), 'theme': 'themes/black-yellow.theme', 'loadfile': 'cnet-fwd-2' } ================================================ FILE: cfg/cnet-fwd.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '03:00.0', '03:00.1', '05:00.0', '05:00.1', ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '1,2-4,5-7,8-10,11-13', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'allowlist': ( '03:00.0', '03:00.1', '05:00.0', '05:00.1', ), 'opts': ( '-v', '-T', '-P', '-G', #'-j', ), 'map': ( '[2:3-4].0', '[5:6-7].1', '[8:9-10].2', '[11:12-13].3', ), # 'pcap': ( # '1:pcap/test1.pcap', # ), 'theme': 'themes/black-yellow.theme', } ================================================ FILE: cfg/dapi-l3fwd.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3' ), 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s', ), 'cores': '14,15-18', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3' ), 'opts': ( '-T', '-P', ), 'map': ( '[15:16].0', '[17:18].1'), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/default-100G.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '82:00.0' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '2,3-4', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( #'03:00.0', '05:00.0', #'81:00.0', '84:00.0' ), 'allowlist': ( '82:00.0,safe-mode-support=1', ), 'opts': ( '-v', '-T', '-P', ), 'map': ( '[3:4].0', ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/default-gui.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '03:00.0' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '2,3-4', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( #'81:00.0', '84:00.0' ), 'opts': ( '-v', '-T', '-P', '-G', ), 'map': ( '[3:4].0' ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/default.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '5e:00.0', '5e:00.1', 'af:00.0', 'af:00.1' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '2,3-4,5-6,28-29,30-31', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( #'03:00.0', '05:00.0', #'81:00.0', '84:00.0' ), 'allowlist': ( '5e:00.0', '5e:00.1', 'af:00.0', 'af:00.1' ), 'opts': ( '-v', '-T', '-P', ), 'map': ( '[3:4].0', '[5:6].1', '[28:29].2', '[30:31].3' ), 'theme': 'themes/black-yellow.theme', #'shared': '/usr/local/lib/x86_64-linux-gnu/dpdk/pmds-21.1' } ================================================ FILE: cfg/dfs.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-22', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'shared': '/tmp/dfs', 'blocklist': ( #'81:00.0', '81:00.1', '81:00.2', '81:00.3', #'83:00.0', '83:00.1', '83:00.2', '83:00.3', '81:00.2', '81:00.3', '83:00.2', '83:00.3' ), 'opts': ( '-T', '-P', ), 'map': ( '[15:16].0', '[17:18].1', '[19:20].2', '[21:22].3' ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/dnet-echo.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-18', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', #'83:00.0', '83:00.1', '83:00.2', '83:00.3', '81:00.2', '81:00.3', '83:00.2', '83:00.3' ), 'opts': ( '-v', '-T', '-P', ), 'map': ( '[15:16].0', '[17:18].1', ), 'theme': 'themes/black-yellow.theme', 'loadfile': 'dnet-echo' } ================================================ FILE: cfg/four-ports.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '03:00.0', '03:00.1', '05:00.0', '05:00.1', ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '1,2-4,5-7,8-10,11-13', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'allowlist': ( '03:00.0', '03:00.1', '05:00.0', '05:00.1', ), 'opts': ( '-v', '-T', '-P', '-G', ), 'map': ( '[2:3-4].0', '[5:6-7].1', '[8:9-10].2', '[11:12-13].3', ), 'theme': 'themes/black-yellow.theme', # 'loadfile': 'test/portstats_with_delay.lua', # 'loadfile': 'test/portstats.lua', } ================================================ FILE: cfg/half-bond.cfg ================================================ description = 'Setup to use the bonding driver, but only one bond connected to non-bonded ports' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '1,14-21,22-23', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'vdev': 'net_bonding0,mode=4,xmit_policy=l23,worker=0000:81:00.0,worker=0000:81:00.1,worker=0000:81:00.2,worker=0000:81:00.3', 'blocklist': ( '81:00.0', '83:00.0' ), 'opts': ( '-T', '-P', ), 'map': ( '[14:15].0', '[16:17].1', '[18:19].2', '[20:21].3', '[22:23].8', ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/hs-fwd.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '03:00.0', '03:00.1', '82:00.0' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '2,4-7,8-11,14-17', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'allowlist': ( '03:00.0', '03:00.1', '82:00.0' ), 'opts': ( '-v', '-T', '-P', '-G', ), 'map': ( '[4-5:6-7].0', '[8-9:10-11].1', '[14-15:16-17].2', ), 'theme': 'themes/black-yellow.theme', 'loadfile': 'hs-fwd' } ================================================ FILE: cfg/ioat.cfg ================================================ description = 'Slave configuration for two Pktgen\'s running' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-18', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'ioat', 'blocklist': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.2', '83:00.3', ), 'opts': ( '-T', '-P', ), 'map': ( '[15:16].0', '[17:18].1' ), 'theme': 'themes/black-yellow.theme', } ================================================ FILE: cfg/lat.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '03:00.0', '03:00.1', '82:00.0', '82:00.1' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '2,4,6,8,10,14,16,18', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'allowlist': ( '03:00.0', '03:00.1', '82:00.0', '82:00.1' ), 'opts': ( '-v', '-T', '-P', '-G', ), 'map': ( '[4:6].0', '[8:10].1', '[14:16].2', '[18:20].3', ), 'theme': 'themes/black-yellow.theme', 'loadfile': 'cnet-fwd' } ================================================ FILE: cfg/lb-fwd.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '82:01.0', '82:01.1', '82:01.2' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '2,14-17,18-21,22-25', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'allowlist': ( '82:01.0', '82:01.1', '82:01.2' ), 'opts': ( '-v', '-T', '-P', '-G', ), 'map': ( '[14-15:16-17].0', '[18-19:20-21].1', '[22-23:24-25].2', ), 'theme': 'themes/black-yellow.theme', 'loadfile': 'lb-fwd' } ================================================ FILE: cfg/many-cores.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '83:00.0', ), 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '1,14-23,42-51,24-27,52-55', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( '81:00.1', '81:00.2', '81:00.3', '83:00.1', '83:00.2', '83:00.3', ), 'opts': ( '-T', '-P', ), 'map': ( '[14-23:42-51].0', '[24-27:52-55].1', ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/multi-port.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-26', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( #'81:00.0', '81:00.1', '81:00.2', '81:00.3', #'83:00.0', '83:00.1', '83:00.2', '83:00.3', '81:00.1', '81:00.2', '81:00.3', '83:00.1', '83:00.2', '83:00.3', '81:00.2', '81:00.3', '83:00.2', '83:00.3' ), 'opts': ( '-T', '-P', ), 'map': ( '[15-17:18-20].0', '[21-23:24-26].1', ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/one-port.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '03:00.0', ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '1,2-4', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'allowlist': ( '03:00.0', ), 'opts': ( '-v', '-T', '-P', '-G', #'-j', ), 'map': ( '[2:3-4].0', ), 'theme': 'themes/black-yellow.theme', 'loadfile': 'cnet-fwd' } ================================================ FILE: cfg/pcap.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-22', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( #'81:00.0', '81:00.1', '81:00.2', '81:00.3', #'83:00.0', '83:00.1', '83:00.2', '83:00.3', '81:00.2', '81:00.3', '83:00.2', '83:00.3' ), 'opts': ( '-v', '-T', '-P', ), 'map': ( '[15:16].0', '[17:18].1', '[19:20].2', '[21:22].3' ), 'pcap': ( '0:pcap/test1.pcap', '1:pcap/gtpv1-u-1024.pcap' ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/pdump.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '83:00.0', '83:00.1', ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-18', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.2', '83:00.3', ), 'opts': ( '-T', '-P', ), 'map': ( '[15:16].0', '[17:18].1', ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/pktgen-1.cfg ================================================ description = 'The initial side of two Pktgen\'s running in the same system' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,16-19', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg1', 'blocklist': ( '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'opts': ( '-T', '-P', ), 'map': ( '[16:17].0', '[18:19].1', ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/pktgen-2.cfg ================================================ description = 'Slave configuration for two Pktgen\'s running' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '15,20-23', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg2', 'blocklist': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.2', '83:00.3', ), 'opts': ( '-T', '-P', ), 'map': ( '[20:21].0', '[21:23].1' ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/server_memif.cfg ================================================ description = 'Using the MEMIF PMD this is the initial side' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', , ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg1', 'vdev': 'net_memif0,role=initial', 'blocklist': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'opts': ( '-T', '-P', ), 'map': ( '15.0', ), 'theme': 'themes/black-white.theme' } ================================================ FILE: cfg/server_mif.cfg ================================================ description = 'Using the MIF PMD this is the initial side' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', , ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg1', 'vdev': 'net_mif0,iftype=server', 'blocklist': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'opts': ( '-T', '-P', ), 'map': ( '15.0', ), 'theme': 'themes/black-white.theme' } ================================================ FILE: cfg/socket.cfg ================================================ description = 'Slave configuration for two Pktgen\'s running' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-16', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg2', 'blocklist': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', #'83:00.0', '83:00.1', '83:00.2', '83:00.3', ), 'opts': ( '-T', '-P', ), 'map': ( '[15:16].0' ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/two-ports-shared.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-18', 'nrank': '4', 'proc': 'primary', 'log': '7', 'prefix': 'pg', 'plugin': ( '/tmp/sofiles/librte_mempool_ring.so.1.1', '/tmp/sofiles/librte_pmd_i40e.so.2.1' ), 'blocklist': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', #'83:00.0', '83:00.1', '83:00.2', '83:00.3', '83:00.2', '83:00.3' ), 'opts': ( '-T', '-P', ), 'map': ( '[15:16].0', '[17:18].1' ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/two-ports.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', '83:00.0', '83:00.1', '83:00.2', '83:00.3' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), 'ld_path': ( ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-18', 'nrank': '4', 'proc': 'primary', 'log': '7', 'prefix': 'pg', 'plugin': ( ), 'blocklist': ( '81:00.0', '81:00.1', '81:00.2', '81:00.3', #'83:00.0', '83:00.1', '83:00.2', '83:00.3', '83:00.2', '83:00.3' ), 'opts': ( '-T', '-P', ), 'map': ( '[15:16].0', '[17:18].1' ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: cfg/tx_perf.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '82:00.0', '82:00.1', ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '14,15-18', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'allowlist': ( '82:00.0', #'82:00.1', ), 'opts': ( '-v', '-T', '-P', '-G', #'-c', #'-j', ), 'map': ( '[15:16-17].0', ), 'theme': 'themes/black-yellow.theme', 'loadfile': 'tx_perf' } ================================================ FILE: cfg/vfio-fwd.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '82:02.0', '82:02.1' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '2,14-17,18-21', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'allowlist': ( '82:02.0', '82:02.1' ), 'opts': ( '-v', '-T', '-P', '-G', ), 'map': ( '[14-15:16-17].0', '[18-19:20-21].1', ), 'theme': 'themes/black-yellow.theme', 'loadfile': 'vfio-fwd' } ================================================ FILE: cfg/xdp-100.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '86:00.0' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', ), 'cores': '28,29-30', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( ), 'allowlist': ( '86:00.0' ), 'opts': ( '-v', '-T', '-P', ), 'map': ( '[29:30].0' ), 'theme': 'themes/black-yellow.theme', 'loadfile': 'xdp-100G', #'shared': '/usr/local/lib/x86_64-linux-gnu/dpdk/pmds-21.1' } ================================================ FILE: cfg/xdp-40.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '5e:00.0', '5e:00.1' ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ('sudo',), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', ), 'cores': '10,11-12,13-14', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( ), 'allowlist': ( '5e:00.0', '5e:00.1', ), 'opts': ( '-v', '-T', '-P', ), 'map': ( '[11:12].0', '[13:14].1', ), 'theme': 'themes/black-yellow.theme', 'loadfile': 'xdp-40G', #'shared': '/usr/local/lib/x86_64-linux-gnu/dpdk/pmds-21.1' } ================================================ FILE: cfg/xl710.cfg ================================================ description = 'A Pktgen default simple configuration' # Setup configuration setup = { 'exec': ( 'sudo', ), 'devices': ( '81:00.0', '83:00.0', ), # UIO module type, igb_uio, vfio-pci or uio_pci_generic 'uio': 'vfio-pci' } # Run command and options run = { 'exec': ( 'sudo', ), # Application name and use app_path to help locate the app 'app_name': 'pktgen', # using (sdk) or (target) for specific variables # add (app_name) of the application # Each path is tested for the application 'app_path': ( './usr/local/bin/%(app_name)s', '/usr/local/bin/%(app_name)s' ), 'cores': '1,14-27', 'nrank': '4', 'proc': 'auto', 'log': '7', 'prefix': 'pg', 'blocklist': ( #'81:00.0', '83:00.0', '85:00.0', '85:00.1', '85:00.2', '85:00.3', ), 'opts': ( '-v', '-T', '-P', ), 'map': ( '[14:15-20].0', '[21-26:27].1', ), 'theme': 'themes/black-yellow.theme' } ================================================ FILE: docs/LUA_API.md ================================================ # Pktgen Lua API (selected) This document describes the Lua functions exported by the `pktgen` module that were recently updated/clarified, and the table shapes they return. ## Conventions - Most functions return a Lua table keyed by numeric port id (`pid`). - Many returned tables include an `n` field holding the number of ports in the returned table. - Unless explicitly stated, tables may contain additional fields beyond what is documented here. ## `pktgen.portStats(portlist, type)` Returns per-port statistics. - `portlist`: port list string understood by pktgen (e.g. `"0"`, `"0-3"`, `"0,2"`). ### Signature `pktgen.portStats(portlist)` ### Return value A table with: - Numeric keys: one entry per port id. - `n`: number of ports returned. Each per-port entry contains: The per-port entry table mirrors a subset of the C `port_stats_t` structure. ### Per-port table layout Each port entry contains: - `curr`: a table containing: - `ipackets`, `opackets`, `ibytes`, `obytes` - `ierrors`, `oerrors`, `rx_nombuf`, `imissed` - `sizes`: packet size distribution: - `_64`, `_65_127`, `_128_255`, `_256_511`, `_512_1023`, `_1024_1522` - `broadcast`, `multicast`, `jumbo`, `runt`, `unknown` - `qstats`: per-queue tables keyed by queue id. - The number of entries is limited to the number of **configured Rx queues** for the port. - Queue ids start at `0`. - Each queue entry has: - `ipackets`, `opackets`, `errors` ### Example ```lua -- Get the current port stats snapshot for ports 0-1 local t = pktgen.portStats("0-1") for pid = 0, 1 do print("port", pid, "curr rx", t[pid].curr.ipackets) print("port", pid, "curr tx", t[pid].curr.opackets) end ``` ## `pktgen.portInfo(portlist)` Returns per-port configuration and informational fields. Notes: - This API intentionally does **not** include `port_stats_t`. - This API intentionally does **not** include `rte_eth_stats` (`stats`/`totals` style counters). ### Return value A table with: - Numeric keys: one entry per port id. - `n`: number of ports returned. Each per-port entry includes a set of informational subtables (non-exhaustive): - `total_stats`: pktgen global totals (e.g. `max_ipackets`, `max_opackets`, cumulative rate totals) - `info`: transmit configuration (pattern type, tx_count, tx_rate, pkt_size, bursts, eth/proto type, vlanid) - `l2_l3_info`: L2/L3 fields (ports, TTL, src/dst IP, src/dst MAC) - `tx_debug`: internal tx debug fields - `802.1p`: QoS fields (`cos`, `dscp`, `ipp`) - `VxLAN`: VxLAN fields and `link_state` - `pci_vendor`: PCI/vendor string (top-level field, if available) ### Example ```lua local info = pktgen.portInfo("0") print("link", info[0].VxLAN.link_state) print("tx rate", info[0].info.tx_rate) ``` ## Example output of scripts/port_stats_dump.lua ```lua Pktgen> load scripts/port_stats_dump.lua Pktgen:/> load scripts/port_stats_dump.lua Executing 'scripts/port_stats_dump.lua' pktgen.portStats("0-1") { [0] = { ["curr"] = { ["ibytes"] = 0 ["ierrors"] = 0 ["imissed"] = 0 ["ipackets"] = 0 ["obytes"] = 0 ["oerrors"] = 0 ["opackets"] = 0 ["rx_nombuf"] = 0 } ["qstats"] = { [0] = { ["errors"] = 0 ["ipackets"] = 0 ["opackets"] = 0 } [1] = { ["errors"] = 0 ["ipackets"] = 0 ["opackets"] = 0 } } ["sizes"] = { ["_1024_1522"] = 0 ["_128_255"] = 0 ["_256_511"] = 0 ["_512_1023"] = 0 ["_64"] = 0 ["_65_127"] = 0 ["broadcast"] = 0 ["jumbo"] = 0 ["multicast"] = 0 ["runt"] = 0 ["unknown"] = 0 } } [1] = { ["curr"] = { ["ibytes"] = 0 ["ierrors"] = 0 ["imissed"] = 0 ["ipackets"] = 0 ["obytes"] = 0 ["oerrors"] = 0 ["opackets"] = 0 ["rx_nombuf"] = 0 } ["qstats"] = { [0] = { ["errors"] = 0 ["ipackets"] = 0 ["opackets"] = 0 } [1] = { ["errors"] = 0 ["ipackets"] = 0 ["opackets"] = 0 } } ["sizes"] = { ["_1024_1522"] = 0 ["_128_255"] = 0 ["_256_511"] = 0 ["_512_1023"] = 0 ["_64"] = 0 ["_65_127"] = 0 ["broadcast"] = 0 ["jumbo"] = 0 ["multicast"] = 0 ["runt"] = 0 ["unknown"] = 0 } } ["n"] = 2 } ``` ================================================ FILE: docs/QUICKSTART.md ================================================ # Pktgen Quick Start This guide provides a concise path from a clean system to generating traffic with Pktgen. ## 1. Prerequisites - A modern Linux distribution (tested on Ubuntu 22.04+) - Root (or sudo) access - Git, build-essential (gcc/clang, make), Python 3 - Meson & Ninja: `pip install meson ninja` (or distro packages) - libbsd headers: `sudo apt install libbsd-dev` - Huge pages configured (e.g. 1G or 2M pages) and IOMMU enabled if using `vfio-pci` - Latest DPDK source () ## 2. Build and Install DPDK ```bash git clone https://github.com/DPDK/dpdk.git cd dpdk meson setup build meson compile -C build sudo meson install -C build sudo ldconfig ``` If `libdpdk.pc` is placed under a non-standard path (commonly `/usr/local/lib/x86_64-linux-gnu/pkgconfig`), export: ```bash export PKG_CONFIG_PATH=/usr/local/lib/x86_64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH ``` ## 3. Clone and Build Pktgen ```bash git clone https://github.com/pktgen/Pktgen-DPDK.git cd Pktgen-DPDK meson setup builddir meson compile -C builddir ``` (Optional) Using Make wrapper: ```bash make # builds via meson/ninja make rebuild make rebuildlua ``` ## 4. Device Preparation Bind NIC ports for DPDK use (one-time per boot). You can use `./tools/run.py -s ` or standard DPDK binding tools. Example with run script: ```bash sudo ./tools/run.py -s default ``` Manual invocation example (adjust cores & ports): ## 5. Launch Pktgen Using a configuration file: ```bash sudo ./builddir/app/pktgen -l 0-3 -n 4 -- -P -m "[1:2].0" -T ``` Where: - `-l 0-3` lcore list (control + workers) - `-n 4` memory channels - After `--` are Pktgen options; `-P` promiscuous, `-m` maps core pairs to port, `-T` enables themed/color output ## 6. Basic Console Commands Inside the interactive console (`Pktgen>` prompt): ```text help # list commands port 0 # focus on port 0 set 0 size 512 # change packet size start 0 # start transmitting on port 0 stop 0 # stop transmitting page stats # view statistics page save test.lua # save current configuration to lua script Remote execution via socket (default port 22022): ``` ## 7. Lua Scripting Run a script at startup: ```bash echo "print(pktgen.info.Pktgen_Version)" | socat - TCP4:localhost:22022 ``` ## 8. Performance Tips - Pin isolated cores (`isolcpus=` kernel parameter or `taskset`) - Keep NIC and memory allocation on the same NUMA node - Ensure sufficient mbufs (configured in source or options) - Disable power management / frequency scaling for consistent latency - Use latest stable DPDK + recent CPU microcode ## 9. Troubleshooting | Missing `libdpdk.so` | `ldconfig` not updated | Run `sudo ldconfig` or adjust `/etc/ld.so.conf.d` | | Permission denied | IOMMU / vfio not enabled | Add `intel_iommu=on` (or `amd_iommu=on`) to GRUB and reboot | | Low performance | NUMA mismatch / core sharing | Reassign cores & ensure local memory | | Socket connect fails | Port blocked/firewall | Check `22022` listening, adjust firewall | ## 10. Next Steps - Explore `cfg/` configs for multi-port examples - Use `page range` or `page seq` for more advanced packet patterns - Capture traffic with Wireshark/tcpdump to validate packet contents --- For deeper details refer to the main README or full documentation site. ================================================ FILE: docs/STYLE.md ================================================ # Documentation & Markdown Style Guide This guide summarizes the conventions enforced (and encouraged) for all Markdown and docs in this repository. It complements `CONTRIBUTING.md` and the root `.markdownlint.yml` configuration. ## 1. Linting - All Markdown must pass `markdownlint` (locally via the pre-commit hook and in CI). - Disabled rule: MD013 (line length) – long lines allowed where readability (tables / URLs / command lines) benefits. - Do not locally override rules unless absolutely necessary; prefer restructuring content. ## 2. Headings - Use ATX (`#`, `##`, `###`) style only. - Start at a single `#` per file (title) and increment by one level; avoid skipping levels. - Surround each heading with exactly one blank line above and below. - Avoid trailing punctuation (no final colon, period, or question mark) unless part of a proper noun. ## 3. Lists - Ordered lists: use `1.` for every item (markdownlint will auto-render numbers). - Unordered lists: use `-` (not `*` or `+`). - Put a blank line before and after a list block (unless tightly bound to a parent list item sub-block). - Indent nested list content by two spaces. ## 4. Code Blocks - Always use fenced blocks (backticks). Indented code blocks are not allowed. - Supply a language hint whenever possible (`bash`, `python`, `lua`, `text`, `console`). - Surround fenced blocks with one blank line before and after. - For shell commands, omit the leading `$` unless demonstrating interactive copy/paste prevention; if shown, also show output to satisfy MD014. - Use `text` for pseudo-code or command output with mixed content. ## 5. Inline Formatting - Wrap bare URLs in angle brackets ``. - Use backticks for filenames, commands, CLI flags, environment variables, and literal code tokens. - Prefer emphasis (`*italic*`) sparingly; bold only for strong warnings or section callouts. ## 6. Tables - Surround tables with one blank line above and below. - Keep header separator row aligned but do not over-focus on spacing (lint does not require column width alignment). - Use backticks for code-like cell content. ## 7. Blockquotes - No blank lines inside a single logical blockquote group. - Each quoted paragraph or list line should start with `>`. - Use blockquotes for external notices, important upstream references, or migration notes—not for styling. ## 8. Line Length & Wrapping - Long lines are acceptable (MD013 disabled). Do not hard-wrap paragraphs unless clarity significantly improves. - Keep tables and link references on single lines when feasible. ## 9. File Naming & Placement - Use `ALL_CAPS.md` for top-level meta-docs (README, LICENSE, CONTRIBUTING, INSTALL). - Place style / guide / topic-specific docs under `docs/`. - Use descriptive filenames (e.g. `QUICKSTART.md`, `STYLE.md`). ## 10. Tone & Clarity - Prefer imperative, concise wording ("Run the script" vs. "You should run"). - Avoid marketing language—be factual and actionable. - Provide context before commands; describe what a block does if non-obvious. ## 11. Common Patterns | Pattern | Example | |---------|---------| | Command sequence | `meson setup builddir && meson compile -C builddir` | | Config file snippet | ``setup = { 'devices': ('81:00.0',) }`` | | Shell pipeline | ``echo \"test\" \| socat - TCP4:localhost:22022`` | | Lua script | ``printf("Hello!\n")`` | | Output capture | ``Pktgen Version: 25.08.0`` | ## 12. Adding New Docs - Link new end-user docs from the main `README.md` or an appropriate existing doc section. - Keep `QUICKSTART.md` focused—avoid duplicating deep details already covered elsewhere. - When adding a new feature, include: purpose, quick example, limitations, and any performance considerations. ## 13. Do / Avoid Summary | Do | Avoid | |----|-------| | Provide a runnable example for new features | Giant monolithic examples without explanation | | Use consistent fenced code | Mixing indentation and fenced styles | | Reference existing sections instead of duplicating content | Copy-pasting large README segments | | Keep tables compact and scannable | Over-formatting table spacing | | Add language hints to all code blocks | Leaving unlabeled fences | ## 14. Exceptions Rare cases (generated content, pasted command output, license headers) may intentionally violate some guidelines—these should be minimal and explained in a comment or preceding sentence. --- Questions or suggestions? Update this file or open an issue. ================================================ FILE: docs/api/doxy-api-index.md ================================================ API === The public API headers are grouped by topics: - **device**: [dev] (@ref rte_dev.h), [ethdev] (@ref rte_ethdev.h), [ethctrl] (@ref rte_eth_ctrl.h), [rte_flow] (@ref rte_flow.h), [rte_tm] (@ref rte_tm.h), [rte_mtr] (@ref rte_mtr.h), [bbdev] (@ref rte_bbdev.h), [cryptodev] (@ref rte_cryptodev.h), [security] (@ref rte_security.h), [compressdev] (@ref rte_compressdev.h), [compress] (@ref rte_comp.h), [eventdev] (@ref rte_eventdev.h), [event_eth_rx_adapter] (@ref rte_event_eth_rx_adapter.h), [event_eth_tx_adapter] (@ref rte_event_eth_tx_adapter.h), [event_timer_adapter] (@ref rte_event_timer_adapter.h), [event_crypto_adapter] (@ref rte_event_crypto_adapter.h), [rawdev] (@ref rte_rawdev.h), [metrics] (@ref rte_metrics.h), [bitrate] (@ref rte_bitrate.h), [latency] (@ref rte_latencystats.h), [devargs] (@ref rte_devargs.h), [PCI] (@ref rte_pci.h), [vdev] (@ref rte_bus_vdev.h), [vfio] (@ref rte_vfio.h) - **device specific**: [softnic] (@ref rte_eth_softnic.h), [bond] (@ref rte_eth_bond.h), [vhost] (@ref rte_vhost.h), [vdpa] (@ref rte_vdpa.h), [KNI] (@ref rte_kni.h), [ixgbe] (@ref rte_pmd_ixgbe.h), [i40e] (@ref rte_pmd_i40e.h), [ice] (@ref rte_pmd_ice.h), [bnxt] (@ref rte_pmd_bnxt.h), [dpaa] (@ref rte_pmd_dpaa.h), [dpaa2] (@ref rte_pmd_dpaa2.h), [dpaa2_mempool] (@ref rte_dpaa2_mempool.h), [dpaa2_cmdif] (@ref rte_pmd_dpaa2_cmdif.h), [dpaa2_qdma] (@ref rte_pmd_dpaa2_qdma.h), [crypto_scheduler] (@ref rte_cryptodev_scheduler.h) - **memory**: [memseg] (@ref rte_memory.h), [memzone] (@ref rte_memzone.h), [mempool] (@ref rte_mempool.h), [malloc] (@ref rte_malloc.h), [memcpy] (@ref rte_memcpy.h) - **timers**: [cycles] (@ref rte_cycles.h), [timer] (@ref rte_timer.h), [alarm] (@ref rte_alarm.h) - **locks**: [atomic] (@ref rte_atomic.h), [mcslock] (@ref rte_mcslock.h), [rwlock] (@ref rte_rwlock.h), [spinlock] (@ref rte_spinlock.h), [ticketlock] (@ref rte_ticketlock.h), [RCU] (@ref rte_rcu_qsbr.h) - **CPU arch**: [branch prediction] (@ref rte_branch_prediction.h), [cache prefetch] (@ref rte_prefetch.h), [SIMD] (@ref rte_vect.h), [byte order] (@ref rte_byteorder.h), [CPU flags] (@ref rte_cpuflags.h), [CPU pause] (@ref rte_pause.h), [I/O access] (@ref rte_io.h) - **CPU multicore**: [interrupts] (@ref rte_interrupts.h), [launch] (@ref rte_launch.h), [lcore] (@ref rte_lcore.h), [per-lcore] (@ref rte_per_lcore.h), [service cores] (@ref rte_service.h), [keepalive] (@ref rte_keepalive.h), [power/freq] (@ref rte_power.h) - **layers**: [ethernet] (@ref rte_ether.h), [ARP] (@ref rte_arp.h), [HIGIG] (@ref rte_higig.h), [ICMP] (@ref rte_icmp.h), [ESP] (@ref rte_esp.h), [IPsec] (@ref rte_ipsec.h), [IPsec group] (@ref rte_ipsec_group.h), [IPsec SA] (@ref rte_ipsec_sa.h), [IPsec SAD] (@ref rte_ipsec_sad.h), [IP] (@ref rte_ip.h), [SCTP] (@ref rte_sctp.h), [TCP] (@ref rte_tcp.h), [UDP] (@ref rte_udp.h), [GTP] (@ref rte_gtp.h), [GRO] (@ref rte_gro.h), [GSO] (@ref rte_gso.h), [frag/reass] (@ref rte_ip_frag.h), [LPM IPv4 route] (@ref rte_lpm.h), [LPM IPv6 route] (@ref rte_lpm6.h), [VXLAN] (@ref rte_vxlan.h) - **QoS**: [metering] (@ref rte_meter.h), [scheduler] (@ref rte_sched.h), [RED congestion] (@ref rte_red.h) - **hashes**: [hash] (@ref rte_hash.h), [jhash] (@ref rte_jhash.h), [thash] (@ref rte_thash.h), [FBK hash] (@ref rte_fbk_hash.h), [CRC hash] (@ref rte_hash_crc.h) - **classification** [reorder] (@ref rte_reorder.h), [distributor] (@ref rte_distributor.h), [EFD] (@ref rte_efd.h), [ACL] (@ref rte_acl.h), [member] (@ref rte_member.h), [flow classify] (@ref rte_flow_classify.h), [BPF] (@ref rte_bpf.h) - **containers**: [mbuf] (@ref rte_mbuf.h), [mbuf pool ops] (@ref rte_mbuf_pool_ops.h), [ring] (@ref rte_ring.h), [stack] (@ref rte_stack.h), [tailq] (@ref rte_tailq.h), [bitmap] (@ref rte_bitmap.h) - **packet framework**: * [port] (@ref rte_port.h): [ethdev] (@ref rte_port_ethdev.h), [ring] (@ref rte_port_ring.h), [frag] (@ref rte_port_frag.h), [reass] (@ref rte_port_ras.h), [sched] (@ref rte_port_sched.h), [kni] (@ref rte_port_kni.h), [src/sink] (@ref rte_port_source_sink.h) * [table] (@ref rte_table.h): [lpm IPv4] (@ref rte_table_lpm.h), [lpm IPv6] (@ref rte_table_lpm_ipv6.h), [ACL] (@ref rte_table_acl.h), [hash] (@ref rte_table_hash.h), [array] (@ref rte_table_array.h), [stub] (@ref rte_table_stub.h) * [pipeline] (@ref rte_pipeline.h) [port_in_action] (@ref rte_port_in_action.h) [table_action] (@ref rte_table_action.h) * [graph] (@ref rte_graph.h): [graph_worker] (@ref rte_graph_worker.h) * graph_nodes: [eth_node] (@ref rte_node_eth_api.h), [ip4_node] (@ref rte_node_ip4_api.h) - **basic**: [approx fraction] (@ref rte_approx.h), [random] (@ref rte_random.h), [config file] (@ref rte_cfgfile.h), [key/value args] (@ref rte_kvargs.h), [string] (@ref rte_string_fns.h) - **debug**: [jobstats] (@ref rte_jobstats.h), [telemetry] (@ref rte_telemetry.h), [pdump] (@ref rte_pdump.h), [hexdump] (@ref rte_hexdump.h), [debug] (@ref rte_debug.h), [log] (@ref rte_log.h), [errno] (@ref rte_errno.h), [trace] (@ref rte_trace.h), [trace_point] (@ref rte_trace_point.h) - **misc**: [EAL config] (@ref rte_eal.h), [common] (@ref rte_common.h), [experimental APIs] (@ref rte_compat.h), [ABI versioning] (@ref rte_function_versioning.h), [version] (@ref rte_version.h) ================================================ FILE: docs/api/doxy-api.conf.in ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright 2019-2020 Intel Corperation Inc. PROJECT_NAME = Pktgen-DPDK PROJECT_NUMBER = @VERSION@ USE_MDFILE_AS_MAINPAGE = @TOPDIR@/docs/api/doxy-api-index.md INPUT = @TOPDIR@/docs/api/doxy-api-index.md \ @TOPDIR@/lib/common \ @TOPDIR@/lib/cli \ @TOPDIR@/lib/lua \ @TOPDIR@/lib/plugin \ @TOPDIR@/lib/utils \ @TOPDIR@/lib/vec \ @TOPDIR@/app INPUT += @API_EXAMPLES@ FILE_PATTERNS = pktgen-*.h PREDEFINED = __DOXYGEN__ \ VFIO_PRESENT \ __attribute__(x)= OPTIMIZE_OUTPUT_FOR_C = YES ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES EXTRACT_STATIC = YES DISTRIBUTE_GROUP_DOC = YES HIDE_UNDOC_MEMBERS = YES HIDE_UNDOC_CLASSES = YES HIDE_SCOPE_NAMES = YES GENERATE_DEPRECATEDLIST = YES VERBATIM_HEADERS = NO ALPHABETICAL_INDEX = NO HTML_DYNAMIC_SECTIONS = YES SEARCHENGINE = YES SORT_MEMBER_DOCS = NO SOURCE_BROWSER = YES EXAMPLE_PATH = @TOPDIR@/examples EXAMPLE_PATTERNS = *.c EXAMPLE_RECURSIVE = YES OUTPUT_DIRECTORY = @OUTPUT@ STRIP_FROM_PATH = @STRIP_FROM_PATH@ GENERATE_HTML = YES HTML_OUTPUT = @HTML_OUTPUT@ GENERATE_LATEX = NO GENERATE_MAN = NO HAVE_DOT = NO ================================================ FILE: docs/api/doxy-html-custom.sh ================================================ #! /bin/sh -e # SPDX-License-Identifier: BSD-3-Clause # Copyright 2013 6WIND S.A. CSS=$1 # space between item and its comment echo 'dd td:first-child {padding-right: 2em;}' >> $CSS ================================================ FILE: docs/api/generate_doxygen.sh ================================================ #! /bin/sh -e # SPDX-License-Identifier: BSD-3-Clause # Copyright 2018 Luca Boccassi DOXYCONF=$1 OUTDIR=$2 SCRIPTCSS=$3 # run doxygen, capturing all the header files it processed doxygen "${DOXYCONF}" | tee doxygen.out echo "$OUTDIR: $(awk '/Preprocessing/ {printf("%s ", substr($2, 1, length($2) - 3))}' doxygen.out)" > $OUTDIR.d "${SCRIPTCSS}" "${OUTDIR}"/doxygen.css ================================================ FILE: docs/api/generate_examples.sh ================================================ #! /bin/sh -e # SPDX-License-Identifier: BSD-3-Clause # Copyright 2018 Luca Boccassi EXAMPLES_DIR=$1 API_EXAMPLES=$2 # generate a .d file including both C files and also build files, so we can # detect both file changes and file additions/deletions echo "$API_EXAMPLES: $(find ${EXAMPLES_DIR} -type f \( -name '*.c' -o -name 'meson.build' \) -printf '%p ' )" > ${API_EXAMPLES}.d exec > "${API_EXAMPLES}" printf '/**\n' printf '@page examples DPDK Example Programs\n\n' find "${EXAMPLES_DIR}" -type f -name '*.c' -printf '@example examples/%P\n' | LC_ALL=C sort printf '*/\n' ================================================ FILE: docs/api/meson.build ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2018 Luca Boccassi doxygen = find_program('doxygen', required: get_option('enable_docs')) if not doxygen.found() subdir_done() endif # due to the CSS customisation script, which needs to run on a file that # is in a subdirectory that is created at build time and thus it cannot # be an individual custom_target, we need to wrap the doxygen call in a # script to run the CSS modification afterwards generate_doxygen = find_program('generate_doxygen.sh') generate_examples = find_program('generate_examples.sh') generate_css = find_program('doxy-html-custom.sh') htmldir = join_paths(get_option('datadir'), 'docs', 'dpdk') # due to the following bug: https://github.com/mesonbuild/meson/issues/4107 # if install is set to true it will override build_by_default and it will # cause the target to always be built. If install were to be always set to # false it would be impossible to install the docs. # So use a configure option for now. example = custom_target('examples.dox', output: 'examples.dox', command: [generate_examples, join_paths(meson.project_source_root(), 'examples'), '@OUTPUT@'], depfile: 'examples.dox.d', install: get_option('enable_docs'), install_dir: htmldir, build_by_default: get_option('enable_docs')) cdata = configuration_data() cdata.set('VERSION', meson.project_version()) cdata.set('API_EXAMPLES', join_paths(meson.project_build_root(), 'docs', 'api', 'examples.dox')) cdata.set('OUTPUT', join_paths(meson.project_build_root(), 'docs', 'api')) cdata.set('HTML_OUTPUT', 'api') cdata.set('TOPDIR', meson.project_source_root()) cdata.set('STRIP_FROM_PATH', meson.project_source_root()) doxy_conf = configure_file(input: 'doxy-api.conf.in', output: 'doxy-api.conf', configuration: cdata) doxy_build = custom_target('doxygen', depends: example, input: doxy_conf, output: 'api', depfile: 'api.d', command: [generate_doxygen, '@INPUT@', '@OUTPUT@', generate_css], install: get_option('enable_docs'), install_dir: htmldir, build_by_default: get_option('enable_docs')) doc_targets += doxy_build doc_target_names += 'Doxygen_API' ================================================ FILE: docs/meson.build ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2019-2026> Intel Corporation doc_targets = [] doc_target_names = [] # Take out API doxygen for now. subdir('api') subdir('source') if doc_targets.length() == 0 message = 'No docs targets found' else message = 'Building docs:' endif run_target('docs', command: ['echo', message, doc_target_names], depends: doc_targets) ================================================ FILE: docs/source/changes.rst ================================================ :tocdepth: 1 .. _changes: Changes in Pktgen ================= This section previously embedded the raw `changelog.txt` file. The standalone changelog file has been removed per project direction. For historical release notes and detailed change history, please refer to the GitHub Releases page: https://github.com/pktgen/Pktgen-DPDK/releases Older point-in-time changes may also be visible in commit history using: ``` git log --oneline --decorate --graph ``` ================================================ FILE: docs/source/cli_design.rst ================================================ CLI design notes (lib/cli) ========================== This page documents the design and usage of the CLI library shipped in this repository under ``lib/cli``. For a developer-focused walkthrough (with examples and pointers to the implementation), see the in-tree document: * ``lib/cli/DESIGN.md`` When the optional ``myst_parser`` Sphinx extension is available, the Markdown source can also be included directly in the documentation build. Overview -------- The CLI is a small shell-like environment with: * A directory-like node tree (commands, files, aliases, directories) * History and line editing * TAB completion * Optional map-driven parsing for command variants Key ideas --------- * The active CLI instance is stored in TLS as ``this_cli``. * The command tree is made of ``struct cli_node`` entries. * Commands are implemented as ``int cmd(int argc, char **argv)`` callbacks. * Command variants can be selected by matching argv against a ``struct cli_map[]`` table via ``cli_mapping()``. Auto-complete ------------- TAB completion is primarily shell-like (commands and paths), but it can also be context-aware if a command has an associated map table registered via ``cli_register_cmd_map()`` / ``cli_register_cmd_maps()``. See also -------- * ``cli.rst`` and ``cli_lib.rst`` in this documentation set * ``lib/cli/DESIGN.md`` in the source tree ================================================ FILE: docs/source/commands.rst ================================================ .. _commands: ``*** Pktgen ***`` Copyright © \<2015-2026\>, Intel Corporation. README for setting up Pktgen with DPDK on Ubuntu 10.04 to 20.04 desktop, it should work on most Linux systems as long as the kernel has hugeTLB page support. Note: Tested with Ubuntu 18.04 and up to 20.04 versions Linux 3.5.0-25-generic #39-Ubuntu SMP Mon Feb 25 18:26:58 UTC 2013 x86_64 I am using Ubuntu 16.10 x86_64 (64 bit support) for running Pktgen-DPDK on a Crownpass Dual socket board running at 2.4GHz with 32GB of ram 16GB per socket. The current kernel version is 4.4.0-66-generic (as of 2018-04-01) support, but should work on just about any new Linux kernel version. Currently using as of 2020-05 Ubuntu 20.04 Kernel 5.6.0+ system. To get hugeTLB page support your Linux kernel must be at least 2.6.33 and in the DPDK documents it talks about how you can upgrade your Linux kernel. Here is another document on how to upgrade your Linux kernel. Ubuntu 10.04 is 2.6.32 by default so upgraded to kernel 2.6.34 using this HOWTO: http://usablesoftware.wordpress.com/2010/05/26/switch-to-a-newer-kernel-in-ubuntu-10-04/ The pktgen output display needs 132 columns and about 42 lines to display currently. I am using an xterm of 132x42, but you can have a larger display and maybe a bit smaller. If you are displaying more then 4-6 ports then you will need a wider display. Pktgen allows you to view a set of ports if they do not all fit on the screen at one time via the 'page' command. Type 'help' at the 'Pktgen>' prompt to see the complete Pktgen command line commands. Pktgen uses VT100 control codes or escape codes to display the screens, which means your terminal must support VT100. The Hyperterminal in windows is not going to work for Pktgen as it has a few problems with VT100 codes. Pktgen has a number of modes to send packets single, range, random, sequeue and PCAP modes. Each mode has its own set of packet buffers and you must configure each mode to work correctly. The single packet mode is the information displayed at startup screen or when using the 'page main or page 0' command. The other screens can be accessed using 'page seq|range|rnd|pcap|stats' command. The pktgen program as built can send up to 16 packets per port in a sequence and you can configure a port using the 'seq' pktgen command. A script file can be loaded from the shell command line via the -f option and you can 'load' a script file from within pktgen as well. In the BIOS make sure the HPET High Precision Event Timer is enabled. Also make sure hyper-threading is enabled. ** NOTE ** On a 10GB NIC if the transceivers are not attached the screen updates will go very slow. Pktgen command line directory format ==================================== -- Pktgen Ver: 3.2.x (DPDK 17.05.0-rc0) Powered by DPDK --------------- Show the commands inside the ``pktgen/bin`` directory:: Pktgen:/> ls [pktgen] [sbin] dpdk-version copyright Pktgen:/> ls pktgen/bin off on rate plugin dbg set pcap stp str stop start disable enable range theme page seq sequence ping4 port restart rst reset cls redisplay save load geom geometry clr clear help Showin the ``1s`` command at root:: Pktgen:/> ls [pktgen] [sbin] copyright Pktgen:/> ls sbin version echo script env path hugepages cmap more history quit screen.clear pwd cd ls rm mkdir chelp sleep delay The case of using ``ls -l`` in a subdirectory:: Pktgen:/> cd sbin Pktgen:/sbin/> ls -l version Command : Display version information echo Command : simple echo a string to the screen script Command : load and process cli command files env Command : Show/del/get/set environment variables path Command : display the execution path for commands hugepages Command : hugepages # display hugepage info cmap Command : cmap # display the core mapping more Command : more # display a file content history Command : history # display the current history quit Command : quit # quit the application screen.clear Command : screen.clear # clear the screen pwd Command : pwd # display current working directory cd Command : cd # change working directory ls Command : ls [-lr] # list current directory rm Command : remove a file or directory mkdir Command : create a directory chelp Command : CLI help - display information for DPDK sleep Command : delay a number of seconds delay Command : delay a number of milliseconds Show help using ``ls -l`` command in pktgen directory:: Pktgen:/sbin/> cd ../pktgen/bin Pktgen:/pktgen/bin/> ls -l off Alias : disable screen on Alias : enable screen rate Command : Rate setup commands plugin Command : Plugin a shared object file dbg Command : debug commands set Command : set a number of options pcap Command : pcap commands stp Alias : stop all str Alias : start all stop Command : stop features start Command : start features disable Command : disable features enable Command : enable features range Command : Range commands theme Command : Set, save, show the theme page Command : change page displays seq Alias : sequence sequence Command : sequence command ping4 Command : Send a ping packet for IPv4 port Command : Switch between ports restart Command : restart port rst Alias : reset all reset Command : reset pktgen configuration cls Alias : redisplay redisplay Command : redisplay the screen save Command : save the current state load Command : load command file geom Alias : geometry geometry Command : show the screen geometry clr Alias : clear all stats clear Command : clear stats, ... help Command : help command Pktgen:/pktgen/bin/> Runtime Options and Commands ============================ While the ``pktgen`` application is running you will see a command prompt as follows:: Pktgen:/> From this you can get help or issue runtime commands:: Pktgen:/> help set value - Set a few port values save - Save a configuration file using the filename load - Load a command/script file from the given path ... The ``page`` commands to show different screens:: ** Pktgen Help Information ** page [0-7] - Show the port pages or configuration or sequence page page main - Display page zero page range - Display the range packet page page cpu - Display the CPU page page pcap - Display the pcap page page cpu - Display some information about the CPU system page next - Display next page of PCAP packets. page sequence | seq - sequence will display a set of packets for a given port Note: use the 'port ' to display a new port sequence page rnd - Display the random bitfields to packets for a given port Note: use the 'port ' to display a new port sequence page log - Display the log messages page page latency - Display the latency page page stats - Display physical ports stats for all ports page xstats - Display port XSTATS values page rate - Display Rate Pacing values List of the ``enable/disable`` commands:: enable|disable process - Enable or Disable processing of ARP/ICMP/IPv4/IPv6 packets enable|disable mpls - Enable/disable sending MPLS entry in packets enable|disable qinq - Enable/disable sending Q-in-Q header in packets enable|disable gre - Enable/disable GRE support enable|disable gre_eth - Enable/disable GRE with Ethernet frame payload enable|disable vlan - Enable/disable VLAN tagging enable|disable garp - Enable or Disable Gratuitous ARP packet processing enable|disable random - Enable/disable Random packet support enable|disable latency - Enable/disable latency testing enable|disable pcap - Enable or Disable sending pcap packets on a portlist enable|disable blink - Blink LED on port(s) enable|disable rx_tap - Enable/Disable RX Tap support enable|disable tx_tap - Enable/Disable TX Tap support enable|disable icmp - Enable/Disable sending ICMP packets enable|disable range - Enable or Disable the given portlist for sending a range of packets enable|disable capture - Enable/disable packet capturing on a portlist, disable to save capture Disable capture on a port to save the data into the currect working directory. enable|disable bonding - Enable call TX with zero packets for bonding driver enable|disable vxlan - Send VxLAN packets enable|disable rate - Enable/Disable Rate Packing on given ports enable|disable mac_from_arp - Enable/disable MAC address from ARP packet enable|disable screen - Enable/disable updating the screen and unlock/lock window off - screen off shortcut on - screen on shortcut List of the ``set`` commands:: note: - a list of ports (no spaces) e.g. 2,4,6-9,12 or the word 'all' set count - number of packets to transmit set size - size of the packet to transmit set rate - Packet rate in percentage set txburst - number of packets in a Tx burst set rxburst - number of packets in a Rx burst set tx_cycles - DEBUG to set the number of cycles per TX burst set sport - Source port number for TCP set dport - Destination port number for TCP set ttl - Set the TTL value for the single port more set seq_cnt|seqcnt|seqCnt - Set the number of packet in the sequence to send [0-16] set prime - Set the number of packets to send on prime command set dump - Dump the next 1-32 received packets to the screen Dumped packets are in the log, use 'page log' to view set vlan|vlanid - Set the VLAN ID value for the portlist set jitter - Set the jitter threshold in micro-seconds set src|dst mac - Set MAC addresses 00:11:22:33:44:55 or 0011:2233:4455 format set type ipv4|ipv6|vlan|arp - Set the packet type to IPv4 or IPv6 or VLAN set proto udp|tcp|icmp - Set the packet protocol to UDP or TCP or ICMP per port set pattern - Set the fill pattern type type - abc - Default pattern of abc string none - No fill pattern, maybe random data zero - Fill of zero bytes user - User supplied string of max 16 bytes set user pattern - A 16 byte string, must set 'pattern user' command set [src|dst] ip ipaddr - Set IP addresses, Source must include network mask e.g. 10.1.2.3/24 set qinqids - Set the Q-in-Q ID's for the portlist set rnd - Set random mask for all transmitted packets from portlist idx: random mask index slot off: offset in bytes to apply mask value mask: up to 32 bit long mask specification (empty to disable): 0: bit will be 0 1: bit will be 1 .: bit will be ignored (original value is retained) X: bit will get random value set cos - Set the CoS value for the portlist set tos - Set the ToS value for the portlist set vxlan - Set the vxlan values set ports_per_page - Set ports per page value 1 - 6 The ``range`` commands:: -- Setup the packet range values -- note: SMMI = start|min|max|inc (start, minimum, maximum, increment) range src|dst mac - Set destination/source MAC address e.g: range 0 src mac start 00:00:00:00:00:00 range 0 dst mac max 00:12:34:56:78:90 or range 0 src mac 00:00:00:00:00:00 00:00:00:00:00:00 00:12:34:56:78:90 00:00:00:01:01:01 range src|dst ip - Set source IP start address e.g: range 0 dst ip start 0.0.0.0 range 0 dst ip min 0.0.0.0 range 0 dst ip max 1.2.3.4 range 0 dst ip inc 0.0.1.0 or range 0 dst ip 0.0.0.0 0.0.0.0 1.2.3.4 0.0.1.0 range proto tcp|udp - Set the IP protocol type range src|dst port - Set UDP/TCP source/dest port number or range src|dst port range vlan - Set vlan id start address or range vlan range size - Set pkt size start address or range size range teid - Set TEID value or range teid range mpls entry - Set MPLS entry value range qinq index - Set QinQ index values range gre key - Set GRE key value range cos - Set cos value range tos - Set tos value The ``sequence`` commands:: sequence dst src dst src sport dport ipv4|ipv6 udp|tcp|icmp vlan size [teid ] sequence ipv4|ipv6 udp|tcp|icmp [] sequence cos tos sequence vxlan gid vid - Set the sequence packet information, make sure the src-IP has the netmask value eg 1.2.3.4/24 The ``pcap`` commands:: pcap show - Show PCAP information pcap index - Move the PCAP file index to the given packet number, 0 - rewind, -1 - end of file pcap filter - PCAP filter string to filter packets on receive The ``start|stop`` commands:: start - Start transmitting packets stop - Stop transmitting packets stp - Stop all ports from transmitting str - Start all ports transmitting start prime - Transmit packets on each port listed. See set prime command above start arp - Send a ARP type packet type - request | gratuitous | req | grat The ``debug`` commands:: dbg l2p - Dump out internal lcore to port mapping dbg tx_dbg - Enable tx debug output dbg mempool - Dump out the mempool info for a given type dbg pdump - Hex dump the first packet to be sent, single packet mode only dbg memzone - List all of the current memzones dbg memseg - List all of the current memsegs dbg hexdump - hex dump memory at given address dbg break - break into the debugger dbg memcpy [loop-cnt KBytes] - run a memcpy test The odd or special commands:: save - Save a configuration file using the filename load - Load a command/script file from the given path script - Execute the Lua script code in file (www.lua.org). (if Lua is enabled) lua 'lua string' - Execute the Lua code in the string needs quotes (if Lua is enabled) clear stats - Clear the statistics clr - Clear all Statistices reset - Reset the configuration the ports to the default rst - Reset the configuration for all ports ports per page [1-6] - Set the number of ports displayed per page port - Sets the sequence packets to display for a given port restart - Restart or stop a ethernet port and restart ping4 - Send a IPv4 ICMP echo request on the given portlist The ``theme`` commands:: theme - Set color for item with fg/bg color and attribute value theme show - List the item strings, colors and attributes to the items theme save - Save the current color theme to a file The ``plugin`` commands:: plugin - Show the plugins currently installed plugin load - Load a plugin file plugin load - Load a plugin file at path plugin rm|delete - Remove or delete a plugin The ``rate` commands for packet pacing:: rate count - number of packets to transmit rate size - size of the packet to transmit rate rate - Packet rate in percentage rate burst - number of packets in a burst rate sport - Source port number for TCP rate dport - Destination port number for TCP rate ttl - Set the TTL value for the single port more rate src|dst mac - Set MAC addresses 00:11:22:33:44:55 or 0011:2233:4455 format rate type ipv4|ipv6|vlan|arp - Set the packet type to IPv4 or IPv6 or VLAN rate proto udp|tcp|icmp - Set the packet protocol to UDP or TCP or ICMP per port rate [src|dst] ip ipaddr - Set IP addresses, Source must include network mask e.g. 10.1.2.3/24 rate fps - Set the frame per second value e.g. 60fps rate lines - Set the number of video lines, e.g. 720 rate pixels - Set the number of pixels per line, e.g. 1280 rate color bits - Set the color bit size 8, 16, 24, ... rate payload size - Set the payload size rate overhead - Set the packet overhead + payload = total packet size The flags:: Flags: P------------------ - Promiscuous mode enabled E - ICMP Echo enabled B - Bonding enabled LACP 802.3ad I - Process packets on input enabled * - Using TAP interface for this port can be [-rt*] g - Process GARP packets C - Capture received packets ------ - Modes Single, pcap, sequence, latency, random, Rate ------ - Modes VLAN, VxLAN, MPLS, QnQ, GRE IPv4, GRE ETH Notes: - Use enable|disable or on|off to set the state. - a list of ports (no spaces) as 2,4,6-9,12 or 3-5,8 or 5 or the word 'all' Colors best seen on a black background for now Several commands take common arguments such as: * ``portlist``: A list of ports such as ``2,4,6-9,12`` or the word ``all``. * ``state``: This is usually ``on`` or ``off`` but will also accept ``enable`` or ``disable``. For example:: Pktgen:/> set all seq_cnt 1 The ``set`` command can also be used to set the MAC address with a format like ``00:11:22:33:44:55`` or ``0011:2233:4455``:: set src|dst mac etheraddr The ``set`` command can also be used to set IP addresses:: set src|dst ip ipaddr seq --- The ``seq`` command sets the flow parameters for a sequence of packets:: seq dst-Mac src-Mac dst-IP src-IP sport dport ipv4|ipv6|vlan udp|tcp|icmp vid pktsize Where the arguments are: * ````: The packet sequence number. * ````: A portlist as explained above. * ``dst-Mac``: The destination MAC address. * ``src-Mac``: The source MAC address. * ``dst-IP``: The destination IP address. * ``src-IP``: The source IP address. Make sure the src-IP has the netmask value such as ``1.2.3.4/24``. * ``sport``: The source port. * ``dport``: The destination port. * ``IP``: The IP layer. One of ``ipv4|ipv6|vlan``. * ``Transport``: The transport. One of ``udp|tcp|icmp``. * ``vid``: The VLAN ID. * ``pktsize``: The packet size. save ---- The ``save`` command saves the current configuration of a file:: save load ---- The ``load`` command loads a configuration from a file:: load The is most often used with a configuration file written with the ``save`` command, see above. ports per page -------------- The ``ports per page`` (ports per page) command sets the number of ports displayed per page:: ports per page [1-6] script ------ The ``script`` command execute the Lua code in specified file:: script See :ref:`scripts`. pages ----- The Random or rnd page. :: Port 0 Copyright(c) <2010-2026>, Intel Corporation Index Offset Act? Mask [0 = 0 bit, 1 = 1 bit, X = random bit, . = ignore] 0 0 No 00000000 00000000 00000000 00000000 1 0 No 00000000 00000000 00000000 00000000 2 0 No 00000000 00000000 00000000 00000000 3 0 No 00000000 00000000 00000000 00000000 4 0 No 00000000 00000000 00000000 00000000 5 0 No 00000000 00000000 00000000 00000000 6 0 No 00000000 00000000 00000000 00000000 7 0 No 00000000 00000000 00000000 00000000 8 0 No 00000000 00000000 00000000 00000000 9 0 No 00000000 00000000 00000000 00000000 10 0 No 00000000 00000000 00000000 00000000 11 0 No 00000000 00000000 00000000 00000000 12 0 No 00000000 00000000 00000000 00000000 13 0 No 00000000 00000000 00000000 00000000 14 0 No 00000000 00000000 00000000 00000000 15 0 No 00000000 00000000 00000000 00000000 16 0 No 00000000 00000000 00000000 00000000 17 0 No 00000000 00000000 00000000 00000000 18 0 No 00000000 00000000 00000000 00000000 19 0 No 00000000 00000000 00000000 00000000 20 0 No 00000000 00000000 00000000 00000000 21 0 No 00000000 00000000 00000000 00000000 22 0 No 00000000 00000000 00000000 00000000 23 0 No 00000000 00000000 00000000 00000000 24 0 No 00000000 00000000 00000000 00000000 25 0 No 00000000 00000000 00000000 00000000 26 0 No 00000000 00000000 00000000 00000000 27 0 No 00000000 00000000 00000000 00000000 28 0 No 00000000 00000000 00000000 00000000 29 0 No 00000000 00000000 00000000 00000000 30 0 No 00000000 00000000 00000000 00000000 31 0 No 00000000 00000000 00000000 00000000 -- Pktgen Ver: 3.2.4 (DPDK 17.05.0-rc0) Powered by DPDK ----- The sequence or seq page. :: Copyright(c) <2010-2026>, Intel Corporation Port : 0, Sequence Count: 8 of 16 GTPu * Seq: Dst MAC Src MAC Dst IP Src IP Port S/D Protocol:VLAN Size TEID * 0: 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:d8 192.168.1.1 192.168.0.1/24 1234/5678 IPv4/TCP:0001 64 0 * 1: 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:d8 192.168.1.1 192.168.0.1/24 1234/5678 IPv4/TCP:0001 64 0 * 2: 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:d8 192.168.1.1 192.168.0.1/24 1234/5678 IPv4/TCP:0001 64 0 * 3: 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:d8 192.168.1.1 192.168.0.1/24 1234/5678 IPv4/TCP:0001 64 0 * 4: 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:d8 192.168.1.1 192.168.0.1/24 1234/5678 IPv4/TCP:0001 64 0 * 5: 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:d8 192.168.1.1 192.168.0.1/24 1234/5678 IPv4/TCP:0001 64 0 * 6: 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:d8 192.168.1.1 192.168.0.1/24 1234/5678 IPv4/TCP:0001 64 0 * 7: 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:d8 192.168.1.1 192.168.0.1/24 1234/5678 IPv4/TCP:0001 64 0 -- Pktgen Ver: 3.2.4 (DPDK 17.05.0-rc0) Powered by DPDK --------------- The CPU information page. :: Copyright(c) <2010-2026>, Intel Corporation Kernel: Linux rkwiles-DESK1.intel.com 4.4.0-66-generic #87-Ubuntu SMP Fri Mar 3 15:29:05 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux Model Name: Intel(R) Xeon(R) CPU E5-2699 v3 @ 2.30GHz CPU Speed : 1201.031 Cache Size: 46080 KB CPU Flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm xsaveopt cqm_llc cqm_occup_llc dtherm ida arat pln pts 2 sockets, 18 cores per socket and 2 threads per core. Socket : 0 1 Core 0 : [ 0,36] [18,54] Core 1 : [ 1,37] [19,55] Core 2 : [ 2,38] [20,56] Core 3 : [ 3,39] [21,57] Core 4 : [ 4,40] [22,58] Core 5 : [ 5,41] [23,59] Core 6 : [ 6,42] [24,60] Core 7 : [ 7,43] [25,61] Core 8 : [ 8,44] [26,62] Core 9 : [ 9,45] [27,63] Core 10 : [10,46] [28,64] Core 11 : [11,47] [29,65] Core 12 : [12,48] [30,66] Core 13 : [13,49] [31,67] Core 14 : [14,50] [32,68] Core 15 : [15,51] [33,69] Core 16 : [16,52] [34,70] Core 17 : [17,53] [35,71] The latency page. :: -- Ports 0-3 of 8
Copyright(c) <2010-2026>, Intel Corporation Flags:Port : P----S---------:0 P--------------:1 P--------------:2 P--------------:3 Link State : ----TotalRate---- Pkts/s Max/Rx : 0/0 0/0 0/0 0/0 0/0 Max/Tx : 0/0 0/0 0/0 0/0 0/0 MBits/s Rx/Tx : 0/0 0/0 0/0 0/0 0/0 : Latency usec : 0 0 0 0 Jitter Threshold : 50 50 50 50 Jitter count : 0 0 0 0 Total Rx pkts : 0 0 0 0 Jitter percent : 0 0 0 0 : Pattern Type : abcd... abcd... abcd... abcd... Tx Count/% Rate : Forever /100% Forever /100% Forever /100% Forever /100% PktSize/Rx:Tx Burst: 64 / 32: 64 64 / 32 64 / 32 64 / 32 Src/Dest Port : 1234 / 5678 1234 / 5678 1234 / 5678 1234 / 5678 Pkt Type:VLAN ID : IPv4 / TCP:0001 IPv4 / TCP:0001 IPv4 / TCP:0001 IPv4 / TCP:0001 Dst IP Address : 192.168.1.1 192.168.0.1 192.168.3.1 192.168.2.1 Src IP Address : 192.168.0.1/24 192.168.1.1/24 192.168.2.1/24 192.168.3.1/24 Dst MAC Address : 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:d8 3c:fd:fe:9c:5c:db 3c:fd:fe:9c:5c:da Src MAC Address : 3c:fd:fe:9c:5c:d8 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:da 3c:fd:fe:9c:5c:db VendID/PCI Addr : 8086:1572/04:00.0 8086:1572/04:00.1 8086:1572/04:00.2 8086:1572/04:00.3 -- Pktgen Ver: 3.2.4 (DPDK 17.05.0-rc0) Powered by DPDK --------------- The config or cfg page. :: Copyright(c) <2010-2026>, Intel Corporation 2 sockets, 18 cores, 2 threads Socket : 0 1 Port description Core 0 : [ 0,36] [18,54] 0000:04:00.0 : Intel Corporation X710 for 10GbE SFP+ (rev 01) Core 1 : [ 1,37] [19,55] 0000:04:00.1 : Intel Corporation X710 for 10GbE SFP+ (rev 01) Core 2 : [ 2,38] [20,56] 0000:04:00.2 : Intel Corporation X710 for 10GbE SFP+ (rev 01) Core 3 : [ 3,39] [21,57] 0000:04:00.3 : Intel Corporation X710 for 10GbE SFP+ (rev 01) Core 4 : [ 4,40] [22,58] 0000:05:00.0 : Intel Corporation I350 Gigabit Network Connection (rev 01) Core 5 : [ 5,41] [23,59] 0000:05:00.1 : Intel Corporation I350 Gigabit Network Connection (rev 01) Core 6 : [ 6,42] [24,60] 0000:81:00.0 : Intel Corporation X710 for 10GbE SFP+ (rev 01) Core 7 : [ 7,43] [25,61] 0000:81:00.1 : Intel Corporation X710 for 10GbE SFP+ (rev 01) Core 8 : [ 8,44] [26,62] 0000:81:00.2 : Intel Corporation X710 for 10GbE SFP+ (rev 01) Core 9 : [ 9,45] [27,63] 0000:81:00.3 : Intel Corporation X710 for 10GbE SFP+ (rev 01) Core 10 : [10,46] [28,64] 0000:82:00.0 : Intel Corporation XL710 for 40GbE QSFP+ (rev 02) Core 11 : [11,47] [29,65] 0000:83:00.0 : Intel Corporation XL710 for 40GbE QSFP+ (rev 02) Core 12 : [12,48] [30,66] Core 13 : [13,49] [31,67] Core 14 : [14,50] [32,68] Core 15 : [15,51] [33,69] Core 16 : [16,52] [34,70] Core 17 : [17,53] [35,71] -- Pktgen Ver: 3.2.4 (DPDK 17.05.0-rc0) Powered by DPDK --------------- Here is the ``page range`` screen. :: Port # Port-0 Port-1 Port-2 Port-3 dst.ip : 192.168.1.1 192.168.2.1 192.168.3.1 192.168.4.1 inc : 0.0.0.1 0.0.0.1 0.0.0.1 0.0.0.1 min : 192.168.1.1 192.168.2.1 192.168.3.1 192.168.4.1 max : 192.168.1.254 192.168.2.254 192.168.3.254 192.168.4.254 : src.ip : 192.168.0.1 192.168.1.1 192.168.2.1 192.168.3.1 inc : 0.0.0.0 0.0.0.0 0.0.0.0 0.0.0.0 min : 192.168.0.1 192.168.1.1 192.168.2.1 192.168.3.1 max : 192.168.0.254 192.168.1.254 192.168.2.254 192.168.3.254 : ip_proto : TCP TCP TCP TCP : dst.port / inc : 0/ 1 256/ 1 512/ 1 768/ 1 min / max : 0/ 254 256/ 510 512/ 766 768/ 1022 : src.port / inc : 0/ 1 256/ 1 512/ 1 768/ 1 min / max : 0/ 254 256/ 510 512/ 766 768/ 1022 : vlan.id / inc : 1/ 0 1/ 0 1/ 0 1/ 0 min / max : 1/4095 1/4095 1/4095 1/4095 : pkt.size / inc : 64/ 0 64/ 0 64/ 0 64/ 0 min / max : 64/1518 64/1518 64/1518 64/1518 : dst.mac : 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:d8 3c:fd:fe:9c:5c:db 3c:fd:fe:9c:5c:da inc : 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 min : 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 max : 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 : src.mac : 3c:fd:fe:9c:5c:d8 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:da 3c:fd:fe:9c:5c:db inc : 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 min : 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 max : 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 00:00:00:00:00:00 : gtpu.teid / inc : 0/ 0 0/ 0 0/ 0 0/ 0 min / max : 0/ 0 0/ 0 0/ 0 0/ 0 -- Pktgen Ver: 3.2.4 (DPDK 17.05.0-rc0) Powered by DPDK --------------- Pktgen:/> s ================================================ FILE: docs/source/conf.py ================================================ # -*- coding: utf-8 -*- from sphinx.highlighting import PygmentsBridge from pygments.formatters.latex import LatexFormatter project = 'Pktgen' copyright = '2010-2024, Keith Wiles' # Version of Pktgen-DPDK version = '23.06.1' release = version extensions = [] source_suffix = { '.rst': 'restructuredtext', } # Optional Markdown support for developer-friendly docs. # If myst_parser is installed, Sphinx can render .md sources referenced by # toctrees (e.g. docs/QUICKSTART.md and lib/cli/DESIGN.md). try: import myst_parser # noqa: F401 except Exception: # pragma: no cover pass else: extensions.append('myst_parser') source_suffix['.md'] = 'markdown' initial_doc = 'index' pygments_style = 'sphinx' html_theme = 'default' html_add_permalinks = '' htmlhelp_basename = 'Pktgendoc' latex_documents = [ ('index', 'pktgen.tex', 'Pktgen-DPDK Documentation', 'Keith Wiles', 'manual'), ] latex_preamble = """ \\usepackage{upquote} \\usepackage[utf8]{inputenc} \\usepackage{DejaVuSansMono} \\usepackage[T1]{fontenc} \\usepackage{helvet} \\renewcommand{\\familydefault}{\\sfdefault} \\RecustomVerbatimEnvironment{Verbatim}{Verbatim}{xleftmargin=5mm} """ latex_elements = { 'papersize': 'a4paper', 'pointsize': '11pt', 'preamble': latex_preamble, } man_pages = [ ('index', 'pktgen', 'Pktgen Documentation', ['Keith Wiles'], 1) ] texinfo_documents = [ ('index', 'Pktgen', 'Pktgen Documentation', 'Keith Wiles', 'Pktgen', 'One line description of project.', 'Miscellaneous'), ] class CustomLatexFormatter(LatexFormatter): def __init__(self, **options): super(CustomLatexFormatter, self).__init__(**options) self.verboptions = r"formatcom=\footnotesize" PygmentsBridge.latex_formatter = CustomLatexFormatter ================================================ FILE: docs/source/contents.rst ================================================ The Pktgen Application ====================== **Pktgen**, (*Packet* *Gen*-erator) is a software based traffic generator powered by the DPDK fast packet processing framework. Some of the features of Pktgen are: * It is capable of generating 10Gbit wire rate traffic with 64 byte frames. * It can act as a transmitter or receiver at line rate. * It has a runtime environment to configure, and start and stop traffic flows. * It can display real time metrics for a number of ports. * It can generate packets in sequence by iterating source or destination MAC, IP addresses or ports. * It can handle packets with UDP, TCP, ARP, ICMP, GRE, MPLS and Queue-in-Queue. * It can be controlled remotely over a TCP connection. * It is configurable via Lua and can run command scripts to set up repeatable test cases. * The software is fully available under a BSD licence. Pktgen was created 2010 by Keith Wiles @ windriver.com, now at intel.com .. only:: html See the sections below for more details. Contents: .. toctree:: :maxdepth: 1 getting_started.rst running.rst usage_eal.rst usage_pktgen.rst commands.rst cli.rst cli_lib.rst cli_design.rst scripts.rst lua.rst socket.rst changes.rst copyright.rst license.rst ================================================ FILE: docs/source/copyright.rst ================================================ .. _copyright: Copyright and License ===================== **Copyright(c) <2010-2026>, Intel Corporation All rights reserved.** Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Intel Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SPDX-License-Identifier: BSD-3-Clause Pktgen: Created 2010 by Keith Wiles @ windriver.com, now at intel.com ================================================ FILE: docs/source/custom.css ================================================ /* Override readthedocs theme */ /* Spacing before a list item must be bigger than spacing inside the item. * Complex list items start with a p.first element. */ .section li > .first { margin-top: 18px; } ================================================ FILE: docs/source/getting_started.rst ================================================ .. _getting_started: Getting Started with Pktgen =========================== This section contains instructions on how to get up and running with `DPDK `_ and the ``pktgen`` traffic generator application. These instructions relate to setting up DPDK and ``pktgen`` on an Ubuntu desktop system. However, the should work on any recent Linux system with kernel support for hugeTLB/hugepages. System requirements ------------------- The main system requirement is that the DPDK packet processing framework is supported. The `DPDK Linux Getting Started Guide `_ has a section on the `System Requirements `_ that explains the BIOS, System and Toolchain requirements to compile and run a DPDK based application such as ``pktgen``. Ensure that your system meets those requirements before proceeding. You will also need a `DPDK supported NIC `_. The current version of ``pktgen`` was developed and tested using Ubuntu 13.10 x86_64, kernel version 3.5.0-25, on a Westmere Dual socket board running at 2.4GHz with 12GB of ram 6GB per socket. Setting up hugeTLB/hugepage support ----------------------------------- To get hugeTLB/hugepage support your Linux kernel must be at least 2.6.33 and the ``HUGETLBFS`` kernel option must be enabled. The DPDK Linux Getting Started Guide has a section on the `Use of Hugepages in the Linux Environment `_. Once you have made the required changed make sure you have HUGE TLB support in the kernel with the following commands:: $ grep -i huge /boot/config-2.6.35-24-generic CONFIG_HUGETLBFS=y CONFIG_HUGETLB_PAGE=y $ grep -i huge /proc/meminfo HugePages_Total: 128 HugePages_Free: 128 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB The values in Total and Free may be different depending on your system. You will need to edit the ``/etc/sysctl.conf`` file to setup the hugepages size:: $ sudo vi /etc/sysctl.conf Add to the bottom of the file: vm.nr_hugepages=256 You can configure the ``vm.nr_hugepages=256`` as required. In some cases making it too small will effect the performance of pktgen or cause it to terminate on startup. You will also need to edit the ``/etc/fstab`` file to mount the hugepages at startup:: $ sudo vi /etc/fstab Add to the bottom of the file: huge /mnt/huge hugetlbfs defaults 0 0 $ sudo mkdir /mnt/huge $ sudo chmod 777 /mnt/huge You should also reboot your machine as the huge pages must be setup just after boot to make sure there is enough contiguous memory for the 2MB pages. .. Note:: If you start an application that makes extensive use of hugepages, such as Eclipse or WR Workbench, before starting ``pktgen`` for the first time after reboot, ``pktgen`` may fail to load. In this case you should close the other application that is using hugepages. BIOS settings ------------- In the BIOS make sure that the HPET High Precision Event Timer is enabled. Also make sure hyper-threading is enabled. See the DPDK documentation on `enabling additional BIOS functionality `_ for more details. Terminal display ---------------- The ``pktgen`` output display requires 132 columns and about 42 lines to display correctly. The author uses an xterm of 132x42, but you can also have a larger display and maybe a bit smaller. If you are displaying more then 4-6 ports then you will need a wider display. Pktgen allows you to view a set ports via the ``page`` runtime command if they do not all fit on the screen at one time, see :ref:`commands`. Pktgen uses VT100 control codes display its output screens, which means your terminal must support VT100. It is also best to set your terminal background to black when working with the default ``pktgen`` color scheme. Get the source code ------------------- Pktgen requires the DPDK source code to build. The main ``dpdk`` and ``pktgen`` git repositories are hosted on `dpdk.org `_. The ``dpdk`` code can be cloned as follows:: git clone git://dpdk.org/dpdk # or: git clone http://dpdk.org/git/dpdk The ``pktgen`` code can be cloned as follows:: git clone git://dpdk.org/apps/pktgen-dpdk # or: git clone http://dpdk.org/git/apps/pktgen-dpdk In the instructions below the repository close directories are referred to as ``DPDKInstallDir`` and ``PktgenInstallDir``. You will also require the Linux kernel headers to allow DPDK to build its kernel modules. On Ubuntu you can install them as follows (where the version matches the kernel version):: $ sudo apt-get install linux-headers-3.5.0-32-generic DPDK can also work with a ``libpcap`` driver which is sometimes useful for testing without a real NIC or for low speed packet capture. Install the ``libpcap`` development libs using your package manage. For example:: $ sudo apt-get install libpcap-dev Build DPDK and Pktgen --------------------- Set up the environmental variables required by DPDK:: export RTE_SDK= export RTE_TARGET=x86_64-native-linux-gcc or export RTE_TARGET=x86_64-native-linuxapp-gcc # or use clang if you have it installed: export RTE_TARGET=x86_64-native-linux-clang or export RTE_TARGET=x86_64-native-linuxapp-clang Create the DPDK build tree:: $ cd $RTE_SDK $ make install T=x86_64-native-linux-gcc or $ make install T=x86_64-native-linuxapp-gcc This above command will create the `x86_64-pktgen-linux-gcc` directory in the top level of the ``$RTE_SDK`` directory. It will also build the basic DPDK libraries, kernel modules and build tree. Pktgen can then be built as follows:: $ cd $ make Setting up your environment --------------------------- In the ``PktgenInstallDir``/tools level directory there is ``run.py`` script, which should be run once per boot with the -s option to setup the ports. The same configuration file is also used to run pktgen by removing the -s option. .. Note:: The run.py script will do the sudo to root internally, which means the ``sudo`` is not required. The script contains the commands required to set up the environment:: $ cd /tools $ ./run.py -s default # setup system using the cfg/default.cfg file The run.py script is a python script and tries to configure the system to run a DPDK application. You will probably have to change the configuration files to match your system. To run pktgen with the default.cfg configuration:: $ cd /tools $ run.py default The ``run.py`` command use python data files to configure setup and run pktgen. The configuration files are located in the ``PktgenInstallDir``/cfg directory. These files allow for setup and running pktgen and can be configured to match you system or new configuration files can be created. Here is the default.cfg file:: # Setup configuration setup = { 'devices': [ '81:00.0 81:00.1 81:00.2 81:00.3', '85:00.0 85:00.1 85:00.2 85:00.3', '83:00.0' ], 'opts': [ '-b igb_uio' ] } # Run command and options run = { 'dpdk': [ '-l 1,1-5,10-13', '-n 4', '--proc-type auto', '--log-level 7', '--socket-mem 2048,2048', '--file-prefix pg' ], 'blocklist': [ #'-b 81:00.0 -b 81:00.1 -b 81:00.2 -b 81:00.3', #'-b 85:00.0 -b 85:00.1 -b 85:00.2 -b 85:00.3', '-b 81:00.0 -b 81:00.1', '-b 85:00.0 -b 85:00.1', '-b 83:00.0' ], 'pktgen': [ '-T', '-P', '-m [2:3].0', '-m [4:5].1', '-m [10:11].2', '-m [12:13].3', ], 'misc': [ '-f themes/black-yellow.theme' ] } We have two sections one for setup and the other for running pktgen. The ``modprobe uio`` command, in the setup script, loads the UIO support module into the kernel as well as loafing the igb-uio.ko module. The two echo commands, in the setup script, set up the huge pages for a two socket system. If you only have a single socket system then remove the second echo command. The last command in the script is used to display the hugepage setup. You may also wish to edit your ``.bashrc``, ``.profile`` or ``.cshrc`` files to permanently add the environment variables that you set up above:: export RTE_SDK= export RTE_TARGET=x86_64-native-linux-gcc or export RTE_TARGET=x86_64-native-linux-appgcc Running the application ----------------------- Once the above steps have been completed and the ``pktgen`` application has been compiled you can run it using the commands shown in the :ref:`running` section. ================================================ FILE: docs/source/index.rst ================================================ The Pktgen Application ====================== **Pktgen**, (*Packet* *Gen*-erator) is a software based traffic generator powered by the DPDK fast packet processing framework. Some of the features of Pktgen are: * It is capable of generating 10Gbit wire rate traffic with 64 byte frames. * It can act as a transmitter or receiver at line rate. * It has a runtime environment to configure, and start and stop traffic flows. * It can display real time metrics for a number of ports. * It can generate packets in sequence by iterating source or destination MAC, IP addresses or ports. * It can handle packets with UDP, TCP, ARP, ICMP, GRE, MPLS and Queue-in-Queue. * It can be controlled remotely over a TCP connection. * It is configurable via Lua and can run command scripts to set up repeatable test cases. * The software is fully available under a BSD licence. Pktgen was created 2010 by Keith Wiles @ windriver.com, now at intel.com .. only:: html See the sections below for more details. Contents: .. toctree:: :maxdepth: 1 ../../docs/QUICKSTART getting_started.rst running.rst usage_eal.rst usage_pktgen.rst commands.rst cli.rst cli_lib.rst cli_design.rst scripts.rst lua.rst socket.rst changes.rst copyright.rst license.rst ================================================ FILE: docs/source/license.rst ================================================ .. _license: Third Party License Notices =========================== This document contains third party intellectual property (IP) notices for the Intel Corp® Packet Generation distribution. Certain licenses and license notices may appear in other parts of the product distribution in accordance with the license requirements. "Intel Corp", is a registered trademark of Intel Corp. All other third-party trademarks are the property of their respective owners. Lua --- Lua Version 5.3.0. Lua is a lightweight, embeddable scripting language. Lua combines simple procedural syntax with powerful data description constructs based on associative arrays and extensible semantics. Lua is dynamically typed, runs by interpreting bytecode for a register-based virtual machine, and has automatic memory management with incremental garbage collection, making it ideal for configuration, scripting, and rapid prototyping. **Copyright(c) 1994-2015 Lua.org, PUC-Rio.** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and tom permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Warranty Disclaimer & Limitation of Liability --------------------------------------------- **OPEN SOURCE SOFTWARE**: "Open Source Software" is software that may be delivered with the Software and is licensed in accordance with open source licenses, including, but not limited to, any software licensed under Academic Free License, Apache Software License, Artistic License, BSD License, GNU General Public License, GNU Library General Public License, GNU Lesser Public License, Mozilla Public License, Python License or any other similar license. **DISCLAIMER OF WARRANTIES**: WIND RIVER AND ITS LICENSORS DISCLAIM ALL WARRANTIES, EXPRESS, IMPLIED AND STATUTORY INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT OF THIRD PARTY RIGHTS WITH RESPECT TO OPEN SOURCE SOFTWARE. NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY WIND RIVER, ITS DEALERS, DISTRIBUTORS, AGENTS OR EMPLOYEES SHALL IN ANY WAY INCREASE THE SCOPE OF THIS WARRANTY. Some jurisdictions do not allow the limitation or exclusion of implied warranties or how long an implied warranty may last, so the above limitations may not apply to Customer. This warranty gives Customer specific legal rights and Customer may have other rights that vary from jurisdiction to jurisdiction. **LIMITATION OF LIABILITY**: WIND RIVER AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY INCIDENTAL, SPECIAL, CONSEQUENTIAL OR INDIRECT DAMAGES OF ANY KIND (INCLUDING DAMAGES FOR INTERRUPTION OF BUSINESS, PROCUREMENT OF SUBSTITUTE GOODS, LOSS OF PROFITS, OR THE LIKE) REGARDLESS OF THE FORM OF ACTION WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT PRODUCT LIABILITY OR ANY OTHER LEGAL OR EQUITABLE THEORY, ARISING OUT OF OR RELATED TO OPEN SOURCE SOFTWARE, EVEN IF WIND RIVER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. IN NO EVENT WILL WIND RIVER’S AGGREGATE CUMULATIVE LIABILITY FOR ANY CLAIMS ARISING OUT OF OR RELATED TO OPEN SOURCE SOFTWARE EXCEED ONE HUNDRED DOLLARS ($100). Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages so this limitation and exclusion may not apply to Customer. THE WARRANTY DISCLAIMER AND LIMITED LIABILITY ARE FUNDAMENTAL ELEMENTS OF THE BASIS OF THE BARGAIN BETWEEN WIND RIVER AND CUSTOMER. WIND RIVER WOULD NOT BE ABLE TO PROVIDE OPEN SOURCE SOFTWARE WITHOUT SUCH LIMITATIONS. ================================================ FILE: docs/source/lua.rst ================================================ .. _lua: Using Lua with Pktgen ===================== Lua is a high level dynamic programming language. It is small and lightweight and can easily be embedded in applications written in other languages. It is also suitable for loading and wrapping dynamic libraries. Lua is used in ``pktgen`` to script and configure the application and also to plug into DPDK functions to expose configuration and statistics. The following are some of the examples included in the ``test`` directory of ``pktgen`` repository. Example: Hello World -------------------- A simple "hello world" example to ensure that everything is working correctly: .. literalinclude:: ../../test/hello-world.lua :language: lua Example: Info ------------- A simple example to print out some metadata and configuration information from ``pktgen``: .. literalinclude:: ../../test/info.lua :language: lua Example: More Info ------------------ Another example to print out data from a running ``pktgen`` instance: .. literalinclude:: ../../test/test3.lua :language: lua Example: Sequence ----------------- An example to set a packet sequence: .. literalinclude:: ../../test/set_seq.lua :language: lua :tab-width: 4 Example: Main ------------- A more complex example showing most of the features available via the Lua interface and also show interaction with the user: .. literalinclude:: ../../test/main.lua :language: lua ================================================ FILE: docs/source/meson.build ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2018-2026> Intel Corporation sphinx = find_program('sphinx-build', required: get_option('enable_docs')) if not sphinx.found() subdir_done() endif htmldir = join_paths(get_option('datadir'), 'docs', 'dpdk') html_guides = custom_target('html_guides', input: files('index.rst'), output: 'html', command: [sphinx_wrapper, sphinx, meson.current_source_dir(), meson.current_build_dir()], depfile: '.html.d', build_by_default: get_option('enable_docs'), install: get_option('enable_docs'), install_dir: htmldir) install_data(files('custom.css'), install_dir: join_paths(htmldir,'_static', 'css')) doc_targets += html_guides doc_target_names += 'HTML_Guides' ================================================ FILE: docs/source/running.rst ================================================ .. _running: Running Pktgen ============== A sample commandline to start a ``pktgen`` instance would look something like the following, which you may need 'sudo -E' added to the front if not superuser. The -E option of sudo passes environment variables to sudo shell as the scripts need the RTE_SDK and RTE_TARGET variables:: ./app/pktgen -l 0-4 -n 3 -- -P -m "[1:3].0, [2:4].1 Pktgen, like other DPDK applications splits its commandline arguments into arguments for the DPDK Environmental Abstraction Layer (EAL) and arguments for the application itself. The two sets of arguments are separated using the standard convention of ``--`` as shown above. These commandline arguments are explained in the :ref:`usage_eal` and :ref:`usage_pktgen`. The output when running ``pktgen`` will look something like the following:: ----------------------- Copyright notices ----------------------- EAL: Detected lcore 0 as core 0 on socket 0 EAL: Detected lcore 1 as core 1 on socket 0 ... EAL: PCI device 0000:07:00.1 on NUMA socket 0 EAL: probe driver: 8086:1521 rte_igb_pmd EAL: 0000:07:00.1 not managed by UIO driver, skipping Lua 5.2.1 Copyright (C) 1994-2012 Lua.org, PUC-Rio >>> Packet Burst 16, RX Desc 256, TX Desc 256, mbufs/port 2048, mbuf cache 256 === port to lcore mapping table (# lcores 5) === lcore: 0 1 2 3 4 port 0: D: T 1: 0 0: 0 0: 1 0: 0 = 1: 1 port 1: D: T 0: 0 1: 0 0: 0 0: 1 = 1: 1 Total : 0: 0 1: 0 1: 0 0: 1 0: 1 Display and Timer on lcore 0, rx:tx counts per port/lcore Configuring 6 ports, MBUF Size 1984, MBUF Cache Size 256 Lcore: 1, type RX , rx_cnt 1, tx_cnt 0, RX (pid:qid): ( 0: 0) , TX (pid:qid): 2, type RX , rx_cnt 1, tx_cnt 0, RX (pid:qid): ( 1: 0) , TX (pid:qid): 3, type TX , rx_cnt 0, tx_cnt 1, RX (pid:qid): , TX (pid:qid): ( 0: 0) 4, type TX , rx_cnt 0, tx_cnt 1, RX (pid:qid): , TX (pid:qid): ( 1: 0) Port : 0, nb_lcores 2, private 0x7d08d8, lcores: 1 3 1, nb_lcores 2, private 0x7d1c48, lcores: 2 4 Initialize Port 0 -- TxQ 1, RxQ 1, Src MAC 90:e2:ba:5a:f7:90 Create: Default RX 0:0 - Mem (MBUFs 2048 x (1984 + 64)) + 790720 = 4869 KB Create: Default TX 0:0 - Mem (MBUFs 2048 x (1984 + 64)) + 790720 = 4869 KB Create: Range TX 0:0 - Mem (MBUFs 2048 x (1984 + 64)) + 790720 = 4869 KB Create: Sequence TX 0:0 - Mem (MBUFs 2048 x (1984 + 64)) + 790720 = 4869 KB Create: Special TX 0:0 - Mem (MBUFs 64 x (1984 + 64)) + 790720 = 901 KB Port memory used = 20373 KB Initialize Port 1 -- TxQ 1, RxQ 1, Src MAC 90:e2:ba:5a:f7:91 Create: Default RX 1:0 - Mem (MBUFs 2048 x (1984 + 64)) + 790720 = 4869 KB Create: Default TX 1:0 - Mem (MBUFs 2048 x (1984 + 64)) + 790720 = 4869 KB Create: Range TX 1:0 - Mem (MBUFs 2048 x (1984 + 64)) + 790720 = 4869 KB Create: Sequence TX 1:0 - Mem (MBUFs 2048 x (1984 + 64)) + 790720 = 4869 KB Create: Special TX 1:0 - Mem (MBUFs 64 x (1984 + 64)) + 790720 = 901 KB Port memory used = 20373 KB Total memory used = 40746 KB Port 0: Link Up - speed 10000 Mbps - full-duplex Port 1: Link Up - speed 10000 Mbps - full-duplex === Display processing on lcore 0 === RX processing on lcore 1, rxcnt 1, port/qid, 0/0 === RX processing on lcore 2, rxcnt 1, port/qid, 1/0 === TX processing on lcore 3, txcnt 1, port/qid, 0/0 === TX processing on lcore 4, txcnt 1, port/qid, 1/0 ... Once ``pktgen`` is running you will see an output like the following:: | Ports 0-3 of 8
Copyright(c) <2010-2026>, Intel Corporation Flags:Port : P--------------:0 P--------------:1 P--------------:2 P--------------:3 Link State : ----TotalRate---- Pkts/s Max/Rx : 0/0 0/0 0/0 0/0 0/0 Max/Tx : 0/0 0/0 0/0 0/0 0/0 MBits/s Rx/Tx : 0/0 0/0 0/0 0/0 0/0 Broadcast : 0 0 0 0 Multicast : 0 0 0 0 64 Bytes : 0 0 0 0 65-127 : 0 0 0 0 128-255 : 0 0 0 0 256-511 : 0 0 0 0 512-1023 : 0 0 0 0 1024-1522 : 0 0 0 0 Runts/Jumbos : 0/0 0/0 0/0 0/0 Errors Rx/Tx : 0/0 0/0 0/0 0/0 Total Rx Pkts : 0 0 0 0 Tx Pkts : 0 0 0 0 Rx MBs : 0 0 0 0 Tx MBs : 0 0 0 0 ARP/ICMP Pkts : 0/0 0/0 0/0 0/0 : Pattern Type : abcd... abcd... abcd... abcd... Tx Count/% Rate : Forever /100% Forever /100% Forever /100% Forever /100% PktSize/Rx:Tx Burst: 64 / 32 64 / 32 64 / 32 64 / 32 Src/Dest Port : 1234 / 5678 1234 / 5678 1234 / 5678 1234 / 5678 Pkt Type:VLAN ID : IPv4 / TCP:0001 IPv4 / TCP:0001 IPv4 / TCP:0001 IPv4 / TCP:0001 Dst IP Address : 192.168.1.1 192.168.0.1 192.168.3.1 192.168.2.1 Src IP Address : 192.168.0.1/24 192.168.1.1/24 192.168.2.1/24 192.168.3.1/24 Dst MAC Address : 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:d8 3c:fd:fe:9c:5c:db 3c:fd:fe:9c:5c:da Src MAC Address : 3c:fd:fe:9c:5c:d8 3c:fd:fe:9c:5c:d9 3c:fd:fe:9c:5c:da 3c:fd:fe:9c:5c:db VendID/PCI Addr : 8086:1572/04:00.0 8086:1572/04:00.1 8086:1572/04:00.2 8086:1572/04:00.3 -- Pktgen Ver: 3.2.4 (DPDK 17.05.0-rc0) Powered by DPDK --------------- Pktgen:/> The flags displayed on the top line for each port are:: Flags: P---------------- - Promiscuous mode enabled E - ICMP Echo enabled A - Send ARP Request flag G - Send Gratuitous ARP flag C - TX Cleanup flag p - PCAP enabled flag S - Send Sequence packets enabled R - Send Range packets enabled D - DPI Scanning enabled (If Enabled) I - Process packets on input enabled * - Using TAP interface for this port can be [-rt*] L - Send Latency packets V - Send VLAN ID tag M - Send MPLS header Q - Send Q-in-Q tags g - Process GARP packets g - Perform GRE with IPv4 payload G - Perform GRE with Ethernet payload C - Capture received packets R - Random bitfield(s) are applied Notes. - Use enable|disable or on|off to set the state. - a list of ports (no spaces) as 2,4,6-9,12 or 3-5,8 or 5 or the word 'all' Color best seen on a black background for now To see a set of example Lua commands see the files in wr-examples/pktgen/test The ``pktgen`` default colors and theme work best on a black background. If required, it is possible to set other color themes, (see :ref:`commands`). ================================================ FILE: docs/source/scripts.rst ================================================ .. _scripts: Running Script Files ==================== Pktgen can read and run files with default values and configurations via the ``-f`` commandline option (:ref:`usage_pktgen`). These files can either be ``.pkt`` files with Pktgen :ref:`runtime commands ` as shown in the previous section or ``.lua`` files with the same commands and options in Lua syntax. For example here is a ``pktgen`` instance that read a ``.pkt`` file:: pktgen -l 0-4 -n 3 --proc-type auto --socket-mem 128,128 -- \ -P -m "[1:3].0, [2:4].1" -f test/set_seq.pkt Where the ``test/set_seq.pkt`` (included in the ``pktgen`` repository) is as follows:: seq 0 all 0000:4455:6677 0000:1234:5678 10.11.0.1 10.10.0.1/16 5 6 ipv4 udp 1 128 set all seqCnt 1 The Lua version (``test/set_seq.lua`` in ``pktgen`` repository) is clearer and allows extension through standard Lua or user defined functions: .. literalinclude:: ../../test/set_seq.lua :language: lua :lines: 3- :tab-width: 4 The Lua interface is explained in the next section :ref:`lua`. ================================================ FILE: docs/source/socket.rst ================================================ .. _socket: Socket Support for Pktgen ========================= Pktgen provides a TCP socket connection to allow you to control it from a remote console or program. The TCP connection uses port 22022, 0x5606, and presents a Lua command shell interface. If you telnet on port 22022 to a machine running ``pktgen`` you will get a Lua command shell like interface. This interface does not have a command line prompt, but you can issue Lua code or load script files from the local disk of the machine. You can also send programs to the remote ``pktgen`` machine to load scripts and run scripts. Another way to connect remotely to ``pktgen`` is to use the ``socat`` program on a Linux machine:: $ socat -d -d READLINE TCP4:localhost:22022 This will create a connection and then wait for Lua command scripts. You can also send ``pktgen`` a command script file and display the output:: $ socat - TCP4:localhost:22022 < test/hello-world.lua Lua Version : Lua 5.3 Pktgen Version : 2.9.0 Pktgen Copyright : Copyright(c) `<2010-2026>`, Intel Corp. Pktgen Authors : Keith Wiles @ Wind River Systems Hello World!!!! Where the the ``test/hello-world.lua`` looks like this: .. literalinclude:: ../../test/hello-world.lua :language: lua Here is another ``socat`` example which loads a file from the local disk where ``pktgen`` is running and then we execute the file with a user defined function:: $ socat READLINE TCP4:172.25.40.163:22022 f,e = loadfile("test/hello-world.lua") f() Lua Version : Lua 5.3 Pktgen Version : 2.9.0 Pktgen Copyright : Copyright(c) `<2010-2026>`, Intel Corp. Pktgen Authors : Keith Wiles @ Wind River Systems Hello World!!!! You can also just send it commands via echo:: $ echo "f,e = loadfile('test/hello-world.lua'); f();" \ | socat - TCP4:172.25.40.163:22022 Lua Version : Lua 5.3 Pktgen Version : 2.9.0 Pktgen Copyright : Copyright(c) `<2010-2026>`, Intel Corp. Pktgen Authors : Keith Wiles @ Wind River Systems Hello World!!!! ================================================ FILE: docs/source/usage_eal.rst ================================================ .. _usage_eal: EAL Commandline Options ======================= Pktgen, like other DPDK applications splits commandline arguments into arguments for the DPDK Environmental Abstraction Layer (EAL) and arguments for the application itself. The two sets of arguments are separated using the standard convention of ``--``:: Pktgen executable is located at ``./app/app/${RTE_TARGET}/pktgen`` pktgen -l 0-4 -n 3 -- -P -m "[1:3].0, [2:4].1 The usual EAL commandline usage for ``pktgen`` is:: pktgen -c COREMASK -n NUM \ [-m NB] \ [-r NUM] \ [-b ] \ [--proc-type primary|secondary|auto] -- [pktgen options] The full list of EAL arguments are:: EAL options: -c COREMASK : A hexadecimal bitmask of cores to run on -n NUM : Number of memory channels -v : Display version information on startup -d LIB.so : Add driver (can be used multiple times) -m MB : Memory to allocate (see also --socket-mem) -r NUM : Force number of memory ranks (don't detect) --xen-dom0 : Support application running on Xen Domain0 without hugetlbfs --syslog : Set syslog facility --socket-mem : Memory to allocate on specific sockets (use comma separated values) --huge-dir : Directory where hugetlbfs is mounted --proc-type : Type of this process --file-prefix : Prefix for hugepage filenames --pci-blocklist, -b : Add a PCI device in block list. Prevent EAL from using this PCI device. The argument format is . --pci-allowlist, -w : Add a PCI device in allow list. Only use the specified PCI devices. The argument format is <[domain:]bus:devid.func>. This option can be present several times (once per device). NOTE: PCI allowlist cannot be used with -b option --vdev : Add a virtual device. The argument format is [,key=val,...] (ex: --vdev=eth_pcap0,iface=eth2). --vmware-tsc-map : Use VMware TSC map instead of native RDTSC --base-virtaddr : Specify base virtual address --vfio-intr : Specify desired interrupt mode for VFIO (legacy|msi|msix) --create-uio-dev : Create /dev/uioX (usually done by hotplug) EAL options for DEBUG use only: --no-huge : Use malloc instead of hugetlbfs --no-pci : Disable pci --no-hpet : Disable hpet --no-shconf : No shared config (mmap'd files) The ``-c COREMASK`` and ``-n NUM`` arguments are required. The other arguments are optional. Pktgen requires 2 logical cores (lcore) in order to run. The first lcore, 0, is used for the ``pktgen`` commandline, for timers and for displaying the runtime metrics text on the terminal. The additional lcores ``1-n`` are used to do the packet receive and transmits along with anything else related to packets. You do not need to start at the actual system lcore 0. The application will use the first lcore in the coremask bitmap. A more typical commandline to start a ``pktgen`` instance would be:: pktgen -l 0-4 -n 3 --proc-type auto --socket-mem 256,256 -b 0000:03:00.0 -b 0000:03:00.1 \ --file-prefix pg \ -- -P -m "[1:3].0, [2:4].1 The coremask ``-c 0x1f`` (0b11111) indicates 5 lcores are used, as the first lcore is used by Pktgen for display and timers. The ``--socket-mem 256,256`` DPDK command will allocate 256M from each CPU (two in this case). The :ref:`usage_pktgen` are shown in the next section. ================================================ FILE: docs/source/usage_pktgen.rst ================================================ .. _usage_pktgen: Pktgen Commandline Options ========================== The Pktgen commandline usage is:: ./app/app/``$(target}``/pktgen [EAL options] -- \ [-h] [-P] [-G] [-T] [-f cmd_file] \ [-l log_file] [-s P:PCAP_file] [-m ] The :ref:`usage_eal` were shown in the previous section. The ``pktgen`` arguments are:: Usage: pktgen [EAL options] -- [-h] [-P] [-G] [-T] [-f cmd_file] [-l log_file] [-s P:PCAP_file] [-m ] -s P:file PCAP packet stream file, 'P' is the port number -f filename Command file (.pkt) to execute or a Lua script (.lua) file -l filename Write log to filename -I use CLI -P Enable PROMISCUOUS mode on all ports -g address Optional IP address and port number default is (localhost:0x5606) If -g is used that enable socket support as a server application -G Enable socket support using default server values localhost:0x5606 -N Enable NUMA support -T Enable the color output -h Display the help information Where the options are: * ``-h``: Display the usage/help information shown above:: lspci | grep Ethernet This shows a list of all ports in the system. Some ports may not be usable by DPDK/Pktgen. The first port listed is bit 0 or least signification bit in the ``-c`` EAL coremask. Another method is to compile and run the DPDK sample application ``testpmd`` to list out the ports DPDK is able to use:: ./test_pmd -c 0x3 -n 2 * ``-s P:file``: The PCAP packet file to stream. ``P`` is the port number. * ``-f filename``: The script command file (.pkt) to execute or a Lua script (.lua) file. See :ref:`scripts`. * ``-l filename``: The filename to write a log to. * ``-P``: Enable PROMISCUOUS mode on all ports. * ``-G``: Enable socket support using default server values of localhost:0x5606. See :ref:`socket`. * ``-g address``: Same as ``-G`` but with an optional IP address and port number. See :ref:`socket`. * ``-T``: Enable color terminal output in VT100 * ``-N``: Enable NUMA support. * ``-m ``: Matrix for mapping ports to logical cores. The format of the port mapping string is defined with a BNF-like grammar as follows:: BNF: (or kind of BNF) := """ { "," } """ := "." := "[" ":" "]" := "[" ":" "]" := { "/" ( | ) } := { "/" ( | ) } := { "/" ( | ) } := "-" { "/" } := + := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 For example:: 1.0, 2.1, 3.2 - core 1 handles port 0 rx/tx, core 2 handles port 1 rx/tx core 3 handles port 2 rx/tx 1.[0-2], 2.3, ... - core 1 handle ports 0,1,2 rx/tx, core 2 handle port 3 rx/tx [0-1].0, [2/4-5].1, ... - cores 0-1 handle port 0 rx/tx, cores 2,4,5 handle port 1 rx/tx [1:2].0, [4:6].1, ... - core 1 handles port 0 rx, core 2 handles port 0 tx, [1:2].[0-1], [4:6].[2/3], ... - core 1 handles port 0 & 1 rx, core 2 handles port 0 & 1 tx [1:2-3].0, [4:5-6].1, ... - core 1 handles port 0 rx, cores 2,3 handle port 0 tx core 4 handles port 1 rx & core 5,6 handles port 1 tx [1-2:3].0, [4-5:6].1, ... - core 1,2 handles port 0 rx, core 3 handles port 0 tx core 4,5 handles port 1 rx & core 6 handles port 1 tx [1-2:3-5].0, [4-5:6/8].1, ... - core 1,2 handles port 0 rx, core 3,4,5 handles port 0 tx core 4,5 handles port 1 rx & core 6,8 handles port 1 tx [1:2].[0:0-7], [3:4].[1:0-7], - core 1 handles port 0 rx, core 2 handles ports 0-7 tx core 3 handles port 1 rx & core 4 handles port 0-7 tx BTW: you can use "{}" instead of "[]" as it does not matter to the syntax. Grouping can use ``{}`` instead of ``[]`` if required. Multiple Instances of Pktgen or other application ================================================= One possible solution I use and if you have enough ports available to use. Lets say you need two ports for your application, but you have 4 ports in your system. I physically loop back the cables to have port 0 connect to port 2 and port 1 connected to port 3. Now I can give two ports to my application and two ports to Pktgen. Setup if pktgen and your application you have to startup each one a bit differently to make sure they share the resources like memory and the ports. I will use two Pktgen running on the same machine, which just means you have to setup your application as one of the applications. In my machine I have 8 10G ports and 72 lcores between 2 sockets. Plus I have 1024 hugepages per socket for a total of 2048. Example commands:: # lspci | grep Ether 06:00.0 Ethernet controller: Intel Corporation Ethernet Converged Network Adapter X520-Q1 (rev 01) 06:00.1 Ethernet controller: Intel Corporation Ethernet Converged Network Adapter X520-Q1 (rev 01) 08:00.0 Ethernet controller: Intel Corporation Ethernet Converged Network Adapter X520-Q1 (rev 01) 08:00.1 Ethernet controller: Intel Corporation Ethernet Converged Network Adapter X520-Q1 (rev 01) 09:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01) 09:00.1 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01) 83:00.1 Ethernet controller: Intel Corporation DH8900CC Null Device (rev 21) 87:00.0 Ethernet controller: Intel Corporation Ethernet Converged Network Adapter X520-Q1 (rev 01) 87:00.1 Ethernet controller: Intel Corporation Ethernet Converged Network Adapter X520-Q1 (rev 01) 89:00.0 Ethernet controller: Intel Corporation Ethernet Converged Network Adapter X520-Q1 (rev 01) 89:00.1 Ethernet controller: Intel Corporation Ethernet Converged Network Adapter X520-Q1 (rev 01) ./app/app/${target}/pktgen -l 2-11 -n 3 --proc-type auto \ --socket-mem 512,512 --file-prefix pg1 \ -b 09:00.0 -b 09:00.1 -b 83:00.1 -b 06:00.0 \ -b 06:00.1 -b 08:00.0 -b 08:00.1 -- \ -T -P -m "[4:6].0, [5:7].1, [8:10].2, [9:11].3" \ -f themes/black-yellow.theme ./app/app/${target}/pktgen -l 2,4-11 -n 3 --proc-type auto \ --socket-mem 512,512 --file-prefix pg2 \ -b 09:00.0 -b 09:00.1 -b 83:00.1 -b 87:00.0 \ -b 87:00.1 -b 89:00.0 -b 89:00.1 -- \ -T -P -m "[12:16].0, [13:17].1, [14:18].2, [15:19].3" \ -f themes/black-yellow.theme Notice I block list the three onboard devices and then block list the other 4 ports I will not be using for each of the pktgen instances. I need 8+1 lcores for each instance for Pktgen use. The -c option of ff2 and FF004 lcores, the ff value are used for port handling and the 2/4 is used because pktgen needs the first lcore for display and timers. The -m option then assigns lcores to the ports. The information from above is taken from two new files pktgen-initial.sh and pktgen-worker.sh, have a look at them and adjust as you need. Pktgen can also be configured using the :ref:`commands`. ================================================ FILE: examples/meson.build ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2023-2026> Intel Corporation if fgen_dep.found() subdirs =['pktperf'] foreach d:subdirs subdir(d) endforeach endif ================================================ FILE: examples/pktperf/README.md ================================================ ## PKTPERF example application to verify Rx/Tx performance with multiple cores The `pktperf` example application is used to verify Rx/Tx performance with multiple cores or queues. The application is configured from the command line arguments and does not have CLI to configure on the fly. --- ```console **Copyright © <2023-2026>, Intel Corporation. All rights reserved.** Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Intel Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SPDX-License-Identifier: BSD-3-Clause pktperf: Created 2023 by Keith Wiles @ Intel.com ``` --- ### Overview The implementation uses multiple Rx/Tx queues per port, which is required to achieve maximum performance for receiving and transmitting frames. If your NIC does not support multiple queues, but does support multiple `virtual functions` per physical port, then you should create multiple `virtual functions` network devices and configure them using the `-m` mapping parameters. A single core can only be attached to one network device or port, which means you can not have a single core processing more then one network device or port. Having a single core processing more then one network device or port was almost never used and made the code more complicated. This is why the implementation was removed from the `pktperf` application. ### Command line arguments The command line arguments contain the standard DPDK arguments with the pktperf parameters after the '--' option. Please look at the DPDK documentation for the EAL arguments. The pktperf parameter can be displayed using the -h or --help option after the '--' option. ```console pktperf [EAL options] -- [-b burst] [-s size] [-r rate] [-d rxd/txd] [-m map] [-T secs] [-P] [-M mbufs] [-v] [-h] -b|--burst-count Number of packets for Rx/Tx burst (default 32) -s|--pkt-size Packet size in bytes (default 64) includes FCS bytes -r|--rate Packet TX rate percentage 0=off (default 100) -d|--descriptors Number of RX/TX descriptors (default 1,024/1,024) -m|--map Core to Port/queue mapping '[Rx-Cores:Tx-Cores].port' -T|--timeout Timeout period in seconds (default 1 second) -P|--no-promiscuous Turn off promiscuous mode (default On) -M|--mbuf-count Number of mbufs to allocate (default 8,192, max 131,072) -v|--verbose Verbose output -h|--help Print this help ``` ### Command line example ```bash sudo builddir/examples/pktperf/pktperf -l 1,2-9,14-21 -a 03:00.0 -a 82:00.0 -- -m "2-5:6-9.0" -m "14-17:18-21.1" ``` ## CPU/Socket layout ```bash ====================================================================== Core and Socket Information (as reported by '/sys/devices/system/cpu') ====================================================================== cores = [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14] sockets = [0, 1] Socket 0 Socket 1 -------- -------- Core 0 [0, 28] [14, 42] Core 1 [1, 29] [15, 43] Core 2 [2, 30] [16, 44] Core 3 [3, 31] [17, 45] Core 4 [4, 32] [18, 46] Core 5 [5, 33] [19, 47] Core 6 [6, 34] [20, 48] Core 8 [7, 35] [21, 49] Core 9 [8, 36] [22, 50] Core 10 [9, 37] [23, 51] Core 11 [10, 38] [24, 52] Core 12 [11, 39] [25, 53] Core 13 [12, 40] [26, 54] Core 14 [13, 41] [27, 55] ``` The `-m` argument defines the core to port mapping `.` the `:` (colon) is used to specify the the Rx and Tx cores for the port mapping. Leaving off the ':' is equivalent to running Rx and Tx processing on the specified core(s). When present the left side denotes the core(s) to use for receive processing and the right side denotes the core(s) to use for transmit processing. ### Example console output ```bash Port : Rate Statistics per queue (-) 0 >> Link up at 40 Gbps FDX Autoneg, WireSize 672 Bits, PPS 59,523,809, Cycles/Burst 5,120 RxQs : 9,512,816 8,870,944 9,235,916 9,272,516 Total: 36,892,192 TxQs : 6,182,144 12,364,288 6,182,144 12,364,288 Total: 37,092,864 TxDrop: 8,779,360 2,596,544 8,779,072 2,596,192 Total: 22,751,168 NoMBUF: 0 0 0 0 Total: 0 TxTime: 351 384 372 330 Total: 1,437 RxMissed: 248,108, ierr: 0, oerr: 0, RxNoMbuf: 0 1 >> Link up at 100 Gbps FDX Autoneg, WireSize 672 Bits, PPS 148,809,523, Cycles/Burst 2,048 RxQs : 23,465,206 24,484,244 24,600,096 25,202,408 Total: 97,751,954 TxQs : 23,915,040 23,915,040 23,915,040 23,915,040 Total: 95,660,160 TxDrop: 13,339,456 13,352,064 13,327,936 13,353,056 Total: 53,372,512 NoMBUF: 0 0 0 0 Total: 0 TxTime: 489 483 474 564 Total: 2,010 RxMissed: 0, ierr: 0, oerr: 0, RxNoMbuf: 0 Burst: 32, MBUF Count: 12,864, PktSize:64, Rx/Tx 1,024/1,024, Rate 100% ``` ================================================ FILE: examples/pktperf/meson.build ================================================ sources = files('pktperf.c', 'parse.c', 'port.c', 'stats.c', 'utils.c') cflags = [] deps = [dpdk, common, utils, fgen_dep] deps += [cc.find_library('rte_net_i40e', dirs: [dpdk_libs_path], required: false)] deps += [cc.find_library('rte_net_ixgbe', dirs: [dpdk_libs_path], required: false)] deps += [cc.find_library('rte_net_ice', dirs: [dpdk_libs_path], required: false)] deps += [cc.find_library('rte_bus_vdev', dirs: [dpdk_libs_path], required: false)] deps += [dependency('threads')] deps += [dependency('numa', required: true)] deps += [dependency('pcap', required: true)] deps += [cc.find_library('dl', required: false)] deps += [cc.find_library('m', required: false)] pktperf = executable('pktperf', sources, c_args: cflags, install: true, dependencies: [deps, dpdk_bond]) ================================================ FILE: examples/pktperf/parse.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) 2023-2026 Intel Corporation */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BURST_COUNT_OPT "burst-count" #define PKT_SIZE_OPT "pkt-size" #define TX_RATE_OPT "tx-rate" #define RX_TX_DESC_OPT "descriptors" #define MAPPING_OPT "map" #define TIMEOUT_OPT "timeout" #define PROMISCUOUS_OPT "no-promiscuous" #define JUMBO_OPT "jumbo" #define MBUF_COUNT_OPT "mbuf-count" #define FGEN_STRING_OPT "fgen" #define FGEN_FILE_OPT "fgen-file" #define VERBOSE_OPT "verbose" #define TCP_OPT "tcp" #define UDP_OPT "udp" #define HELP_OPT "help" // clang-format off static const struct option lgopts[] = { {BURST_COUNT_OPT, 1, 0, 'b'}, {PKT_SIZE_OPT, 1, 0, 's'}, {TX_RATE_OPT, 1, 0, 'r'}, {RX_TX_DESC_OPT, 1, 0, 'd'}, {MAPPING_OPT, 1, 0, 'm'}, {TIMEOUT_OPT, 1, 0, 'T'}, {MBUF_COUNT_OPT, 1, 0, 'M'}, {PROMISCUOUS_OPT, 0, 0, 'P'}, {JUMBO_OPT, 0, 0, 'j'}, {FGEN_STRING_OPT, 0, 0, 'f'}, {FGEN_FILE_OPT, 0, 0, 'F'}, {VERBOSE_OPT, 0, 0, 'v'}, {TCP_OPT, 0, 0, 't'}, {UDP_OPT, 0, 0, 'u'}, {HELP_OPT, 0, 0, 'h'}, {NULL, 0, 0, 0} }; // clang-format on static const char *short_options = "t:b:s:r:d:m:T:M:F:f:Pjvhtu"; /* display usage */ void usage(int err) { printf( "pktperf [EAL options] -- [-b burst] [-s size] [-r rate] [-d rxd/txd] [-m map] [-T secs] " "[-P] [-M mbufs] [-v] [-h]\n" "\t-b|--burst-count Number of packets for Rx/Tx burst (default %d)\n" "\t-s|--pkt-size Packet size in bytes (default %'d) includes FCS bytes\n" "\t-r|--rate Packet TX rate percentage 0=off (default %'d)\n" "\t-d|--descriptors Number of RX/TX descriptors (default %'d/%'d)\n" "\t-m|--map Core to Port/queue mapping '[Rx-Cores:Tx-Cores].port'\n" "\t-T|--timeout Timeout period in seconds (default %d second)\n" "\t-P|--no-promiscuous Turn off promiscuous mode (default On)\n" "\t-M|--mbuf-count Number of mbufs to allocate (default %'d, max %'d)\n" "\t-t|--tcp Use TCP\n" "\t-u|--udp Use UDP (default UDP)\n" "\t-j|--jumbo Enable jumbo frame support\n" "\t-f|--fgen FGEN string to load\n" "\t-F|--fgen-file FGEN file to load\n" "\t-v|--verbose Verbose output\n" "\t-h|--help Print this help\n", DEFAULT_BURST_COUNT, DEFAULT_PKT_SIZE, DEFAULT_TX_RATE, DEFAULT_RX_DESC, DEFAULT_TX_DESC, DEFAULT_TIMEOUT_PERIOD, DEFAULT_MBUF_COUNT, MAX_MBUF_COUNT); exit(err); } static struct rte_mempool * create_pktmbuf_pool(const char *type, uint16_t lid, uint16_t pid, uint16_t qid, uint32_t nb_mbufs, uint32_t cache_size) { struct rte_mempool *mp; char name[RTE_MEMZONE_NAMESIZE]; /* Create the pktmbuf pool one per lcore/port */ snprintf(name, sizeof(name) - 1, "%s-%u/%u/%u", type, lid, pid, qid); printf("Creating %s mbuf pool for lcore %3u, port %2u, qid %2u, MBUF Count %'u, size %'u on " "NUMA %d\n", name, lid, pid, qid, nb_mbufs, info->mbuf_size, pg_eth_dev_socket_id(pid)); mp = rte_pktmbuf_pool_create(name, info->mbuf_count, cache_size, 0, info->mbuf_size, pg_eth_dev_socket_id(pid)); return mp; } static int parse_cores(l2p_port_t *port, const char *cores, int mode) { char *core_map = NULL; int num_cores = 0, l, h, num_fields; char *fields[3] = {0}, *f0, *f1; char name[64]; core_map = alloca(MAX_ALLOCA_SIZE); if (!core_map) ERR_RET("out of memory for core string\n"); snprintf(core_map, MAX_ALLOCA_SIZE - 1, "%s", cores); num_fields = rte_strsplit(core_map, strlen(core_map), fields, RTE_DIM(fields), '-'); if (num_fields <= 0 || num_fields > 2) ERR_RET("invalid core mapping '%s'\n", cores); DBG_PRINT("num_fields: %d from cores '%s'\n", num_fields, cores); f0 = fields[0]; f1 = (fields[1] == NULL) ? f0 : fields[1]; f0 = pg_strtrimset(f0, "[]"); f0 = pg_strtrimset(f0, "{}"); f1 = pg_strtrimset(f1, "[]"); f1 = pg_strtrimset(f1, "{}"); DBG_PRINT("range of cores specified: %s - %s\n", f0, f1); l = strtol(f0, NULL, 10); h = strtol(f1, NULL, 10); DBG_PRINT("lcore: %d to %d\n", l, h); do { l2p_lport_t *lport; int32_t sid = pg_eth_dev_socket_id(port->pid); lport = info->lports[l]; if (lport == NULL) { snprintf(name, sizeof(name) - 1, "lport-%u:%u", l, port->pid); lport = rte_zmalloc_socket(name, sizeof(l2p_lport_t), RTE_CACHE_LINE_SIZE, sid); if (!lport) ERR_RET("Failed to allocate memory for lport info\n"); lport->lid = l; info->lports[l] = lport; } else ERR_PRINT("lcore %u already in use\n", l); num_cores++; lport->port = port; lport->mode = mode; switch (mode) { case LCORE_MODE_RX: lport->rx_qid = port->num_rx_qids++; DBG_PRINT("lcore %u:%u:%u is in RX mode\n", l, lport->port->pid, lport->rx_qid); port->rx_mp[lport->rx_qid] = create_pktmbuf_pool( "Rx", lport->lid, port->pid, lport->rx_qid, info->mbuf_count, MEMPOOL_CACHE_SIZE); if (port->rx_mp[lport->rx_qid] == NULL) ERR_RET("Unable to allocate Rx pktmbuf pool for lid/port %d/%d\n", lport->lid, port->pid); break; case LCORE_MODE_TX: lport->tx_qid = port->num_tx_qids++; DBG_PRINT("lcore %u:%u:%u is in TX mode\n", l, lport->port->pid, lport->tx_qid); port->tx_mp[lport->tx_qid] = create_pktmbuf_pool( "Tx", lport->lid, port->pid, lport->tx_qid, info->mbuf_count, MEMPOOL_CACHE_SIZE); if (port->tx_mp[lport->tx_qid] == NULL) ERR_RET("Unable to allocate Tx pktmbuf pool for lid/port %d/%d\n", lport->lid, port->pid); break; case LCORE_MODE_BOTH: lport->rx_qid = port->num_rx_qids++; lport->tx_qid = port->num_tx_qids++; DBG_PRINT("lcore %u:%u:%u is in RX/TX mode\n", l, lport->port->pid, lport->rx_qid); port->rx_mp[lport->rx_qid] = create_pktmbuf_pool( "Rx", lport->lid, port->pid, lport->rx_qid, info->mbuf_count, MEMPOOL_CACHE_SIZE); if (port->rx_mp[lport->rx_qid] == NULL) ERR_RET("Unable to allocate Rx pktmbuf pool for lid/port %d/%d\n", lport->lid, port->pid); port->tx_mp[lport->tx_qid] = create_pktmbuf_pool( "Tx", lport->lid, port->pid, lport->tx_qid, info->mbuf_count, MEMPOOL_CACHE_SIZE); if (port->tx_mp[lport->tx_qid] == NULL) ERR_RET("Unable to allocate Tx pktmbuf pool for lid/port %d/%d\n", lport->lid, port->pid); break; default: ERR_RET("invalid port mode\n"); break; } DBG_PRINT("lcore: %u port: %u qid: %u/%u name:'%s'\n", lport->lid, port->pid, lport->rx_qid, lport->tx_qid, name); } while (l++ < h); DBG_PRINT("num_cores: %d\n", num_cores); return num_cores; } static int parse_mapping(const char *map) { char *fields[3] = {0}, *f0, *f1, *lcores[3] = {0}, *c0, *c1; char *mapping = NULL; int num_fields, num_cores, num_lcores; uint16_t pid; if (!map || strlen(map) == 0) ERR_RET("no mapping specified or string empty\n"); mapping = alloca(MAX_ALLOCA_SIZE); if (!mapping) ERR_RET("unable to allocate map string\n"); snprintf(mapping, MAX_ALLOCA_SIZE - 1, "%s", map); DBG_PRINT("Mapping: '%s'\n", mapping); /* parse map into a lcore list and port number */ num_fields = rte_strsplit(mapping, strlen(mapping), fields, RTE_DIM(fields), '.'); if (num_fields != 2) ERR_RET("Invalid mapping format '%s'\n", mapping); f0 = fields[0]; f1 = (fields[1] == NULL) ? f0 : fields[1]; f0 = pg_strtrimset(f0, "[]"); f0 = pg_strtrimset(f0, "{}"); f1 = pg_strtrimset(f1, "[]"); f1 = pg_strtrimset(f1, "{}"); DBG_PRINT("Mapping: fields(%u) lcore '%s', port '%s'\n", num_fields, f0, f1); pid = strtol(f1, NULL, 10); if (pid >= RTE_MAX_ETHPORTS) ERR_RET("Invalid port number '%s'\n", f1); DBG_PRINT("Mapping: Port %u\n", pid); info->ports[pid].pid = pid; num_lcores = rte_strsplit(f0, strlen(f0), lcores, RTE_DIM(lcores), ':'); if (num_lcores <= 0 || num_lcores > 2) ERR_RET("Invalid mapping format '%s'\n", fields[0]); c0 = lcores[0]; c1 = (lcores[1] == NULL) ? c0 : lcores[1]; if (num_lcores == 1) { num_cores = parse_cores(&info->ports[pid], c0, LCORE_MODE_BOTH); if (num_cores <= 0) ERR_RET("Invalid mapping format '%s'\n", c0); DBG_PRINT("num_cores for both Rx/Tx: %d\n", num_cores); } else { num_cores = parse_cores(&info->ports[pid], c0, LCORE_MODE_RX); if (num_cores <= 0) ERR_RET("Invalid mapping format '%s'\n", c0); DBG_PRINT("num_cores for RX: %d\n", num_cores); num_cores = parse_cores(&info->ports[pid], c1, LCORE_MODE_TX); if (num_cores <= 0) ERR_RET("Invalid mapping format '%s'\n", c1); DBG_PRINT("num_cores for TX: %d\n", num_cores); } return EXIT_SUCCESS; } static void validate_args(void) { if (info->tx_rate > MAX_TX_RATE) info->tx_rate = DEFAULT_TX_RATE; DBG_PRINT("Packet Tx rate: %'u\n", info->tx_rate); if (info->burst_count <= 0) info->burst_count = DEFAULT_BURST_COUNT; else if (info->burst_count > MAX_BURST_COUNT) info->burst_count = MAX_BURST_COUNT; DBG_PRINT("RX/TX burst count: %'u\n", info->burst_count); if (info->pkt_size == 0) info->pkt_size = DEFAULT_PKT_SIZE; else if (info->pkt_size > MAX_PKT_SIZE) info->pkt_size = MAX_PKT_SIZE; DBG_PRINT("Packet size: %'u\n", info->pkt_size); if (info->mbuf_count < DEFAULT_MBUF_COUNT) info->mbuf_count = DEFAULT_MBUF_COUNT; else if (info->mbuf_count > MAX_MBUF_COUNT) { ERR_PRINT("invalid MBUF Count value 1 <= %'u <= %'d (default %'d)\n", info->mbuf_count, MAX_MBUF_COUNT, DEFAULT_MBUF_COUNT); usage(EXIT_FAILURE); } DBG_PRINT("Timeout period: %'u\n", info->timeout_secs); if (info->nb_rxd < MIN_RX_DESC || info->nb_rxd > MAX_RX_DESC) info->nb_rxd = DEFAULT_RX_DESC; if (info->nb_txd < MIN_TX_DESC || info->nb_txd > MAX_TX_DESC) info->nb_txd = DEFAULT_TX_DESC; DBG_PRINT("Rx/Tx Ring Size: %'u/%'u\n", info->nb_rxd, info->nb_txd); if (info->timeout_secs <= 0) info->timeout_secs = DEFAULT_TIMEOUT_PERIOD; else if (info->timeout_secs > MAX_TIMEOUT_PERIOD) { ERR_PRINT("invalid timeout value 1 <= %'u <= %'d (default %d)\n", info->timeout_secs, MAX_TIMEOUT_PERIOD, DEFAULT_TIMEOUT_PERIOD); usage(EXIT_FAILURE); } DBG_PRINT("Timeout period: %'u\n", info->timeout_secs); printf("num_ports: %'u, nb_rxd %'u, nb_txd %'u, burst %'u, lcores %'u\n", info->num_ports, info->nb_rxd, info->nb_txd, info->burst_count, rte_lcore_count()); info->mbuf_count += info->num_ports * (info->nb_rxd + info->nb_txd + info->burst_count + (rte_lcore_count() * MEMPOOL_CACHE_SIZE)); info->mbuf_count = RTE_MAX(info->mbuf_count, DEFAULT_MBUF_COUNT); DBG_PRINT("TX packet application started, Burst size %'u, Packet size %'u, Rate %u%%\n", info->burst_count, info->pkt_size, info->tx_rate); if (info->num_mappings == 0) { ERR_PRINT("No port mappings specified, use '-m' option\n"); usage(EXIT_FAILURE); } } /* Parse the argument given on the command line of the application */ static int parse_args(int argc, char **argv) { int opt, ret; char **argvopt; int option_index; char rxtx_desc[64]; char *descs[3]; argvopt = argv; info->promiscuous_on = DEFAULT_PROMISCUOUS_MODE; info->jumbo_frame_on = DEFAULT_JUMBO_FRAME_MODE; info->timeout_secs = DEFAULT_TIMEOUT_PERIOD; info->burst_count = DEFAULT_BURST_COUNT; info->nb_rxd = DEFAULT_RX_DESC; info->nb_txd = DEFAULT_TX_DESC; info->mbuf_count = DEFAULT_MBUF_COUNT; info->mbuf_size = RTE_MBUF_DEFAULT_BUF_SIZE; info->pkt_size = DEFAULT_PKT_SIZE; info->tx_rate = DEFAULT_TX_RATE; info->ip_proto = IPPROTO_UDP; info->force_quit = false; info->verbose = false; while ((opt = getopt_long(argc, argvopt, short_options, lgopts, &option_index)) != EOF) { switch (opt) { case 'b': /* RX/TX burst option */ info->burst_count = strtoul(optarg, NULL, 10); break; case 's': /* Packet size option */ info->pkt_size = strtoul(optarg, NULL, 10); break; case 'r': /* Tx Rate option */ info->tx_rate = strtoul(optarg, NULL, 10); break; case 'd': /* Number of Rx/Tx descriptors */ snprintf(rxtx_desc, sizeof(rxtx_desc) - 1, "%s", optarg); if (rte_strsplit(rxtx_desc, strlen(rxtx_desc), descs, RTE_DIM(descs), '/') != 2) ERR_RET("Invalid Rx/Tx descriptors '%s'\n", optarg); info->nb_rxd = strtoul(descs[0], NULL, 10); info->nb_txd = strtoul(descs[1], NULL, 10); break; case 'm': /* Mapping option */ DBG_PRINT("Mapping: '%s'\n", optarg); /* place mapping strings into a list for processing later */ info->mappings[info->num_mappings++] = strdup(optarg); break; case 'T': /* Timeout option */ info->timeout_secs = strtol(optarg, NULL, 0); break; case 'M': /* MBUF Count */ info->mbuf_count = strtol(optarg, NULL, 0); break; case 'P': /* Promiscuous option */ info->promiscuous_on = 0; DBG_PRINT("Promiscuous mode: off\n"); break; case 'f': /* FGEN string */ if (fgen_load_strings(info->fgen, &optarg, 1) < 0) { ERR_PRINT("Unable to load FGEN string '%s'\n", optarg); usage(EXIT_FAILURE); } break; case 'F': /* FGEN file */ if (fgen_load_files(info->fgen, &optarg, 1) < 0) { ERR_PRINT("Unable to load FGEN file '%s'\n", optarg); usage(EXIT_FAILURE); } break; case 't': /* TCP */ info->ip_proto = IPPROTO_TCP; break; case 'u': /* UDP */ info->ip_proto = IPPROTO_UDP; break; case 'j': /* Jumbo frames */ info->mbuf_size = JUMBO_MBUF_SIZE; info->jumbo_frame_on = 1; break; case 'v': /* Verbose option */ info->verbose = true; break; case 'h': /* Help option */ usage(EXIT_SUCCESS); break; default: usage(EXIT_FAILURE); break; } } validate_args(); ret = optind - 1; optind = 1; /* reset getopt lib */ return ret; } int parse_configuration(int argc, char **argv) { /* parse application arguments (after the EAL ones) */ if (parse_args(argc, argv) < 0) ERR_RET("Invalid PKTPERF arguments\n"); for (int i = 0; i < info->num_mappings; i++) parse_mapping(info->mappings[i]); for (int pid = 0; pid < info->num_ports; pid++) { if (port_setup(&info->ports[pid]) < 0) ERR_RET("Port setup failed\n"); } if ((info->fgen = fgen_create(0)) == NULL) ERR_RET("FGEN creation failed\n"); return 0; } ================================================ FILE: examples/pktperf/pktperf.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) 2023-2026 Intel Corporation */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include txpkts_info_t *info; static __inline__ void mbuf_iterate_cb(struct rte_mempool *mp, void *opaque, void *obj, unsigned obj_idx __rte_unused) { l2p_lport_t *lport = (l2p_lport_t *)opaque; struct rte_mbuf *m = (struct rte_mbuf *)obj; uint16_t plen = info->pkt_size - RTE_ETHER_CRC_LEN; if (plen < RTE_ETHER_MIN_LEN - RTE_ETHER_CRC_LEN) { printf("Invalid packet size %u, setting to minimum %u\n", plen, RTE_ETHER_MIN_LEN - RTE_ETHER_CRC_LEN); plen = RTE_ETHER_MIN_LEN - RTE_ETHER_CRC_LEN; } packet_constructor(lport, rte_pktmbuf_mtod(m, uint8_t *), info->ip_proto); m->pool = mp; m->next = NULL; m->data_len = plen; m->pkt_len = plen; m->port = 0; m->ol_flags = 0; } static __inline__ void do_rx_process(l2p_lport_t *lport, struct rte_mbuf **mbufs, uint32_t n_mbufs, uint64_t curr_tsc) { l2p_port_t *port = lport->port; qstats_t *c; uint16_t nb_pkts, rx_qid; rx_qid = lport->rx_qid; c = &port->pq[rx_qid].curr; /* drain the RX queue */ nb_pkts = rte_eth_rx_burst(port->pid, rx_qid, mbufs, n_mbufs); if (nb_pkts) { for (uint16_t i = 0; i < nb_pkts; i++) c->q_ibytes[rx_qid] += rte_pktmbuf_pkt_len(mbufs[i]); c->q_ipackets[rx_qid] += nb_pkts; rte_pktmbuf_free_bulk(mbufs, nb_pkts); c->q_rx_time[rx_qid] = rte_rdtsc() - curr_tsc; } } static __inline__ void do_tx_process(l2p_lport_t *lport, struct rte_mbuf **mbufs, uint16_t n_mbufs, uint64_t curr_tsc) { l2p_port_t *port = lport->port; struct rte_mempool *mp; qstats_t *c; uint16_t pid, tx_qid; pid = port->pid; tx_qid = lport->tx_qid; c = &port->pq[tx_qid].curr; mp = lport->port->tx_mp[tx_qid]; /* Use mempool routines instead of pktmbuf to make sure the mbufs is not altered */ if (rte_mempool_get_bulk(mp, (void **)mbufs, n_mbufs) == 0) { uint16_t plen = info->pkt_size - RTE_ETHER_CRC_LEN; uint16_t sent, send = n_mbufs; for (uint16_t i = 0; i < n_mbufs; i++) { if (mbufs[i]->data_len != plen || mbufs[i]->pkt_len != plen) { printf("Mismatch mbuf lengths mbuf[%u] data_len=%u pkt_len=%u, resetting\n", i, mbufs[i]->data_len, mbufs[i]->pkt_len); /* Reset mbuf fields that might have been changed */ mbufs[i]->data_len = plen; mbufs[i]->pkt_len = plen; mbufs[i]->next = NULL; mbufs[i]->port = 0; mbufs[i]->ol_flags = 0; } } do { sent = rte_eth_tx_burst(pid, tx_qid, mbufs, send); send -= sent; mbufs += sent; } while (send > 0); c->q_opackets[tx_qid] += n_mbufs; c->q_obytes[tx_qid] += (n_mbufs * plen); /* does not include FCS */ c->q_tx_time[tx_qid] = rte_rdtsc() - curr_tsc; } else c->q_no_txmbufs[tx_qid]++; } /* main processing loop */ static void rx_loop(void) { l2p_lport_t *lport; uint16_t rx_burst = info->burst_count * 2; struct rte_mbuf *mbufs[rx_burst]; lport = info->lports[rte_lcore_id()]; printf("Starting Rx loop for lcore:port:queue %3u:%2u:%2u\n", rte_lcore_id(), lport->port->pid, lport->rx_qid); while (!info->force_quit) do_rx_process(lport, mbufs, rx_burst, rte_rdtsc()); DBG_PRINT("Exiting loop for lcore:port:queue %3u:%2u:%2u\n", rte_lcore_id(), lport->port->pid, lport->rx_qid); } static void tx_loop(void) { l2p_lport_t *lport; l2p_port_t *port; uint64_t curr_tsc, burst_tsc; uint16_t tx_burst = info->burst_count; struct rte_mbuf *mbufs[tx_burst]; lport = info->lports[rte_lcore_id()]; port = lport->port; printf("Starting Tx loop for lcore:port:queue %3u:%2u:%2u\n", rte_lcore_id(), port->pid, lport->tx_qid); pthread_spin_lock(&port->tx_lock); if (port->tx_inited[lport->tx_qid] == 0) { port->tx_inited[lport->tx_qid] = 1; /* iterate over all buffers in the pktmbuf pool and setup the packet data */ rte_mempool_obj_iter(port->tx_mp[lport->tx_qid], mbuf_iterate_cb, (void *)lport); } pthread_spin_unlock(&port->tx_lock); burst_tsc = rte_rdtsc() + port->tx_cycles; while (!info->force_quit) { curr_tsc = rte_rdtsc(); if (unlikely(curr_tsc >= burst_tsc)) { burst_tsc = curr_tsc + port->tx_cycles; if (likely(port->tx_cycles)) do_tx_process(lport, mbufs, tx_burst, curr_tsc); } } DBG_PRINT("Exiting loop for lcore:port:queue %3u:%2u:%2u\n", rte_lcore_id(), port->pid, lport->tx_qid); } static void rxtx_loop(void) { l2p_lport_t *lport; l2p_port_t *port; uint64_t curr_tsc, burst_tsc; uint16_t rx_burst = info->burst_count * 2; uint16_t tx_burst = info->burst_count; struct rte_mbuf *mbufs[rx_burst]; lport = info->lports[rte_lcore_id()]; port = lport->port; printf("Starting Rx/Tx loop for lcore:port:queue %3u:%2u:%2u.%2u\n", rte_lcore_id(), port->pid, lport->rx_qid, lport->tx_qid); pthread_spin_lock(&port->tx_lock); if (port->tx_inited[lport->tx_qid] == 0) { port->tx_inited[lport->tx_qid] = 1; /* iterate over all buffers in the pktmbuf pool and setup the packet data */ rte_mempool_obj_iter(port->tx_mp[lport->tx_qid], mbuf_iterate_cb, (void *)lport); } pthread_spin_unlock(&port->tx_lock); burst_tsc = rte_rdtsc() + port->tx_cycles; while (!info->force_quit) { curr_tsc = rte_rdtsc(); do_rx_process(lport, mbufs, rx_burst, curr_tsc); if (unlikely(curr_tsc >= burst_tsc)) { burst_tsc = curr_tsc + port->tx_cycles; if (likely(port->tx_cycles)) do_tx_process(lport, mbufs, tx_burst, curr_tsc); } } DBG_PRINT("Exiting loop for lcore:port:queue %3u:%2u:%2u.%u\n", rte_lcore_id(), port->pid, lport->rx_qid, lport->tx_qid); } static int txpkts_launch_one_lcore(__rte_unused void *dummy) { l2p_lport_t *lport = info->lports[rte_lcore_id()]; if (lport == NULL) ERR_RET("lport is NULL lcore(%u)\n", rte_lcore_id()); if (lport->port == NULL) ERR_RET("lport->port is NULL lcore(%u)\n", rte_lcore_id()); if (lport->port->pid >= RTE_MAX_ETHPORTS) ERR_RET("lport->port->pid is invalid (%u) lcore(%u)\n", lport->port->pid, rte_lcore_id()); switch (lport->mode) { case LCORE_MODE_RX: rx_loop(); break; case LCORE_MODE_TX: tx_loop(); break; case LCORE_MODE_BOTH: rxtx_loop(); break; case LCORE_MODE_UNKNOWN: default: ERR_RET("Invalid mode %u\n", lport->mode); break; } return 0; } static void signal_handler(int signum) { if (signum == SIGINT || signum == SIGTERM) { DBG_PRINT("\n\nSignal %d received, preparing to exit...\n", signum); info->force_quit = true; } } static int initialize_dpdk(int argc, char **argv) { int ret = 0; signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); srandom(RANDOM_SEED); setlocale(LC_ALL, ""); if ((ret = rte_eal_init(argc, argv)) < 0) ERR_RET("Invalid DPDK arguments\n"); argc -= ret; /* Skip DPDK configuration options */ argv += ret; if ((info->num_ports = rte_eth_dev_count_avail()) == 0) ERR_RET("No Ethernet ports found - bye\n"); if (parse_configuration(argc, argv) < 0) ERR_RET("Invalid configuration\n"); return 0; } static int launch_lcore_threads(void) { int lid; /* launch per-lcore init on every worker lcore */ if (rte_eal_mp_remote_launch(txpkts_launch_one_lcore, NULL, SKIP_MAIN) != 0) ERR_RET("Failed to launch lcore threads\n"); /* Scroll the screen to keep console output for debugging */ for (int i = 0; i < 10; i++) PRINT("\n\n\n\n\n\n\n\n\n\n\n"); /* Display the statistics */ do { print_stats(); rte_delay_us_sleep(info->timeout_secs * Million); } while (!info->force_quit); RTE_LCORE_FOREACH_WORKER(lid) { DBG_PRINT("Waiting for lcore %d to exit\n", lid); if (rte_eal_wait_lcore(lid) < 0) ERR_RET("Error waiting for lcore %d to exit\n", lid); } for (uint16_t portid = 0; portid < info->num_ports; portid++) { DBG_PRINT("Closing port %d... ", portid); if (rte_eth_dev_stop(portid) == 0) rte_eth_dev_close(portid); DBG_PRINT("\n"); } return rte_eal_cleanup(); } static txpkts_info_t * info_alloc(void) { txpkts_info_t *txinfo; txinfo = (txpkts_info_t *)calloc(1, sizeof(txpkts_info_t)); if (txinfo) { for (int i = 0; i < RTE_MAX_ETHPORTS; i++) { int ret; txinfo->ports[i].pid = RTE_MAX_ETHPORTS + 1; /* set to invalid port id */ ret = pthread_spin_init(&txinfo->ports[i].tx_lock, PTHREAD_PROCESS_PRIVATE); if (ret != 0) { free(txinfo); ERR_RET_NULL("Unable to initialize tx_lock for port %d: %s\n", i, strerror(ret)); } } } return txinfo; } int main(int argc, char **argv) { info = info_alloc(); if (info) { if (initialize_dpdk(argc, argv) == 0) { if (launch_lcore_threads() == 0) { // Waits for all threads to exit free(info); return EXIT_SUCCESS; } } free(info); } return EXIT_FAILURE; } ================================================ FILE: examples/pktperf/pktperf.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) 2023-2026 Intel Corporation */ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PRINT(format, args...) \ do { \ printf(format, ##args); \ fflush(stdout); \ } while (0) #define INFO_PRINT(format, args...) \ do { \ char buf[64]; \ snprintf(buf, sizeof(buf), "%s(%'d)", __func__, __LINE__); \ printf("INFO>%-24s:" format, buf, ##args); \ fflush(stdout); \ } while (0) #define ERR_PRINT(format, args...) \ do { \ char buf[64]; \ snprintf(buf, sizeof(buf), "%s(%'d)", __func__, __LINE__); \ printf("ERROR>%-24s:" format, buf, ##args); \ fflush(stdout); \ } while (0) #define ERR_RET(format, args...) \ do { \ char buf[64]; \ snprintf(buf, sizeof(buf), "%s(%'d)", __func__, __LINE__); \ printf("ERROR>%-24s:" format, buf, ##args); \ fflush(stdout); \ return EXIT_FAILURE; \ } while (0) #define ERR_RET_NULL(format, args...) \ do { \ char buf[64]; \ snprintf(buf, sizeof(buf), "%s(%'d)", __func__, __LINE__); \ printf("ERROR>%-24s:" format, buf, ##args); \ fflush(stdout); \ return NULL; \ } while (0) #define DBG_PRINT(format, args...) \ do { \ if (info->verbose) { \ char buf[64]; \ snprintf(buf, sizeof(buf), "%s(%'d)", __func__, __LINE__); \ printf("DEBUG> %-24s:" format, buf, ##args); \ fflush(stdout); \ } \ } while (0) #define JUMBO_ETHER_MTU 9216 // 9K total size of the Ethernet jumbo frame #define JUMBO_DATAROOM_SIZE 9000 // 9K data room size in the Ethernet jumbo frame #define JUMBO_HEADROOM_SIZE \ (JUMBO_ETHER_MTU - JUMBO_DATAROOM_SIZE) // 9K headroom size in the Ethernet jumbo frame #define JUMBO_MBUF_SIZE (JUMBO_ETHER_MTU + RTE_PKTMBUF_HEADROOM) enum { DEFAULT_PKT_SIZE = 64, /* Default packet size */ DEFAULT_TX_RATE = 100, /* Default TX rate */ DEFAULT_RX_DESC = 1024, /* RX descriptors by default */ DEFAULT_TX_DESC = 1024, /* TX descriptors by default */ DEFAULT_BURST_COUNT = 32, /* default RX/TX burst count */ DEFAULT_TX_DRAIN_US = 100, /* default TX drain every ~100us */ DEFAULT_TIMEOUT_PERIOD = 1, /* 1 seconds default */ DEFAULT_PROMISCUOUS_MODE = 1, /* default to enabled */ DEFAULT_JUMBO_FRAME_MODE = 0, /* default to disabled for jumbo frames */ DEFAULT_MBUF_COUNT = (8 * 1024), /* default to 16K mbufs */ MAX_MBUF_COUNT = (128 * 1024), /* max to 128K mbufs */ MIN_RX_DESC = 512, /* Minimum number of RX descriptors */ MIN_TX_DESC = 512, /* Minimum number of TX descriptors */ MAX_RX_DESC = 4096, /* Maximum number of RX descriptors */ MAX_TX_DESC = 4096, /* Maximum number of TX descriptors */ MAX_QUEUES_PER_PORT = 16, /* Max number of queues per port */ MAX_MAPPINGS = 32, /* Max number of mappings */ MAX_TX_RATE = 100, /* Max TX rate percentage */ MAX_PKT_SIZE = 1518, /* Maximum packet size */ MAX_ALLOCA_SIZE = 1024, /* Maximum size of an allocation */ MAX_BURST_COUNT = 512, /* max burst count */ MAX_CHECK_TIME = 40, /* (40 * CHECK_INTERVAL) is 10s */ RANDOM_SEED = 0x19560630, /* Random seed */ MEMPOOL_CACHE_SIZE = RTE_MEMPOOL_CACHE_MAX_SIZE / 2, /* Size of mempool cache */ PKT_BUFF_SIZE = 2048, /* Size of packet buffers */ MAX_TIMEOUT_PERIOD = (60 * 60), /* 1 hour max */ CHECK_INTERVAL = 250, /* Link status check interval 250ms */ INTER_FRAME_GAP = 12, /* inter-frame gap in bytes */ START_FRAME_DELIMITER = 1, /* Start Frame Delimiter in bytes*/ PKT_PREAMBLE_SIZE = 7, /* Packet preamble in bytes */ PKT_OVERHEAD_SIZE = (INTER_FRAME_GAP + START_FRAME_DELIMITER + PKT_PREAMBLE_SIZE + RTE_ETHER_CRC_LEN), }; #define Million (uint64_t)(1000000UL) #define Billion (uint64_t)(1000000000UL) enum { LCORE_MODE_UNKNOWN = 0, LCORE_MODE_RX = 1, LCORE_MODE_TX = 2, LCORE_MODE_BOTH = 3 }; typedef struct qstats_s { uint64_t q_ipackets[MAX_QUEUES_PER_PORT]; /** queue Rx packets. */ uint64_t q_ibytes[MAX_QUEUES_PER_PORT]; /** successfully received queue bytes */ uint64_t q_opackets[MAX_QUEUES_PER_PORT]; /** queue Tx packets */ uint64_t q_obytes[MAX_QUEUES_PER_PORT]; /** successfully transmitted queue bytes */ uint64_t q_rx_time[MAX_QUEUES_PER_PORT]; /* Cycles to receive a burst of packets */ uint64_t q_tx_drops[MAX_QUEUES_PER_PORT]; /* Tx dropped packets per queue */ uint64_t q_tx_time[MAX_QUEUES_PER_PORT]; /* Cycles to transmit a burst of packets */ uint64_t q_no_txmbufs[MAX_QUEUES_PER_PORT]; /* Number of times no mbufs were allocated */ } qstats_t __rte_cache_aligned; typedef struct pq_s { /* Port/Queue structure */ qstats_t curr; /* Current statistics */ qstats_t prev; /* Previous statistics */ qstats_t rate; /* Rate statistics */ } pq_t; typedef struct l2p_port_s { rte_atomic16_t inited; /* Port initialized flag */ pthread_spinlock_t tx_lock; /* Tx port lock */ volatile uint16_t tx_inited[MAX_QUEUES_PER_PORT]; /* Tx port initialized flag */ uint16_t pid; /* Port ID attached to lcore */ uint16_t num_rx_qids; /* Number of Rx queues */ uint16_t num_tx_qids; /* Number of Tx queues */ uint16_t mtu_size; /* MTU size */ uint16_t cksum_requires_phdr; /* Flag to indicate if pseudo-header is needed for port */ uint64_t tx_cycles; /* Tx cycles */ uint64_t bpp; /* Bits per packet */ uint64_t pps; /* Packets per second */ uint64_t ppt; /* Packets per thread */ struct rte_mempool *rx_mp[MAX_QUEUES_PER_PORT]; /* Rx pktmbuf mempool per queue */ struct rte_mempool *tx_mp[MAX_QUEUES_PER_PORT]; /* Tx pktmbuf mempool per queue */ struct rte_eth_link link; /* Port link status */ struct rte_ether_addr mac_addr; /* MAC addresses of Port */ struct rte_eth_stats stats; /* Port statistics */ struct rte_eth_stats pstats; /* Previous port statistics */ pq_t pq[MAX_QUEUES_PER_PORT]; /* port/queue information */ } l2p_port_t; typedef struct l2p_lport_s { /* Each lcore has one port/queue attached */ uint16_t mode; /* TXPKTS_MODE_RX or TXPKTS_MODE_TX or BOTH */ uint16_t lid; /* Lcore ID */ uint16_t rx_qid; /* Queue ID attached to Rx lcore */ uint16_t tx_qid; /* Queue ID attached to Tx lcore */ l2p_port_t *port; /* Port structure */ } l2p_lport_t; typedef struct { volatile bool force_quit; /* force quit flag */ bool verbose; /* verbose flag */ uint16_t num_lcores; /* number total of lcores */ uint16_t num_ports; /* number total of ports */ uint16_t num_mappings; /* number total of mappings */ l2p_lport_t *lports[RTE_MAX_LCORE] __rte_cache_aligned; /* Array of lcore/port structure pointers */ l2p_port_t ports[RTE_MAX_ETHPORTS] __rte_cache_aligned; /* Array of port structures */ char *mappings[MAX_MAPPINGS]; /* Array of string port/queue mappings */ /* Configuration values from command line options */ uint32_t mbuf_count; /* Number of mbufs to allocate per port. */ uint32_t mbuf_size; /* Size of the MBUFS being used */ uint16_t tx_rate; /* packet TX rate percentage in whole numbers */ uint16_t promiscuous_on; /* Ports set in promiscuous mode off by default. */ uint16_t jumbo_frame_on; /* Enable jumbo frame support */ uint16_t burst_count; /* Burst size for RX and TX */ uint16_t pkt_size; /* Packet size with FCS */ uint16_t nb_rxd; /* number of RX descriptors */ uint16_t nb_txd; /* number of TX descriptors */ uint16_t timeout_secs; /* Statistics print timeout */ uint16_t ip_proto; /* IP protocol type */ fgen_t *fgen; /* Packet generator */ const char *fgen_file; /* File to use for packet generator */ } txpkts_info_t; extern txpkts_info_t *info; int parse_configuration(int argc, char **argv); void packet_rate(l2p_port_t *port); void print_stats(void); int port_setup(l2p_port_t *port); void packet_constructor(l2p_lport_t *lport, uint8_t *pkt, uint16_t proto); void usage(int err); static __inline__ int pg_socket_id(void) { int sid = rte_socket_id(); return (sid == -1) ? 0 : sid; } static __inline__ int pg_eth_dev_socket_id(int pid) { int sid = rte_eth_dev_socket_id(pid); return (sid == -1) ? 0 : sid; } #ifdef __cplusplus } #endif ================================================ FILE: examples/pktperf/port.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) 2023-2026 Intel Corporation */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * An array of drivers that require a pseudo-header calculation before the checksum calculation. * The names used are the ones used by DPDK. */ static const char *DRIVERS_REQUIRING_PHDR[] = { "net_ixgbe", // TODO: Add the others }; static struct rte_eth_conf port_conf = { .rxmode = { .mq_mode = RTE_ETH_MQ_RX_RSS, .max_lro_pkt_size = RTE_ETHER_MAX_LEN, .offloads = RTE_ETH_RX_OFFLOAD_CHECKSUM, .mtu = RTE_ETHER_MTU, }, .txmode = { .mq_mode = RTE_ETH_MQ_TX_NONE, }, .rx_adv_conf = { .rss_conf = { .rss_key = NULL, .rss_hf = RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP | RTE_ETH_RSS_SCTP | RTE_ETH_RSS_L2_PAYLOAD, }, }, .intr_conf = { .lsc = 0, }, }; /** * Determines whether the pseudo-header is required when calculating the checksum. * Depends on the original NIC driver (e.g., ixgbe NICs expect the pseudo-header) * See Table 1.133: https://doc.dpdk.org/guides/nics/overview.html */ bool is_cksum_phdr_required(const char *driver_name) { size_t num_drivers = RTE_DIM(DRIVERS_REQUIRING_PHDR); for (size_t i = 0; i < num_drivers; i++) { if (DRIVERS_REQUIRING_PHDR[i] == NULL) break; if (strcmp(driver_name, DRIVERS_REQUIRING_PHDR[i]) == 0) return true; } return false; } static uint32_t eth_dev_get_overhead_len(uint32_t max_rx_pktlen, uint16_t max_mtu) { uint32_t overhead_len; if (max_mtu != UINT16_MAX && max_rx_pktlen > max_mtu) overhead_len = max_rx_pktlen - max_mtu; else overhead_len = RTE_ETHER_HDR_LEN + RTE_ETHER_CRC_LEN; return overhead_len; } int port_setup(l2p_port_t *port) { uint16_t pid; struct rte_eth_rxconf rxq_conf; struct rte_eth_txconf txq_conf; struct rte_eth_conf conf = port_conf; struct rte_eth_dev_info dev_info; if (!port) ERR_RET("%s: port is NULL\n", __func__); pid = port->pid; port->mtu_size = RTE_ETHER_MTU; if (rte_atomic16_cmpset(&port->inited.cnt, 0, 1) > 0) { int ret; DBG_PRINT("Initializing port %u\n", pid); ret = rte_eth_dev_info_get(pid, &dev_info); if (ret != 0) ERR_RET("Error during getting device (port %u) info: %s\n", pid, strerror(-ret)); DBG_PRINT("Driver: %s\n", dev_info.driver_name); /* Determines if pseudo-header is needed, based on the driver type */ port->cksum_requires_phdr = is_cksum_phdr_required(dev_info.driver_name); printf(" Checksum offload Pseudo-header required: %s\n", port->cksum_requires_phdr ? "Yes" : "No"); if (info->jumbo_frame_on) { uint32_t eth_overhead_len; uint32_t max_mtu; conf.rxmode.max_lro_pkt_size = JUMBO_ETHER_MTU; eth_overhead_len = eth_dev_get_overhead_len(dev_info.max_rx_pktlen, dev_info.max_mtu); max_mtu = dev_info.max_mtu - eth_overhead_len; printf("Jumbo Frames enabled: Default Max Rx pktlen: %'u, MTU %'u, overhead len: %'u, " "New MTU %'d\n", dev_info.max_rx_pktlen, dev_info.max_mtu, eth_overhead_len, max_mtu); /* device may have higher theoretical MTU e.g. for infiniband */ if (max_mtu > JUMBO_ETHER_MTU) max_mtu = JUMBO_ETHER_MTU; printf("Jumbo Frames enabled: Using Max MTU: %'d", max_mtu); conf.rxmode.mtu = max_mtu; #if 0 // FIXME: Tx performance takes a big hit when enabled if (dev_info.rx_offload_capa & RTE_ETH_RX_OFFLOAD_SCATTER) conf.rxmode.offloads |= RTE_ETH_RX_OFFLOAD_SCATTER; if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MULTI_SEGS) conf.txmode.offloads |= RTE_ETH_TX_OFFLOAD_MULTI_SEGS; #endif } if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE) conf.txmode.offloads |= RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE; #if 0 // FIXME: performance drops a lot when enabled if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_TCP_CKSUM) { printf(" Enabling Tx TCP_CKSUM offload"); conf.txmode.offloads |= RTE_ETH_TX_OFFLOAD_TCP_CKSUM; } if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_UDP_CKSUM) { printf(" Enabling Tx UDP_CKSUM offload\r\n"); conf.txmode.offloads |= RTE_ETH_TX_OFFLOAD_UDP_CKSUM; } if (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_IPV4_CKSUM) { printf(" Enabling Tx IPV4_CKSUM offload\r\n"); conf.txmode.offloads |= RTE_ETH_TX_OFFLOAD_IPV4_CKSUM; } #endif DBG_PRINT("Port %u configure with %u:%u queues\n", pid, port->num_rx_qids, port->num_tx_qids); conf.rx_adv_conf.rss_conf.rss_key = NULL; conf.rx_adv_conf.rss_conf.rss_hf &= dev_info.flow_type_rss_offloads; if (dev_info.max_rx_queues == 1) conf.rxmode.mq_mode = RTE_ETH_MQ_RX_NONE; DBG_PRINT("Port %u configure with mode %" PRIx64 "\n", pid, conf.rx_adv_conf.rss_conf.rss_hf); if (dev_info.max_vfs) { if (conf.rx_adv_conf.rss_conf.rss_hf != 0) conf.rxmode.mq_mode = RTE_ETH_MQ_RX_VMDQ_RSS; } conf.rxmode.offloads &= dev_info.rx_offload_capa; DBG_PRINT("Port %u configure with mode %u\n", pid, conf.rxmode.mq_mode); /* Configure the number of queues for a port. */ ret = rte_eth_dev_configure(pid, port->num_rx_qids, port->num_tx_qids, &conf); if (ret < 0) ERR_RET("Can't configure device: err=%d, port=%u\n", ret, pid); ret = rte_eth_dev_adjust_nb_rx_tx_desc(pid, &info->nb_rxd, &info->nb_txd); if (ret < 0) ERR_RET("Can't adjust number of descriptors: port=%u:%s\n", pid, rte_strerror(-ret)); if ((ret = rte_eth_macaddr_get(pid, &port->mac_addr)) < 0) ERR_RET("Can't get MAC address: err=%d, port=%u\n", ret, pid); DBG_PRINT("Port %u MAC address: " RTE_ETHER_ADDR_PRT_FMT "\n", pid, RTE_ETHER_ADDR_BYTES(&port->mac_addr)); ret = rte_eth_dev_set_ptypes(pid, RTE_PTYPE_UNKNOWN, NULL, 0); if (ret < 0) ERR_RET("Port %u, Failed to disable Ptype parsing\n", pid); DBG_PRINT("Port %u configured with %08x Ptypes\n", pid, RTE_PTYPE_UNKNOWN); if (port->mtu_size < dev_info.min_mtu) { INFO_PRINT("Increasing MTU from %u to %u", port->mtu_size, dev_info.min_mtu); port->mtu_size = dev_info.min_mtu; } if (port->mtu_size > dev_info.max_mtu) { INFO_PRINT("Reducing MTU from %u to %u", port->mtu_size, dev_info.max_mtu); port->mtu_size = dev_info.max_mtu; } if ((ret = rte_eth_dev_set_mtu(pid, port->mtu_size)) < 0) ERR_RET("Cannot set MTU %u on port %u, (%d)%s", port->mtu_size, pid, -ret, rte_strerror(-ret)); DBG_PRINT("Port %u Rx/Tx queues %u/%u\n", pid, port->num_rx_qids, port->num_tx_qids); /* Setup Rx/Tx Queues */ for (int q = 0; q < port->num_rx_qids; q++) { uint32_t sid = pg_eth_dev_socket_id(pid); rxq_conf = dev_info.default_rxconf; rxq_conf.offloads = conf.rxmode.offloads; ret = rte_eth_rx_queue_setup(pid, q, info->nb_rxd, sid, &rxq_conf, port->rx_mp[q]); if (ret < 0) ERR_RET("rte_eth_rx_queue_setup:err=%d, port=%u\n", ret, pid); DBG_PRINT("Port %u:%u configured with %u RX descriptors\n", pid, q, info->nb_rxd); } for (int q = 0; q < port->num_tx_qids; q++) { uint32_t sid = pg_eth_dev_socket_id(pid); txq_conf = dev_info.default_txconf; txq_conf.offloads = conf.txmode.offloads; ret = rte_eth_tx_queue_setup(pid, q, info->nb_txd, sid, &txq_conf); if (ret < 0) ERR_RET("rte_eth_tx_queue_setup:err=%d, port=%u\n", ret, pid); DBG_PRINT("Port %u:%u configured with %u TX descriptors\n", pid, q, info->nb_txd); } if (info->promiscuous_on) { ret = rte_eth_promiscuous_enable(pid); if (ret != 0) DBG_PRINT("INFO: rte_eth_promiscuous_enable:err=%s, port=%u\n", rte_strerror(-ret), pid); else DBG_PRINT("Port %u promiscuous mode enabled\n", pid); } DBG_PRINT("Port %u promiscuous mode is '%s'\n", pid, info->promiscuous_on ? "on" : "off"); /* Start device */ ret = rte_eth_dev_start(pid); if (ret < 0) ERR_RET("rte_eth_dev_start:err=%d, port=%u\n", ret, pid); DBG_PRINT("Port %u started\n", pid); } DBG_PRINT("Port %u initialized\n", pid); return 0; } ================================================ FILE: examples/pktperf/stats.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) 2023-2026 Intel Corporation */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define sprint(name, cntr, nl) \ do { \ qstats_t *r; \ uint64_t total = 0; \ uint16_t nb_queues = (port->num_rx_qids > port->num_tx_qids) ? port->num_rx_qids \ : port->num_tx_qids; \ printf(" %-12s", name); \ for (uint16_t q = 0; q < nb_queues; q++) { \ r = &port->pq[q].rate; \ total += r->cntr[q]; \ printf("|%'12" PRIu64, (r->cntr[q] / info->timeout_secs)); \ } \ printf("|%'14" PRIu64 "|", (total / info->timeout_secs)); \ if (nl) \ printf("\n"); \ fflush(stdout); \ } while (0) /* Print out statistics on packets dropped */ void print_stats(void) { struct rte_eth_stats rate; char link_status_text[RTE_ETH_LINK_MAX_STR_LEN]; char twirl[] = "|/-\\"; static int cnt = 0; const char clr[] = {27, '[', '2', 'J', '\0'}; const char top_left[] = {27, '[', '1', ';', '1', 'H', '\0'}; /* Clear screen and move to top left */ printf("%s%s", clr, top_left); printf("Port : Rate Statistics per queue (%c), PID:%d, ", twirl[cnt++ % 4], getpid()); printf("Size: %'u, Burst: %'u\n", info->pkt_size, info->burst_count); printf(" : MBUFs:%'u Size:%'u Rx/Tx:%'d/%'d TxRate:%u%%\n", info->mbuf_count, info->mbuf_size, info->nb_rxd, info->nb_txd, info->tx_rate); printf(" : Mapping: "); for (int i = 0; i < info->num_mappings; i++) printf("%s ", info->mappings[i]); printf("\n\n"); for (uint16_t pid = 0; pid < info->num_ports; pid++) { l2p_port_t *port = &info->ports[pid]; if (rte_atomic16_read(&port->inited) == 0) { printf("Port %u is not initialized\n", pid); continue; } packet_rate(port); rte_eth_stats_get(port->pid, &port->stats); rate.imissed = port->stats.imissed - port->pstats.imissed; rate.ierrors = port->stats.ierrors - port->pstats.ierrors; rate.oerrors = port->stats.oerrors - port->pstats.oerrors; rate.rx_nombuf = port->stats.rx_nombuf - port->pstats.rx_nombuf; memcpy(&port->pstats, &port->stats, sizeof(struct rte_eth_stats)); uint16_t nb_queues = (port->num_rx_qids > port->num_tx_qids) ? port->num_rx_qids : port->num_tx_qids; for (uint16_t q = 0; q < nb_queues; q++) { qstats_t *c, *p, *r; c = &port->pq[q].curr; p = &port->pq[q].prev; r = &port->pq[q].rate; r->q_opackets[q] = c->q_opackets[q] - p->q_opackets[q]; r->q_obytes[q] = c->q_obytes[q] - p->q_obytes[q]; r->q_ipackets[q] = c->q_ipackets[q] - p->q_ipackets[q]; r->q_ibytes[q] = c->q_ibytes[q] - p->q_ibytes[q]; r->q_no_txmbufs[q] = c->q_no_txmbufs[q] - p->q_no_txmbufs[q]; r->q_tx_drops[q] = c->q_tx_drops[q] - p->q_tx_drops[q]; r->q_tx_time[q] = c->q_tx_time[q]; r->q_rx_time[q] = c->q_rx_time[q]; memcpy(p, c, sizeof(qstats_t)); } memset(&port->link, 0, sizeof(port->link)); if (rte_eth_link_get_nowait(port->pid, &port->link) < 0) { printf("Port %u: Failed to get link status\n", pid); continue; } rte_eth_link_to_str(link_status_text, sizeof(link_status_text), &port->link); printf(" %2u : %s, ", pid, link_status_text); packet_rate(port); printf("MaxPPS:%'" PRIu64 "\n : Pkts/Thread:%'" PRIu64 ", TxCPB:%'" PRIu64 "\n\n", port->pps, port->ppt, port->tx_cycles); printf(" Queue ID "); for (uint16_t q = 0; q < nb_queues; q++) printf("|%10u ", q); printf("| %11s |\n", "Total"); printf(" ------------+"); for (uint16_t q = 0; q < nb_queues; q++) printf("------------+"); printf("--------------+\n"); sprint("RxQs", q_ipackets, 0); if (rate.ierrors) printf(" Err : %'12" PRIu64, rate.ierrors); if (rate.imissed) printf(" Miss: %'12" PRIu64, rate.imissed); printf("\n"); sprint("TxQs", q_opackets, 0); if (rate.oerrors) printf(" Err : %'12" PRIu64, rate.oerrors); printf("\n"); sprint("TxFull", q_tx_drops, 1); // tx_drops mean the TX ring was full sprint("NoTxMBUF", q_no_txmbufs, 1); sprint("RxTime", q_rx_time, 1); sprint("TxTime", q_tx_time, 1); printf("\n"); } fflush(stdout); } ================================================ FILE: examples/pktperf/utils.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) 2023-2026 Intel Corporation */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum { DEFAULT_WND_SIZE = 8192 }; /** * * packet_rate - Calculate the transmit rate. * * DESCRIPTION * Calculate the number of cycles to wait between sending bursts of traffic. * * RETURNS: N/A * * SEE ALSO: */ void packet_rate(l2p_port_t *port) { uint64_t link_speed, bpp, pps, cpb, txcnt; if (!port) return; // link speed in Megabits per second and tx_rate in percentage if (port->link.link_speed == 0 || info->tx_rate == 0) { port->tx_cycles = 0; port->pps = 0; return; } // total link speed in bits per second link_speed = (uint64_t)port->link.link_speed * Million; txcnt = port->num_tx_qids; // bits per packet includes preamble, inter-frame gap, and FCS bpp = ((((uint64_t)info->pkt_size - RTE_ETHER_CRC_LEN) + PKT_OVERHEAD_SIZE) * 8); // packets per second per thread based on requested (tx_rate/txcnt) pps = (((link_speed / bpp) * (info->tx_rate / txcnt)) / 100); pps = ((pps > 0) ? pps : 1); // Make sure pps is not zero // cycles per burst is hz divided by pps times burst count cpb = (rte_get_timer_hz() / pps) * (uint64_t)info->burst_count; port->tx_cycles = cpb * (uint64_t)txcnt; port->pps = pps; port->ppt = pps / txcnt; // packets per thread port->bpp = bpp; DBG_PRINT(" Speed:%'4" PRIu64 " Gbit, BPP: %'6" PRIu64 ", PPS: %'12" PRIu64 ", CPB: %'" PRIu64 "\n", link_speed / Billion, bpp, pps, port->tx_cycles); } static __inline__ long get_rand(long range) { return (random() >> 8) % range; } /* * IPv4/UDP packet * Port Src/Dest : 1234/ 5678 * Pkt Type : IPv4 / UDP * IP Destination : 198.18.1.1 * Source : 198.18.0.1/24 * MAC Destination : 3c:fd:fe:e4:34:c0 * Source : 3c:fd:fe:e4:38:40 */ void packet_constructor(l2p_lport_t *lport, uint8_t *pkt, uint16_t proto) { l2p_port_t *port = lport->port; uint16_t len; char addr[32]; struct rte_ether_hdr *eth; struct rte_ipv4_hdr *ipv4; struct rte_udp_hdr *udp; struct rte_tcp_hdr *tcp; uint16_t tx_qid; if (info->fgen_file) { // Get next frame for buffer. return; } tx_qid = lport->tx_qid; eth = (struct rte_ether_hdr *)pkt; ipv4 = (struct rte_ipv4_hdr *)(pkt + sizeof(struct rte_ether_hdr)); for (unsigned int i = 0; i < RTE_ETHER_MAX_LEN; i++) pkt[i] = (uint8_t)(32 + (i % (127 - 32))); eth->dst_addr.addr_bytes[0] = 0x00; eth->dst_addr.addr_bytes[1] = 0xaa; eth->dst_addr.addr_bytes[2] = 0xbb; eth->dst_addr.addr_bytes[3] = 0xcc; eth->dst_addr.addr_bytes[4] = 0xdd; eth->dst_addr.addr_bytes[5] = tx_qid; memcpy(eth->src_addr.addr_bytes, port->mac_addr.addr_bytes, sizeof(struct rte_ether_addr)); eth->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4); ipv4->version_ihl = 0x45; ipv4->type_of_service = 0; ipv4->total_length = rte_cpu_to_be_16(info->pkt_size - sizeof(struct rte_ether_hdr) - RTE_ETHER_CRC_LEN); ipv4->packet_id = rte_cpu_to_be_16(1); ipv4->fragment_offset = 0; ipv4->time_to_live = 64; ipv4->next_proto_id = proto; ipv4->hdr_checksum = 0; snprintf(addr, sizeof(addr), "192.18.0.%u", (uint8_t)(get_rand(200) + 1)); inet_pton(AF_INET, addr, &ipv4->dst_addr); snprintf(addr, sizeof(addr), "192.18.0.%u", (uint8_t)(get_rand(200) + 1)); inet_pton(AF_INET, addr, &ipv4->src_addr); if (proto == IPPROTO_UDP) { udp = (struct rte_udp_hdr *)(pkt + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr)); udp->src_port = rte_cpu_to_be_16(get_rand(0xFFFE) + 1); udp->dst_port = rte_cpu_to_be_16(get_rand(0xFFFE) + 1); len = (uint16_t)(info->pkt_size - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr) - RTE_ETHER_CRC_LEN); udp->dgram_len = rte_cpu_to_be_16(len); udp->dgram_cksum = 0; udp->dgram_cksum = rte_ipv4_udptcp_cksum(ipv4, (const void *)udp); if (udp->dgram_cksum == 0) udp->dgram_cksum = 0xFFFF; } else if (proto == IPPROTO_TCP) { tcp = (struct rte_tcp_hdr *)(pkt + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr)); tcp->src_port = rte_cpu_to_be_16(get_rand(0xFFFE) + 1); tcp->dst_port = rte_cpu_to_be_16(get_rand(0xFFFE) + 1); tcp->sent_seq = rte_cpu_to_be_32(0); tcp->recv_ack = rte_cpu_to_be_32(0); tcp->data_off = ((sizeof(struct rte_tcp_hdr) / sizeof(uint32_t)) << 4); /* Offset in words */ tcp->tcp_flags = RTE_TCP_ACK_FLAG; tcp->rx_win = rte_cpu_to_be_16(DEFAULT_WND_SIZE); tcp->tcp_urp = 0; tcp->cksum = 0; tcp->cksum = rte_ipv4_udptcp_cksum(ipv4, (const void *)tcp); } ipv4->hdr_checksum = rte_ipv4_cksum(ipv4); } ================================================ FILE: lib/cli/DESIGN.md ================================================ # CLI library (lib/cli) — design and usage This directory contains Pktgen’s interactive CLI library (a DPDK-style “cmdline workalike”). It implements a small shell-like environment with a directory tree of nodes (commands, files, aliases, and directories), history and editing, and TAB completion. If you want the full historical narrative and sample application details, also see: - `lib/cli/cli.rst` (CLI sample application guide) - `lib/cli/cli_lib.rst` (CLI library guide) This document is intended as a **developer-focused** overview of how the code is structured, how to extend it, and how completion and map-driven parsing work in this repository. ## High-level architecture ### Core concepts - **CLI instance (`struct cli`)** - Stored in TLS as `this_cli` (per-lcore/thread instance). - Holds the current working directory, search path, history, environment variables, screen state, and registered command maps. - **Node tree (`struct cli_node`)** - The CLI is organized like a filesystem. - Node types include: directory, command, file, alias, and string nodes. - Nodes are linked in per-directory lists; directories have child lists. - **Executable search path** - Similar to shell `PATH`: the CLI maintains a list of “bin directories” to search for commands. ### Key modules - `cli.c` - Core runtime: building/managing nodes, executing commands, history integration, prompt, etc. - Contains the **command→map registry** used by map-driven tooling. - `cli_input.c`, `cli_vt100.c`, `cli_scrn.c`, `cli_gapbuf.c` - Terminal I/O, editing primitives, cursor movement, and the gap buffer. - `cli_search.c` - Locating nodes by name/path and enumerating directories. - `cli_env.c` - Environment variables and command-line substitution of `$(VAR)`. - `cli_map.c` - Map (pattern) matching for command variants. - `cli_auto_complete.c` - TAB completion for commands/paths and (optionally) map-driven, context-aware token suggestions. ## Creating commands and trees Most applications build an initial tree using the helper macros defined in `cli.h`: - `c_dir("/bin")` / `c_bin("/bin")`: create a directory (bin marks it as executable path) - `c_cmd("show", show_cmd, "...")`: register a command callback - `c_file("copyright", file_cb, "...")`: file node backed by callback - `c_alias("ll", "ls -l", "...")`: shell-like alias - `c_str("FOO", func, "default")`: string node (often used as an env-like value) - `c_end()`: terminator A command callback uses an argv-style signature: ```c int my_cmd(int argc, char **argv); ``` The CLI library does not convert arguments for you. Treat argv tokens as strings and parse as needed. ## Map-driven parsing (cli_map) The CLI supports two common ways to interpret a command line: 1. **Direct argv parsing** in your command callback. 2. **Map-driven selection** using `struct cli_map` and `cli_mapping()`. A map is an array of format strings with an index: ```c static struct cli_map my_map[] = { {10, "show"}, {20, "show %s"}, {30, "show %P stats"}, {40, "show %P %|link|errors|missed stats"}, {-1, NULL} }; ``` The first map entry whose format matches the user’s `argc/argv` is returned, and the index is typically used in a switch statement. ### Format tokens The map format is similar to `printf`, but `%` has CLI-specific meaning: - `%d`, `%D`, `%u`, `%U`, `%b`, `%n`: numeric placeholders - `%h`, `%H`: hex placeholders - `%s`: generic string placeholder - `%c`: comma-separated list (string) - `%m`: MAC-like token - `%4`, `%6`: IPv4/IPv6 token - `%P`, `%C`: portlist/corelist tokens - `%k`: key/value argument blob - `%l`: list string (may require quoting if it contains spaces) - `%|a|b|c`: fixed choice list (one of the options must match) See `cli_map.c` for exact validation behavior. ## Auto-complete (TAB) TAB completion provides two layers: 1. **Shell-like completion** for commands, directories, and files by scanning the CLI tree and search path. 2. **Map-driven completion** when a command has a map registered. ### Registering maps for completion To enable map-driven completion, the CLI needs to know which map applies to a command. The registry lives in the CLI instance and can be populated via: - `cli_register_cmd_map(cmd, map)` - `cli_register_cmd_maps(map)` (registers all command names referenced in the map) Once registered, `cli_auto_complete.c` can interpret “fixed” tokens (e.g., `tcp`, `dst`, `vlan`) vs placeholders (e.g., `%d`, `%P`) and offer: - a list of valid next fixed tokens - placeholder hints such as `` or `` when appropriate ### Placeholder hints When there is no concrete completion (because the next token is a user-provided value), the engine may print a *hint* like `` or ``. In general: - hints are printed when the completion prefix is empty - hints are not inserted into the input line ## Common debugging tips - Use the CLI “help” or map dump mechanisms to verify the expected token order. - If completion suggests an unexpected placeholder hint, it usually means multiple map entries appear compatible with the already-typed tokens. - Ensure fixed keywords are represented as literal tokens or choice tokens in the map. - Ensure placeholders are specific enough (e.g., `%c` for comma-list) rather than `%s`. ## Where to look next - For adding a new command: locate where the application builds its `cli_tree` and add a `c_cmd(...)` entry. - For a command with multiple syntaxes: add/extend its `struct cli_map[]` and use `cli_mapping()` in the callback. - For completion improvements: start in `cli_auto_complete.c`, then check the command’s map registration and token formats. ================================================ FILE: lib/cli/README ================================================ The cli directory is a simple command line interface. Please read the cli.rst and libcli.rst files for more details. ================================================ FILE: lib/cli/cli.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /* Created by Keith Wiles @ intel.com */ /** * @file * CLI core implementation. * * Owns the CLI tree, history, prompt handling, and command execution. * Also includes the command-to-map registry used by map-driven features such * as auto-complete. */ #include #include #include #include #include #include #include #include #include #include "cli.h" #include "cli_input.h" static int (*__dofile_lua)(void *, const char *); struct cli_node_chunk { TAILQ_ENTRY(cli_node_chunk) next; struct cli_node *mem; uint32_t count; }; RTE_DEFINE_PER_LCORE(struct cli *, cli); int cli_register_cmd_map(const char *cmd, struct cli_map *map) { struct cli *cli = this_cli; if (!cli || !cmd || (*cmd == '\0')) return -1; /* Unregister */ if (!map) { for (uint32_t i = 0; i < cli->nb_cmd_maps; i++) { if (cli->cmd_maps[i].cmd && !strcmp(cli->cmd_maps[i].cmd, cmd)) { free(cli->cmd_maps[i].cmd); memmove(&cli->cmd_maps[i], &cli->cmd_maps[i + 1], (cli->nb_cmd_maps - (i + 1)) * sizeof(cli->cmd_maps[0])); cli->nb_cmd_maps--; memset(&cli->cmd_maps[cli->nb_cmd_maps], 0, sizeof(cli->cmd_maps[0])); return 0; } } return 0; } /* Update existing */ for (uint32_t i = 0; i < cli->nb_cmd_maps; i++) { if (cli->cmd_maps[i].cmd && !strcmp(cli->cmd_maps[i].cmd, cmd)) { cli->cmd_maps[i].map = map; return 0; } } /* Grow registry if needed */ if (cli->nb_cmd_maps >= cli->cmd_maps_cap) { uint32_t new_cap = cli->cmd_maps_cap ? (cli->cmd_maps_cap * 2) : (uint32_t)CLI_MAX_CMD_MAPS; if (new_cap < cli->nb_cmd_maps + 1) new_cap = cli->nb_cmd_maps + 1; cli_cmd_map_t *new_maps = realloc(cli->cmd_maps, (size_t)new_cap * sizeof(*new_maps)); if (!new_maps) return -1; /* Zero the new slots */ if (new_cap > cli->cmd_maps_cap) memset(&new_maps[cli->cmd_maps_cap], 0, (size_t)(new_cap - cli->cmd_maps_cap) * sizeof(*new_maps)); cli->cmd_maps = new_maps; cli->cmd_maps_cap = new_cap; } cli->cmd_maps[cli->nb_cmd_maps].cmd = strdup(cmd); if (!cli->cmd_maps[cli->nb_cmd_maps].cmd) return -1; cli->cmd_maps[cli->nb_cmd_maps].map = map; cli->nb_cmd_maps++; return 0; } int cli_register_cmd_maps(struct cli_map *maps) { char token[CLI_NAME_LEN]; char choices[CLI_MAX_PATH_LENGTH + 1]; if (!maps) return -1; for (int mi = 0; maps[mi].fmt != NULL; mi++) { const char *fmt = maps[mi].fmt; const char *sp; size_t len; if (!fmt) continue; while (*fmt == ' ') fmt++; if (*fmt == '\0') continue; sp = strchr(fmt, ' '); len = sp ? (size_t)(sp - fmt) : strlen(fmt); if (len == 0) continue; if (len >= sizeof(token)) len = sizeof(token) - 1; memcpy(token, fmt, len); token[len] = '\0'; /* Skip tokens that are placeholders except for %|choice lists. */ if (token[0] == '%') { if (token[1] != '|') continue; /* Register each choice (e.g. %|seq|sequence). */ snprintf(choices, sizeof(choices), "%s", token + 2); char *saveptr = NULL; for (char *opt = strtok_r(choices, "|", &saveptr); opt != NULL; opt = strtok_r(NULL, "|", &saveptr)) { if (*opt == '\0') continue; if (cli_register_cmd_map(opt, maps) < 0) return -1; } continue; } if (cli_register_cmd_map(token, maps) < 0) return -1; } return 0; } struct cli_map * cli_get_cmd_map(const char *cmd) { struct cli *cli = this_cli; if (!cli || !cmd || (*cmd == '\0')) return NULL; for (uint32_t i = 0; i < cli->nb_cmd_maps; i++) { if (cli->cmd_maps[i].cmd && !strcmp(cli->cmd_maps[i].cmd, cmd)) return cli->cmd_maps[i].map; } return NULL; } int cli_nodes_unlimited(void) { if (!this_cli) return 0; return this_cli->flags & CLI_NODES_UNLIMITED; } /* Allocate a node from the CLI node pool */ static inline struct cli_node * cli_alloc(void) { struct cli *cli = this_cli; struct cli_node *node; node = (struct cli_node *)TAILQ_FIRST(&cli->free_nodes); if (node) TAILQ_REMOVE(&cli->free_nodes, node, next); else if (cli_nodes_unlimited()) { struct cli_node_chunk *chunk; struct cli_node *mem; chunk = calloc(1, sizeof(*chunk)); if (!chunk) return NULL; mem = calloc(CLI_DEFAULT_NB_NODES, sizeof(*mem)); if (!mem) { free(chunk); return NULL; } chunk->mem = mem; chunk->count = CLI_DEFAULT_NB_NODES; TAILQ_INSERT_TAIL(&cli->node_chunks, chunk, next); for (uint32_t i = 0; i < chunk->count; i++) TAILQ_INSERT_TAIL(&cli->free_nodes, &mem[i], next); cli->nb_nodes += chunk->count; node = (struct cli_node *)TAILQ_FIRST(&cli->free_nodes); if (node) TAILQ_REMOVE(&cli->free_nodes, node, next); } return node; } /* Free a node back to the CLI mempool */ static inline void cli_free(struct cli_node *node) { TAILQ_INSERT_TAIL(&this_cli->free_nodes, node, next); } /* Add a directory to the executable path */ int cli_add_bin(struct cli_node *dir) { struct cli *cli = this_cli; if (!dir || !is_directory(dir)) return -1; /* Check for duplicates */ for (int i = 0; i < CLI_MAX_BINS; i++) { struct cli_node *d = cli->bins[i]; if (!d) continue; if (strcmp(d->name, dir->name) == 0) { RTE_LOG(WARNING, EAL, "Adding duplicate bin directory (%s)\n", dir->name); return 0; } } // printf("Searching for free bin directory slot for: %s\n", dir->name); /* Skip first special entry for current working directory */ for (int i = 0; i < CLI_MAX_BINS; i++) { struct cli_node *d = cli->bins[i]; if (d) continue; cli->bins[i] = dir; return 0; } return -1; } /* Remove a directory from the executable path */ int cli_del_bin(struct cli_node *dir) { struct cli *cli = this_cli; if (!dir || !is_directory(dir)) return -1; for (int i = 0; i < CLI_MAX_BINS; i++) if (cli->bins[i] == dir) { cli->bins[i] = NULL; /* compress the list of directories */ if ((i + 1) < CLI_MAX_BINS) { memmove(&cli->bins[i], &cli->bins[i + 1], (CLI_MAX_BINS - (i + 1)) * sizeof(void *)); cli->bins[CLI_MAX_BINS - 1] = NULL; } return 0; } return -1; } /* Add a directory to the executable path using the path string */ int cli_add_bin_path(const char *path) { struct cli_node *node; if (cli_find_node(path, &node)) { if (cli_add_bin(node)) return -1; } else return -1; return 0; } /* Helper routine to remove nodes to the CLI tree */ int cli_remove_node(struct cli_node *node) { struct cli_node *parent, *n; if (!node) return 0; parent = node->parent; if (!parent) /* Can not remove '/' or root */ return -1; switch (node->type) { case CLI_DIR_NODE: if (!TAILQ_EMPTY(&node->items)) while (!TAILQ_EMPTY(&node->items)) { n = TAILQ_FIRST(&node->items); if (cli_remove_node(n)) return -1; } break; case CLI_CMD_NODE: case CLI_FILE_NODE: case CLI_ALIAS_NODE: case CLI_STR_NODE: break; default: return -1; } if (is_directory(node)) cli_del_bin(node); TAILQ_REMOVE(&parent->items, node, next); cli_free(node); return 0; } /* Helper routine to add nodes to the CLI tree */ static struct cli_node * __add_node(const char *name, struct cli_node *parent, int type, cli_funcs_t func, const char *short_desc) { struct cli_node *node; if (!name) return NULL; switch (type) { case CLI_DIR_NODE: if (parent && (strcmp(name, CLI_ROOT_NAME) == 0)) return NULL; if (!parent && strcmp(name, CLI_ROOT_NAME)) return NULL; if (func.cfunc) return NULL; break; case CLI_CMD_NODE: if (!parent || !func.cfunc) return NULL; break; case CLI_FILE_NODE: if (!parent || !func.ffunc) return NULL; break; case CLI_ALIAS_NODE: if (!parent || func.cfunc) return NULL; break; case CLI_STR_NODE: if (!func.sfunc && !short_desc) return NULL; break; default: return NULL; } node = cli_alloc(); if (node == NULL) { cli_printf("%s: No nodes left\n", __func__); return NULL; } node->type = type; node->parent = parent; switch (type) { case CLI_CMD_NODE: case CLI_ALIAS_NODE: node->cfunc = func.cfunc; break; case CLI_FILE_NODE: node->ffunc = func.ffunc; break; case CLI_DIR_NODE: case CLI_STR_NODE: break; default: break; } node->short_desc = short_desc; snprintf(node->name, sizeof(node->name), "%s", name); node->name_sz = strlen(node->name); if (parent) TAILQ_INSERT_HEAD(&parent->items, node, next); return node; } /* Add a directory to the CLI tree */ struct cli_node * cli_add_dir(const char *name, struct cli_node *dir) { struct cli *cli = this_cli; char *argv[CLI_MAX_ARGVS], *p; char path[CLI_MAX_PATH_LENGTH]; int cnt, i; struct cli_node *n, *ret; cli_funcs_t funcs; RTE_ASSERT(cli != NULL); if (!name) return NULL; /* return the last node if directory path already exists */ if (cli_find_node((char *)(uintptr_t)name, &ret)) return ret; /* Set the function structure to NULL */ funcs.cfunc = NULL; p = cli->scratch; if (!dir) /* Passed in a NULL to start at root node */ dir = cli->root.tqh_first; memset(path, '\0', sizeof(path)); p = cli->scratch; /* Grab a local copy of the directory path */ snprintf(p, CLI_MAX_SCRATCH_LENGTH, "%s", name); if (p[0] == '/') { /* Start from root */ dir = cli->root.tqh_first; p++; /* Skip the / in the original path */ path[0] = '/'; /* Add root to the path */ } cnt = pg_strtok(p, "/", argv, CLI_MAX_ARGVS); n = NULL; for (i = 0; i < cnt; i++) { /* Append each directory part to the search path (with bounds + '/') */ size_t used = strnlen(path, sizeof(path)); size_t need_slash = (used > 0 && path[used - 1] != '/'); int wrote; if (need_slash) { wrote = snprintf(path + used, sizeof(path) - used, "/"); if (wrote < 0 || (size_t)wrote >= (sizeof(path) - used)) return NULL; used += (size_t)wrote; } wrote = snprintf(path + used, sizeof(path) - used, "%s", argv[i]); if (wrote < 0 || (size_t)wrote >= (sizeof(path) - used)) return NULL; if (cli_find_node(path, &ret)) { dir = ret; continue; } n = __add_node(argv[i], dir, CLI_DIR_NODE, funcs, NULL); if (n == NULL) break; dir = n; } return n; } /* Add a command executable to the CLI tree */ struct cli_node * cli_add_cmd(const char *name, struct cli_node *dir, cli_cfunc_t func, const char *short_desc) { cli_funcs_t funcs; funcs.cfunc = func; return __add_node(name, dir, CLI_CMD_NODE, funcs, short_desc); } /* Add a command alias executable to the CLI tree */ struct cli_node * cli_add_alias(const char *name, struct cli_node *dir, const char *line, const char *short_desc) { struct cli_node *alias; cli_funcs_t funcs; funcs.cfunc = NULL; alias = __add_node(name, dir, CLI_ALIAS_NODE, funcs, short_desc); alias->alias_str = (const char *)strdup(line); return alias; } /* Add a file to the CLI tree */ struct cli_node * cli_add_file(const char *name, struct cli_node *dir, cli_ffunc_t func, const char *short_desc) { cli_funcs_t funcs; funcs.ffunc = func; return __add_node(name, dir, CLI_FILE_NODE, funcs, short_desc); } /* Add a string to the CLI tree */ int cli_add_str(const char *name, cli_sfunc_t func, const char *str) { return cli_env_string(this_cli->env, name, func, str); } /* Add a directory/commands/files/... to a directory */ int cli_add_tree(struct cli_node *parent, struct cli_tree *tree) { struct cli *cli = this_cli; struct cli_tree *t; struct cli_dir *d; struct cli_cmd *c; struct cli_file *f; struct cli_alias *a; struct cli_str *s; struct cli_node *n; if (!tree) return -1; if (!parent) parent = cli->root.tqh_first; for (t = tree; t->type != CLI_UNK_NODE; t++) { switch (t->type) { case CLI_DIR_NODE: d = &t->dir; if (!(n = cli_add_dir(d->name, parent))) { RTE_LOG(ERR, EAL, "Add directory %s failed\n", d->name); return -1; } if (d->bin) cli_add_bin_path(d->name); parent = n; break; case CLI_CMD_NODE: c = &t->cmd; if (!cli_add_cmd(c->name, parent, c->cfunc, c->short_desc)) { RTE_LOG(ERR, EAL, "Add command %s failed\n", c->name); return -1; } break; case CLI_FILE_NODE: f = &t->file; if (!cli_add_file(f->name, parent, f->ffunc, f->short_desc)) { RTE_LOG(ERR, EAL, "Add file %s failed\n", f->name); return -1; } break; case CLI_ALIAS_NODE: a = &t->alias; if (!cli_add_alias(a->name, parent, a->alias_atr, a->short_desc)) { RTE_LOG(ERR, EAL, "Add alias %s failed\n", a->name); return -1; } break; case CLI_STR_NODE: s = &t->str; if (cli_add_str(s->name, s->sfunc, s->string)) { RTE_LOG(ERR, EAL, "Add string %s failed\n", s->name); return -1; } break; case CLI_UNK_NODE: default: RTE_LOG(ERR, EAL, "Unknown Node type %d\n", t->type); return 0; } } return 0; } /* execute a command or alias node in the CLI tree */ int cli_execute(void) { struct cli *cli = this_cli; struct cli_node *node; int argc, ret, sz; struct gapbuf *gb = cli->gb; char *line = NULL, *p, *hist; RTE_ASSERT(cli != NULL); sz = gb_data_size(gb); sz = RTE_MAX(sz, CLI_MAX_PATH_LENGTH); sz = RTE_MIN(sz, (int)(CLI_MAX_SCRATCH_LENGTH - 1)); line = calloc(1, (size_t)sz + 1); if (!line) return -1; /* gb_copy_to_buf() forces linebuf to be null terminated */ gb_copy_to_buf(gb, line, sz); /* Trim the string of whitespace on front and back */ p = pg_strtrim(line); if (!strlen(p)) goto out_ok; if (p[0] == '#') /* Found a comment line starting with a '#' */ goto out_ok; else if (p[0] == '!') { /* History command */ hist = cli_history_line(atoi(&p[1])); if (!hist) { cli_printf("Unknown history line number %d\n", atoi(&p[1])); goto out_ok; } /* History lines are already trimmed and ready to be executed */ snprintf(line, (size_t)sz + 1, "%s", hist); #ifdef RTE_CLI_HOST_COMMANDS } else if (p[0] == '@') { /* System execute a command */ ret = cli_system(&p[1]); if (!ret) cli_history_add(p); free(line); return ret; #endif } else cli_history_add(p); /* Process the line for environment variable substitution */ cli_env_substitution(cli->env, p, sz - (p - line)); argc = pg_strqtok(p, " \r\n", cli->argv, CLI_MAX_ARGVS); if (!argc) goto out_ok; node = cli_find_cmd(cli->argv[0]); if (!node) { cli_printf("** command not found (%s)\n", cli->argv[0]); ret = -1; goto out; } ret = -1; switch (node->type) { case CLI_CMD_NODE: /* * Reset global optind so getopt works as expected in a node's command function. The * getopt man page says to set optind to 0 instead of 1 if a program scans multiple * argument vectors, which can happen if multiple commands use getopt. */ optind = 0; cli->exe_node = node; ret = node->cfunc(argc, cli->argv); cli->exe_node = NULL; break; case CLI_ALIAS_NODE: /* Delete the alias history line just added */ cli_history_del(); cli->scratch[0] = '\0'; /* Reset scratch to empty */ /* If there is more data after command name save it */ if (gb_data_size(gb) > node->name_sz) gb_copy_to_buf(cli->gb, cli->scratch, gb_data_size(gb)); sz = strlen(cli->scratch); gb_reset_buf(gb); gb_str_insert(gb, (char *)(uintptr_t)node->alias_str, strlen(node->alias_str)); /* Add the extra line arguments */ sz = sz - node->name_sz; if (sz > 0) gb_str_insert(gb, &cli->scratch[node->name_sz], sz); ret = cli_execute(); break; case CLI_DIR_NODE: cli_printf("** (%s) is a directory\n", cli->argv[0]); break; case CLI_FILE_NODE: cli_printf("** (%s) is a file\n", cli->argv[0]); break; case CLI_STR_NODE: cli_printf("** (%s) is a string\n", cli->argv[0]); break; case CLI_UNK_NODE: default: cli_printf("** unknown type (%s)\n", cli->argv[0]); break; } cli_history_reset(); out: free(line); return ret; out_ok: ret = 0; goto out; } /* Main entry point into the CLI system to start accepting user input */ void cli_start(const char *msg) { char c; RTE_ASSERT(this_cli != NULL); cli_printf("\n** Version: %s, %s\n", rte_version(), (msg == NULL) ? "Command Line Interface" : msg); this_cli->plen = this_cli->prompt(0); while (!this_cli->quit_flag) { if (cli_poll(&c)) cli_input(&c, 1); rte_pause(); } cli_printf("\n"); } /* Create a CLI root node for the tree */ struct cli_node * cli_create_root(const char *dirname) { struct cli_node *root; cli_funcs_t funcs; funcs.cfunc = NULL; /* Create and add the root directory */ root = __add_node(dirname, NULL, CLI_DIR_NODE, funcs, NULL); if (!root) return NULL; TAILQ_INSERT_HEAD(&this_cli->root, root, next); /* point at the root directory for current working directory */ set_cwd(root); return root; } /* Default CLI prompt routine */ static int __default_prompt(int cont) { char *str = cli_cwd_path(); int len = 0; if (strlen(str) > 1) /* trim the trailing '/' from string */ str[strlen(str) - 1] = '\0'; scrn_color(SCRN_GREEN, SCRN_NO_CHANGE, SCRN_OFF); len += cli_printf("%s:", (cont) ? " >> " : "DPDK-cli"); scrn_color(SCRN_CYAN, SCRN_NO_CHANGE, SCRN_OFF); len += cli_printf("%s", str); scrn_color(SCRN_DEFAULT_FG, SCRN_DEFAULT_BG, SCRN_OFF); len += cli_printf("> "); return len; } /* Main entry point to create a CLI system */ int cli_init(int nb_entries, uint32_t nb_hist) { int i; size_t size; struct cli *cli; struct cli_node *node; struct cli_node *root; cli = calloc(1, sizeof(struct cli)); if (cli == NULL) rte_exit(EXIT_FAILURE, "Unable to allocate CLI structure\n"); this_cli = cli; cli->prompt = __default_prompt; if (nb_entries == 0) nb_entries = CLI_DEFAULT_NB_NODES; else if (nb_entries == -1) { cli->flags |= CLI_NODES_UNLIMITED; nb_entries = CLI_DEFAULT_NB_NODES; } cli->nb_nodes = nb_entries; if (nb_hist == (uint32_t)CLI_DEFAULT_HISTORY) nb_hist = CLI_DEFAULT_HIST_LINES; size = (nb_entries * sizeof(struct cli_node)); cli->node_mem = calloc(1, size); if (cli->node_mem == NULL) rte_exit(EXIT_FAILURE, "Unable to allocate CLI node structures\n"); cli->nb_hist = nb_hist; TAILQ_INIT(&cli->root); /* Init the directory list */ TAILQ_INIT(&cli->free_nodes); /* List of free nodes */ TAILQ_INIT(&cli->help_nodes); /* List of help nodes */ TAILQ_INIT(&cli->node_chunks); CIRCLEQ_INIT(&cli->free_hist); /* List of free hist nodes */ CIRCLEQ_INIT(&cli->hd_hist); /* Init the history for list head */ if (scrn_create_with_defaults(SCRN_THEME_ON)) goto error_exit; cli->vt = vt100_setup(); if (!cli->vt) goto error_exit; cli->scratch = calloc(1, CLI_MAX_SCRATCH_LENGTH); if (!cli->scratch) goto error_exit; cli->argv = calloc(CLI_MAX_ARGVS, sizeof(void *)); if (!cli->argv) goto error_exit; /* command -> cli_map registry */ cli->cmd_maps = calloc((size_t)CLI_MAX_CMD_MAPS, sizeof(*cli->cmd_maps)); if (!cli->cmd_maps) goto error_exit; cli->cmd_maps_cap = CLI_MAX_CMD_MAPS; /* Create the pool for the number of nodes */ node = cli->node_mem; for (i = 0; i < nb_entries; i++, node++) TAILQ_INSERT_TAIL(&cli->free_nodes, node, next); root = cli_create_root(CLI_ROOT_NAME); if (!root) { RTE_LOG(ERR, EAL, "Unable to create root directory\n"); goto error_exit; } /* Set current working directory to root*/ set_cwd(root); if (cli_set_history(nb_hist)) { RTE_LOG(ERR, EAL, "Unable to create history\n"); goto error_exit; } /* create and initialize the gap buffer structures */ cli->gb = gb_create(); if (!cli->gb) { RTE_LOG(ERR, EAL, "Unable to create Gap Buffer\n"); goto error_exit; } /* Startup the environment system */ cli->env = cli_env_create(); if (!cli->env) goto error_exit; return 0; error_exit: cli_destroy(); return -1; } int cli_create(void) { return cli_init(CLI_DEFAULT_NODES, CLI_DEFAULT_HIST_LINES); } int cli_create_with_defaults(void) { if (cli_init(CLI_DEFAULT_NODES, CLI_DEFAULT_HIST_LINES) == 0) return cli_setup_with_defaults(); return -1; } /* Cleanup the CLI allocation of memory */ void cli_destroy(void) { struct cli *cli = this_cli; if (!cli) return; gb_destroy(cli->gb); vt100_free(cli->vt); cli_history_delete(); free(cli->scratch); free(cli->kill); free(cli->argv); free(cli->hist_mem); free(cli->node_mem); if (cli->cmd_maps) { for (uint32_t i = 0; i < cli->nb_cmd_maps; i++) free(cli->cmd_maps[i].cmd); free(cli->cmd_maps); cli->cmd_maps = NULL; } cli->nb_cmd_maps = 0; cli->cmd_maps_cap = 0; while (!TAILQ_EMPTY(&cli->node_chunks)) { struct cli_node_chunk *chunk = TAILQ_FIRST(&cli->node_chunks); TAILQ_REMOVE(&cli->node_chunks, chunk, next); free(chunk->mem); free(chunk); } free(cli); this_cli = NULL; } int cli_setup(cli_prompt_t prompt, cli_tree_t default_func) { if (!this_cli) return -1; /* Set the user or default prompt routine */ this_cli->prompt = (prompt == NULL) ? __default_prompt : prompt; /* when null call our default tree setup routine */ if (default_func == NULL) default_func = cli_default_tree_init; /* now call the user supplied func or ours if default_func was NULL */ return default_func(); } /* Helper routine around the cli_create() routine */ int cli_setup_with_defaults(void) { return cli_setup(NULL, NULL); } /* Helper routine around the cli_create() routine */ int cli_setup_with_tree(cli_tree_t tree) { return cli_setup(NULL, tree); } /* Add a new prompt routine to the CLI system */ cli_prompt_t cli_set_prompt(cli_prompt_t prompt) { struct cli *cli = this_cli; cli_prompt_t old; old = cli->prompt; /* Save old prompt function */ cli->prompt = prompt; /* Install new prompt function */ if (cli->prompt == NULL) /* Set to default function if NULL */ cli->prompt = __default_prompt; return old; } /** * set the callout function pointer to execute a lua file. */ void cli_set_lua_callback(int (*func)(void *, const char *)) { __dofile_lua = func; } /** * Load and execute a command file or Lua script file. * */ int cli_execute_cmdfile(const char *filename) { if (filename == NULL) return 0; gb_reset_buf(this_cli->gb); cli_printf("\nExecuting '%s'\n", filename); if (strstr(filename, ".lua") || strstr(filename, ".LUA")) { if (!this_cli->user_state) { cli_printf(">>> User State for CLI not set for Lua, please build with Lua enabled\n"); return -1; } if (__dofile_lua) { /* Execute the Lua script file. */ if (__dofile_lua(this_cli->user_state, filename) != 0) return -1; } else cli_printf(">>> Lua is not enabled in configuration!\n"); } else { FILE *fd; char buff[1024]; fd = fopen(filename, "r"); if (fd == NULL) return -1; /* Read and feed the lines to the cmdline parser. */ while (fgets(buff, sizeof(buff), fd)) { cli_input(buff, strlen(buff)); memset(buff, 0, sizeof(buff)); } fclose(fd); } return 0; } int cli_execute_cmdfiles(void) { this_scrn->no_write = 1; for (uint32_t i = 0; i < this_cli->cmd_files.idx; i++) { const char *path; if ((path = this_cli->cmd_files.filename[i]) == NULL) continue; if (cli_execute_cmdfile(path)) { this_scrn->no_write = 0; return -1; } free((char *)(uintptr_t)path); this_cli->cmd_files.filename[i] = NULL; } this_cli->cmd_files.idx = 0; this_scrn->no_write = 0; return 0; } int cli_num_cmdfiles(void) { return this_cli->cmd_files.idx; } ================================================ FILE: lib/cli/cli.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /* Created by Keith Wiles @ intel.com */ #ifndef _CLI_H_ #define _CLI_H_ /** * @file * CLI engine — core structs, node management, and setup API. * * Defines the cli_node and cli directory-tree types, the per-lcore struct cli * instance, and all public functions for initialising, populating, and running * the interactive CLI engine. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define CLI_ROOT_NAME "/" #define CLI_BIN_NAME "bin" enum { CLI_MAX_ARGVS = 64, /**< Max number of args to support */ CLI_DEFAULT_NB_NODES = 256, /**< Default number of nodes */ CLI_DEFAULT_HIST_LINES = 128, /**< Default number of history lines */ CLI_MAX_PATH_LENGTH = 2048, /**< Max path string length */ CLI_MAX_SCRATCH_LENGTH = 4096, /**< Max scratch space length */ CLI_NAME_LEN = 64, /**< Max node name dir/cmd/file/.. */ CLI_MAX_LIST_NODES = 128, /**< Max number of nodes to list */ CLI_MAX_BINS = 32, /**< Max number of bin directories */ CLI_DEFAULT_NODES = 0, /**< Use default node count */ CLI_SCREEN_WIDTH = 80 /**< Size of screen width */ }; enum { /* Initial number of command->map registrations. The registry can grow. */ CLI_MAX_CMD_MAPS = 64 }; #define CLI_RECURSE_FLAG (1 << 0) #define CLI_LONG_LIST_FLAG (1 << 1) /* bitmap for the node type */ typedef enum { CLI_UNK_NODE = 0x0000, /**< Unknown node type */ CLI_DIR_NODE = 0x0001, /**< Directory node type */ CLI_CMD_NODE = 0x0002, /**< Command node type */ CLI_FILE_NODE = 0x0004, /**< File node type */ CLI_ALIAS_NODE = 0x0008, /**< Alias node type */ CLI_STR_NODE = 0x0010, /**< String node type */ } node_type_t; /* Keep this list in sync with the node_type_t enum above */ #define CLI_NODE_TYPES {"Unknown", "Directory", "Command", "File", "Alias", "String", NULL} enum { CLI_EXE_TYPE = (CLI_CMD_NODE | CLI_ALIAS_NODE), CLI_ALL_TYPE = (CLI_EXE_TYPE | CLI_FILE_NODE | CLI_DIR_NODE), CLI_OTHER_TYPE = (CLI_DIR_NODE | CLI_FILE_NODE) }; struct cli; struct cli_node; struct cli_node_chunk; typedef struct { char *cmd; struct cli_map *map; } cli_cmd_map_t; typedef int (*cli_cfunc_t)(int argc, char **argv); /**< CLI function pointer type for a command/alias node */ typedef int (*cli_ffunc_t)(struct cli_node *node, char *buff, int len, uint32_t opt); /**< CLI function pointer type for a file type node */ typedef int (*cli_prompt_t)(int continuation); /**< CLI prompt routine */ typedef int (*cli_tree_t)(void); /**< CLI function pointer type for user initialization */ /* Generic node structure for all node types in the directory tree */ struct cli_node { TAILQ_ENTRY(cli_node) next; /**< link list of commands */ struct cli_node *parent; /**< Parent directory (NULL == ROOT) */ char name[CLI_NAME_LEN]; /**< Name of Node */ uint16_t name_sz; /**< Number of bytes in name w/o null */ uint16_t fstate; /**< File State */ uint16_t fflags; /**< File flags */ uint16_t pad0; node_type_t type; /**< Node Type Root, Dir or cmd */ union { cli_cfunc_t cfunc; /**< Function pointer for commands */ cli_ffunc_t ffunc; /**< Function pointer for files */ cli_sfunc_t sfunc; /**< Function pointer for Strings */ }; const char *short_desc; /**< Short description */ const char *alias_str; /**< Alias string */ size_t foffset; /**< Current offset in file */ size_t file_size; /**< Size of file */ char *file_data; /**< Pointer to file data */ TAILQ_HEAD(, cli_node) items; /**< List of nodes for directory */ } __rte_cache_aligned; /**< Structure for each node type */ #define MAX_CMD_FILES 16 typedef struct { char *filename[MAX_CMD_FILES]; uint32_t idx; } cli_files_t; struct cli { TAILQ_HEAD(, cli_node) root; /**< head of node entries or root */ CIRCLEQ_HEAD(, cli_hist) hd_hist; /**< History circular queue */ uint32_t flags; /**< Flags about CLI */ uint32_t nb_nodes; /**< total number of nodes */ volatile uint16_t quit_flag; /**< When set to non-zero quit */ uint16_t plen; /**< Length of current prompt */ uint32_t nb_hist; /**< total number of history lines */ cli_files_t cmd_files; /**< array of command filename pointers */ struct cli_hist *curr_hist; /**< Current history */ struct cli_node *bins[CLI_MAX_BINS]; /**< Arrays of bin directories, first is the current working directory */ struct cli_node *exe_node; /**< Node currently being executed */ struct cli_env *env; /**< Environment variables */ struct gapbuf *gb; /**< Gap buffer information */ struct cli_vt100 *vt; /**< vt100 information */ char **argv; /**< array of argument string pointers */ cli_prompt_t prompt; /**< Prompt function pointer */ char *scratch; /**< Place to build the path string */ char *kill; /**< strdup() string of last kill data */ struct cli_node *node_mem; /**< Base address of node memory */ struct cli_hist *hist_mem; /**< Base address of history memory */ TAILQ_HEAD(, cli_node_chunk) node_chunks; /**< Extra node chunks when unlimited */ /* command -> cli_map table (growable) */ cli_cmd_map_t *cmd_maps; uint32_t nb_cmd_maps; uint32_t cmd_maps_cap; uint64_t ac_last_tsc; /**< Auto-complete last tab timestamp (cycles) */ uint32_t ac_last_hash; /**< Auto-complete last tab line hash */ TAILQ_HEAD(, help_node) help_nodes; /**< head of help */ TAILQ_HEAD(, cli_node) free_nodes; /**< Free list of nodes */ CIRCLEQ_HEAD(, cli_hist) free_hist; /**< free list of history nodes */ void *user_state; /**< Pointer to some state variable */ } __rte_cache_aligned; /** * Register a cli_map table for a command name. * * This is used by auto-complete and other helpers that want to understand * which tokens are fixed subcommands vs user-supplied values. * * @param cmd * Command name (argv[0]) * @param map * Pointer to a cli_map array (terminated by {-1, NULL}). If NULL, unregister. * @return * 0 on success, -1 on error */ int cli_register_cmd_map(const char *cmd, struct cli_map *map); /** * Register all command names referenced by a cli_map table. * * This scans each map entry's first token (command word) and registers that * command with the provided map table. If the first token is a choice token * (e.g. "%|seq|sequence"), each choice is registered. * * @param maps * Pointer to a cli_map array (terminated by {-1, NULL}). * @return * 0 on success, -1 on error */ int cli_register_cmd_maps(struct cli_map *maps); /** * Lookup a registered cli_map table by command name. * * @param cmd * Command name (argv[0]) * @return * Pointer to cli_map table or NULL */ struct cli_map *cli_get_cmd_map(const char *cmd); RTE_DECLARE_PER_LCORE(struct cli *, cli); #define this_cli RTE_PER_LCORE(cli) /* cli.flags */ #define CLEAR_TO_EOL (1 << 0) #define DISPLAY_PROMPT (1 << 1) #define PROMPT_CONTINUE (1 << 2) #define DELETE_CHAR (1 << 3) #define CLEAR_LINE (1 << 4) #define CLI_NODES_UNLIMITED (1 << 9) /**< Allocate nodes with no limit */ #define CLI_YIELD_IO (1 << 10) #define CLI_DEFAULT_TREE (1 << 11) /** * Set one or more CLI flags. * * @param x * Bitmask of flags to set in this_cli->flags. */ static inline void cli_set_flag(uint32_t x) { this_cli->flags |= x; } /** * Clear one or more CLI flags. * * @param x * Bitmask of flags to clear in this_cli->flags. */ static inline void cli_clr_flag(uint32_t x) { this_cli->flags &= ~x; } /** * Test whether any of the given CLI flags are set. * * @param x * Bitmask of flags to test. * @return * Non-zero if any flag in @p x is set, zero otherwise. */ static inline int cli_tst_flag(uint32_t x) { return this_cli->flags & x; } typedef union { cli_cfunc_t cfunc; /**< Function pointer for commands */ cli_ffunc_t ffunc; /**< Function pointer for files */ cli_sfunc_t sfunc; /**< Function pointer for strings */ } cli_funcs_t; /* Internal: Used in argument list for adding nodes */ struct cli_dir { const char *name; /**< directory name */ uint8_t bin; }; struct cli_cmd { const char *name; /**< Name of command */ cli_cfunc_t cfunc; /**< Function pointer */ const char *short_desc; /**< Short description */ }; /**< List of commands for cli_add_cmds() */ struct cli_alias { const char *name; /**< Name of command */ const char *alias_atr; /**< Alias string */ const char *short_desc; /**< Short description */ }; /**< List of alias for cli_add_aliases() */ struct cli_file { const char *name; /**< Name of command */ cli_ffunc_t ffunc; /**< Read/Write function pointer */ const char *short_desc; /**< Short description */ }; /**< List of alias for cli_add_aliases() */ struct cli_str { const char *name; /**< Name of command */ cli_sfunc_t sfunc; /**< Function pointer */ const char *string; /**< Default string */ }; /**< List of commands for cli_add_str() */ struct cli_tree { node_type_t type; /**< type of node to create */ union { struct cli_dir dir; /**< directory and bin directory */ struct cli_cmd cmd; /**< command nodes */ struct cli_file file; /**< file nodes */ struct cli_alias alias; /**< alias nodes */ struct cli_str str; /**< string node */ }; }; /**< Used to help create a directory tree */ #define c_dir(n) {CLI_DIR_NODE, .dir = {(n), 0}} #define c_bin(n) {CLI_DIR_NODE, .dir = {(n), 1}} #define c_cmd(n, f, h) \ { \ CLI_CMD_NODE, .cmd = {(n), (f), (h) } \ } #define c_file(n, rw, h) \ { \ CLI_FILE_NODE, .file = {(n), (rw), (h) } \ } #define c_alias(n, l, h) \ { \ CLI_ALIAS_NODE, .alias = {(n), (l), (h) } \ } #define c_str(n, f, s) \ { \ CLI_STR_NODE, .str = {(n), (f), (s) } \ } #define c_end() {CLI_UNK_NODE, .dir = {NULL, 0}} /** * Store an arbitrary user-state pointer in the CLI instance. * * @note Uses thread variable this_cli. * * @param val * Pointer to store as the user state value. */ static inline void cli_set_user_state(void *val) { this_cli->user_state = val; } /** * CLI root directory node. * * @note Uses thread variable this_cli. * * @return * Pointer to current working directory. */ static inline struct cli_node * get_root(void) { RTE_ASSERT(this_cli != NULL); return this_cli->root.tqh_first; } /** * CLI current working directory. * * @note Uses thread variable this_cli. * * @return * Pointer to current working directory. */ static inline struct cli_node * get_cwd(void) { RTE_ASSERT(this_cli != NULL); return this_cli->bins[0]; } /** * set CLI current working directory. * * @note Uses thread variable this_cli. * * @param node * Pointer to set as the current working directory * @return * None */ static inline void set_cwd(struct cli_node *node) { RTE_ASSERT(this_cli != NULL); this_cli->bins[0] = node; } /** * Check if this_cli pointer is valid * * @return * 1 if true else 0 */ static inline int is_cli_valid(void) { return (this_cli) ? 1 : 0; } /** * Helper routine to compare two strings exactly * * @param s1 * Pointer to first string. * @param s2 * Pointer to second string. * @return * 0 failed to compare and 1 is equal. */ static inline int is_match(const char *s1, const char *s2) { if (!s1 || !s2) return 0; while ((*s1 != '\0') && (*s2 != '\0')) { if (*s1++ != *s2++) return 0; } if (*s1 != *s2) return 0; return 1; } /** * Test if the node is of a given type(s) * * @param node * Pointer the cli_node structure * @return * True if node is one of the types given */ static inline int is_node(struct cli_node *node, uint32_t types) { return node->type & types; } /** * Test if the node is a command * * @param node * Pointer the cli_node structure * @return * True if command else false if not */ static inline int is_command(struct cli_node *node) { return is_node(node, CLI_CMD_NODE); } /** * Test if the node is alias * * @param node * Pointer the cli_node structure * @return * True if alias else false if not */ static inline int is_alias(struct cli_node *node) { return is_node(node, CLI_ALIAS_NODE); } /** * Test if the node is a file * * @param node * Pointer the cli_node structure * @return * True if a file else false if not */ static inline int is_file(struct cli_node *node) { return is_node(node, CLI_FILE_NODE); } /** * Test if the node is directory * * @param node * Pointer the cli_node structure * @return * True if directory else false if not */ static inline int is_directory(struct cli_node *node) { return is_node(node, CLI_DIR_NODE); } /** * Test if the node is executable * * @param node * Pointer the cli_node structure * @return * True if executable else false if not */ static inline int is_executable(struct cli_node *node) { return is_command(node) || is_alias(node); } /** * Print out the short description for commands. * * @note Uses thread variable this_cli. * * @return * -1 just to remove code having to return error anyway. */ static inline int cli_usage(void) { if (this_cli && this_cli->exe_node) { const char *p = this_cli->exe_node->short_desc; cli_printf(" Usage: %s\n", (p) ? p : "No description found"); } return -1; } /** * Return the string for the given node type * * @param node * struct cli_node pointer * @return * String for the node type. */ static inline const char * cli_node_type(struct cli_node *node) { const char *node_str[] = CLI_NODE_TYPES; switch (node->type) { case CLI_UNK_NODE: default: break; case CLI_DIR_NODE: return node_str[1]; case CLI_CMD_NODE: return node_str[2]; case CLI_FILE_NODE: return node_str[3]; case CLI_ALIAS_NODE: return node_str[4]; } return node_str[0]; } /** * Create the current working directory string, which is the complete * path to node. Uses CLI routines to output the string to the console. * * @note Uses thread variable this_cli. * * @param node * Starting node or last file/dir to be printed * @param path * Pointer to a path buffer string. * @return * Return the pointer to the cli->scratch buffer or buf with path string. */ static inline char * cli_path_string(struct cli_node *node, char *path) { if (!path) path = this_cli->scratch; if (!node) node = get_cwd(); if (node->parent) { cli_path_string(node->parent, path); strcat(path, node->name); strcat(path, "/"); } else strcpy(path, "/"); return path; } /** * Return the path string for the current working directory. * * @note Uses thread variable this_cli. * * @return * Pointer to the scratch buffer containing the CWD path string. */ static inline char * cli_cwd_path(void) { return cli_path_string(get_cwd(), NULL); } /** * Print the current working directory string, which is the complete * path to node. Uses CLI routines to output the string to the console. * * @note Uses thread variable this_cli. * * @param node * Starting node or last file/dir to be printed * @return * N/A. */ static inline void cli_pwd(struct cli_node *node) { cli_printf("%s", cli_path_string(node, NULL)); } /** * Set the number of lines in history * * @note Uses thread variable this_cli. * * @param nb_hist * Number of lines in history if zero disable history. * @return * zero on success or -1 on error */ static inline int cli_set_history_size(uint32_t nb_hist) { return cli_set_history(nb_hist); } /** * Get the total number of lines in history * * @note Uses thread variable this_cli. * * @return * total number of line for history */ static inline uint32_t cli_get_history_size(void) { return this_cli->nb_hist; } /** * List the history lines * * @note Uses thread variable this_cli. * * @return * N/A */ static inline void cli_history_list(void) { cli_history_dump(); } /** * Return the CLI root node. * * @return * Pointer to root node. */ static inline struct cli_node * cli_root_node(void) { return this_cli->root.tqh_first; } /** * Create the CLI engine * * @param nb_entries * Total number of commands, files, aliases and directories. If 0 then use * the default number of nodes. If -1 then unlimited number of nodes. * @param nb_hist * The number of lines to keep in history. If zero then turn off history. * If the value is CLI_DEFAULT_HISTORY use CLI_DEFAULT_HIST_LINES * @return * 0 on success or -1 */ int cli_init(int nb_entries, uint32_t nb_hist); /** * Create the CLI engine with defaults * * @return * 0 on success or -1 */ int cli_create_with_defaults(void); /** * Create the CLI engine using compiled-in defaults. * * @return * 0 on success or -1 on error */ int cli_create(void); /** * Configure the CLI with a prompt function and optional default tree. * * @param prompt * Function pointer for displaying the CLI prompt, or NULL for default. * @param default_func * Function pointer to populate the initial command tree, or NULL. * @return * 0 on success or -1 on error */ int cli_setup(cli_prompt_t prompt, cli_tree_t default_func); /** * Create the CLI engine using system defaults. * * @return * 0 on success or -1 */ int cli_setup_with_defaults(void); /** * Create the CLI engine using system defaults and supplied tree init function. * * @param tree * The user supplied function to init the tree or can be NULL. If NULL then * a default tree is initialized with default commands. * @return * 0 on success or -1 */ int cli_setup_with_tree(cli_tree_t tree); /** * Set the CLI prompt function pointer * * @param prompt * Function pointer to display the prompt * @return * Return the old prompt function pointer or NULL if one does not exist */ cli_prompt_t cli_set_prompt(cli_prompt_t prompt); /** * Create the root directory * * @note Uses thread variable this_cli. * * @param dirname * Name of root directory, if null uses '/' * @return * NULL on error or the root node on success. */ struct cli_node *cli_create_root(const char *dirname); /** * Create the default directory tree * * @note Uses thread variable this_cli. * * @return * 0 on success or non-zero on error */ int cli_default_tree_init(void); /** * Destroy the CLI engine * * @note Uses thread variable this_cli. * * @return * N/A */ void cli_destroy(void); /** * Start the CLI running * * @note Uses thread variable this_cli. * * @param msg * User message to be displayed on startup * @return * N/A */ void cli_start(const char *msg); /** * Execute command line string in cli->input * * @note Uses thread variable this_cli. * * @return * zero on success or -1 on error */ int cli_execute(void); /** * Add a bin directory to the bin list * * @note Uses thread variable this_cli. * * @param node * Directory to add to bin list * @return * 0 is ok, -1 is full */ int cli_add_bin(struct cli_node *node); /** * Remove a bin directory from the bin list * * @note Uses thread variable this_cli. * * @param node * Directory to add to bin list * @return * 0 is ok, -1 is not found */ int cli_del_bin(struct cli_node *node); /** * Add a bin directory to the bin list using path * * @note Uses thread variable this_cli. * * @param path * path to bin directory to add, must exist first. * @return * 0 is ok, -1 is full */ int cli_add_bin_path(const char *path); /** * Add a cli directory * * @note Uses thread variable this_cli. * * @param dirname * String pointing to the directory name * @param parent * Parent node of the new directory * @return * pointer to directory entry or NULL on error */ struct cli_node *cli_add_dir(const char *dirname, struct cli_node *parent); /** * Add a command to a directory * * @note Uses thread variable this_cli. * * @param name * Pointer to command name string * @param dir * Directory node pointer * @param func * Pointer to function to execute * @param short_desc * Short string for help to display * @return * NULL on error or the node address for the command */ struct cli_node *cli_add_cmd(const char *name, struct cli_node *dir, cli_cfunc_t func, const char *short_desc); /** * Add an alias string or special command type * * @note Uses thread variable this_cli. * * @param name * Pointer to command name string * @param dir * Directory node pointer * @param line * Pointer to alias string * @param short_desc * Short string for help to display * @return * NULL on error or the node address for the command */ struct cli_node *cli_add_alias(const char *name, struct cli_node *dir, const char *line, const char *short_desc); /** * Add an file to a directory * * @note Uses thread variable this_cli. * * @param name * Pointer to command name string * @param dir * Directory node pointer * @param func * Pointer to a function attached to the file. * @param short_desc * Short string for help to display * @return * NULL on error or the node pointer. */ struct cli_node *cli_add_file(const char *name, struct cli_node *dir, cli_ffunc_t func, const char *short_desc); /** * Add a string to the system. * * @note Uses thread variable this_cli. * * @param name * Pointer to command name string * @param dir * Directory node pointer * @param func * Pointer to a function attached to the string. * @param str * Value of string if no function defined. * @return * NULL on error or the node pointer. */ int cli_add_str(const char *name, cli_sfunc_t func, const char *str); /** * Add a list of nodes to a directory * * @note Uses thread variable this_cli. * * @param dir * Node pointer to directory for add commands * @param treee * Pointer to list of nodes to add to the tree * @return * -1 on error or 0 for OK */ int cli_add_tree(struct cli_node *dir, struct cli_tree *tree); /** * Add filenames to the CLI command list. * * @param filename * Path of command file. * @return * 0 is OK and -1 if error */ static inline int cli_add_cmdfile(const char *filename) { if (this_cli->cmd_files.idx >= MAX_CMD_FILES) return -1; this_cli->cmd_files.filename[this_cli->cmd_files.idx++] = strdup(filename); return 0; } /** * execute a command file * * @note Uses thread variable this_cli. * * @param path * Pointer to path to file * @return * 0 on OK or -1 on error */ int cli_execute_cmdfile(const char *path); /** * execute a list for command files * * @note Uses thread variable this_cli. * * @return * 0 on OK or -1 on error */ int cli_execute_cmdfiles(void); /** * Return the number of command files registered with cli_add_cmdfile(). * * @return * Count of pending command files. */ int cli_num_cmdfiles(void); /** * Remove a node from the directory tree * * @note Uses thread variable this_cli. * * @param node * The pointer to the node to remove * @return * 0 on OK and -1 on error */ int cli_remove_node(struct cli_node *node); /** * return true if allocating unlimited nodes are enabled. * * @note Uses thread variable this_cli. * * @return * non-zero if true else 0 */ int cli_nodes_unlimited(void); /** * shutdown the CLI command interface. * */ static inline void cli_quit(void) { this_cli->quit_flag = 1; } /** * Register a Lua evaluation callback used by the CLI Lua command. * * @param func * Function pointer with signature int(void *lua_state, const char *script). * Pass NULL to unregister. */ void cli_set_lua_callback(int (*func)(void *, const char *)); #ifdef __cplusplus } #endif #endif /* _CLI_H_ */ ================================================ FILE: lib/cli/cli.rst ================================================ .. BSD LICENSE Copyright(c) <2016-2026>, Intel Corporation. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Intel Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. CLI Sample Application =============================== CLI stands for "Command Line Interface". This chapter describes the CLI sample application that is part of the Data Plane Development Kit (DPDK). The CLI is a workalike replacement for cmdline library in DPDK and has a simpler programming interface and programming model. The primary goal of CLI is to allow the developer to create commands quickly and with very little compile or runtime configuration. Using standard Unix* like constructs which are very familar to the developer. Allowing the developer to construct a set of commands for development or deployment of the application. The CLI design uses a directory like design instead of a single level command line interface. Allowing the developer to use a directory style solution to controlling a DPDK application. The directory style design is nothing new, but it does have some advantages. One advantage allows the directory path for the command to be part of the information used in executing the command. The next advantage is creating directories to make a hierarchy of commands, plus allowing whole directroy trees to dynamically come and go as required by the developer. Some of the advantages are: * CLI has no global variable other then the single thread variable called *this_cli* which can only be accessed from the thread which created the CLI instance. * CLI supports commands, files, aliases, directories. - The alias command is just a string using a simple substitution support for other commands similar to the bash shell like alias commands. - Files can be static or dynamic information, can be changed on the fly and saved for later. The file is backed with a simple function callback to allow the developer to update the content or not. * Added support for color and cursor movement APIs similar to Pktgen if needed by the developer. * It is a work alike replacement for cmdline library. Both cmdline and CLI can be used in the same application if care is taken. * Uses a simple fake like directory layout for command and files. Allowing for command hierarchy as path to the command can allow for specific targets to be identified without having to state it on the command line. * Has auto-complete for commands, similar to Unix/Linux autocomplete and provides support for command option help as well. * Callback functions for commands are simply just argc/argv like functions. - The CLI does not convert arguments for the user, it is up to the developer to decode the argv[] values. - Most of the arguments converted in the current cmdline are difficult to use or not required as the developer just picks string type and does the conversion himself. * Dynamically be able to add and remove commands, directories, files and aliases, does not need to be statically compiled into the application. * No weird structures in the code and reduces the line count for testpmd from 12K to 4.5K lines. I convert testpmd to have both CMDLINE and CLI with a command line option. * Two methods to parse command lines, first is the standard argc/argv method in the function. - The second method is to use a map of strings with simple printf like formatting to detect which command line the user typed. - An ID value it returned to the used to indicate which mapping string was found to make the command line to be used in a switch statement. * Environment variable support using the **env** command or using an API. * Central help support if needed (optional). Overview -------- The CLI sample application is a simple application that demonstrates the use of the command line interface in the DPDK. This application is a readline-like interface that can be used to control a DPDK application. One of the advantages of CLI over Cmdline is it is dynamic, which means nodes or items can be added and removed on the fly. Which allows adding new directories, file or commands as needed or removing these items at runtime. The CLI has no global modifiable variable as the one global pointer is a thread based variable. Which allows the developer to have multiple CLI commands per thread if needed. Another advantage is the calling of the backend function to support a command is very familar to developers as it is basically just a argc/argv style command and the developer gets the complete command line. One other advantage is the use of MAP structures, to help identify commands quickly plus allowing the developer to define new versions of commands and be able to identify these new versions using a simple identifier value. Look at the sample application to see a simple usage. Another advantage of CLI is how simple it is to add new directroies, files and commands for user development. The basic concept is for the developer to use standard Unix like designs. To add a command a developer needs to add an entry to the cli_tree_t structure and create a function using the following prototype: .. code-block:: c int user_cmd(int argc, char **argv); The argc/argv is exactly like the standard usage in a Unix* system, which allows for using getopt() and other standard functions. The Cmdline structures and text conversions were defined at compile time in most cases. In CLI the routine is passed the argc/argv information to convert these options as needed. The cli variable being a thread Local Storage (TLS) all user routines a CLI routine only need to access the thread variable to eliminate needing a global variable to reference the specific CLI instance and passing the value in the API. The user can also set environment variables using the **env** command. These variables are also parsed in the command line a direct substitution is done. The CLI system also has support for simple files along with alias like commands. These alias commands are fixed strings which are executed instead of a function provided by the developer. If the user has more arguments these are appended to the alias string and processed as if typed on the command line. .. note:: The CLI library was designed to be used in production code and the Cmdline was not validated to the same standard as other DPDK libraries. The goal is to provide a production CLI design. The CLI sample application supports some of the features of the Cmdline library such as, completion, cut/paste and some other special bindings that make configuration and debug faster and easier. The CLI design uses some very simple VT100 control strings for displaying data and accepting input from the user. Some of the control strings are used to clear the screen or line and position the cursor on a VT100 compatible terminal. The CLI screen code also supports basic color and many other VT100 commands. The application also shows how the CLI application can be extended to handle a list of commands and user input. The example presents a simple command prompt **DPDK-cli:/>** similar to a Unix* shell command along with a directory like file system. Some of the **default** commands contained under /sbin directory are: * **ls**: list the current or provided directory files/commands. * **cd**: Change directory command. * **pwd**: print out the current working directory. * **history**: List the current command line history if enabled. * **more**: A simple command to page contents of files. * **help**: display a the help screen. * **quit**: exit the CLI application, also **Ctrl-x** will exit as well. * **mkdir**: add a directory to the current directory. * **delay**: wait for a given number of microseconds. * **sleep**: wait for a given number of seconds. * **rm**: remove a directory, file or command. Removing a file will delete the data. * **cls**: clear the screen and redisplay the prompt. * **version**: Display the current DPDK version being used. * **path**: display the current search path for executable commands. * **cmap**: Display the current system core and socket information. * **hugepages**: Display the current hugepage information. * **sizes**: a collection system structure and buffer sizes for debugging. * **copyright**: a file containing DPDK copyright information. * **env**: a command show/set/modify the environment variables. Some example commands under /bin directory are: * **ll**: an alias command to display long ls listing **ls -l** * **h**: alias command for **history** * **hello**: a simple Hello World! command. * **show**: has a number of commands using the map feature. Under the /data directory is: * **pci**: a simple example file for displaying the **lspci** command in CLI. .. note:: To terminate the application, use **Ctrl-x** or the command **quit**. Auto completion --------------- CLI does support auto completion at the file or directory level, meaning the arguments to commands are not expanded as was done in Cmdline code. The CLI auto completion works similar to the standard Unix* system by expanding commands and directory paths. In normal Unix* like commands the user needs to execute the command asking for the help information and CLI uses this method. Special command features ------------------------ Using the '!' followed by a number from the history list of commands you can execute that command again. Using the UP/Down arrows the user can quickly find and execute or modify a previous command in history. The user can also execute host level commands if enabled using the '@' prefix to a command line e.g. @ls or @lspci or ... line is passed to popen or system function to be executed and the output displayed on the console if any output. Compiling the Application ------------------------- #. Go to example directory: .. code-block:: console export RTE_SDK=/path/to/rte_sdk cd ${RTE_SDK}/examples/cli #. Set the target (a default target is used if not specified). For example: .. code-block:: console export RTE_TARGET=x86_64-native-linux-gcc or export RTE_TARGET=x86_64-native-linuxapp-gcc Refer to the *DPDK Getting Started Guide* for possible RTE_TARGET values. #. Build the application: .. code-block:: console make Running the Application ----------------------- To run the application in linux environment, issue the following command: .. code-block:: console $ ./build/cli .. note:: The example cli application does not require to be run as superuser as it does not startup DPDK by calling rte_eal_init() routine. Which means it also does not use DPDK features except for a few routines not requiring EAL initialization. Refer to the *DPDK Getting Started Guide* for general information on running applications and the Environment Abstraction Layer (EAL) options. Explanation ----------- The following sections provide some explanation of the code. EAL Initialization and cmdline Start ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The first task is the initialization of the Environment Abstraction Layer (EAL), if required for the application. .. code-block:: c int main(int argc, char **argv) { if (cli_create_with_tree(init_tree) ==0) { cli_start(NULL, 0); /* NULL is some init message done only once */ /* 0 means do not use color themes */ cli_destroy(); } ... The cli_start() function returns when the user types **Ctrl-x** or uses the quit command in this case, the application exits. The cli_create() call takes four arguments and each has a default value if not provided. The API used here is the cli_create_with_tree(), which uses defaults for three of the arguments. .. code-block:: c /** * Create the CLI engine * * @param prompt_func * Function pointer to call for displaying the prompt. * @param tree_func * The user supplied function to init the tree or can be NULL. If NULL then * a default tree is initialized with basic commands. * @param nb_entries * Total number of commands, files, aliases and directories. If 0 then use * the default number of nodes. If -1 then unlimited number of nodes. * @param nb_hist * The number of lines to keep in history. If zero then turn off history. * If the value is CLI_DEFAULT_HISTORY use CLI_DEFAULT_HIST_LINES * @return * 0 on success or -1 */ int cli_create(cli_prompt_t prompt_func, cli_tree_t tree_func, int nb_entries, uint32_t nb_hist); The cli_create_with_tree() has only one argument which is the structure to use in order to setup the initial directory structure. Also the wrapper function int cli_create_with_defaults(void) can be used as well. Consult the cli.h header file for the default values. Also the alias node is a special alias file to allow for aliasing a command to another command. The tree init routine is defined like: .. code-block:: c static struct cli_tree my_tree[] = { c_dir("/data"), c_file("pci", pci_file, "display lspci information"), c_dir("/bin"), c_cmd("hello", hello_cmd, "Hello-World!!"), c_alias("h", "history", "display history commands"), c_alias("ll", "ls -l", "long directory listing alias"), c_end() }; static int init_tree(void) { /* * Root is created already and using system default cmds and dirs, the * developer is not required to use the system default cmds/dirs. */ if (cli_default_tree_init()) return -1; /* Using NULL here to start at root directory */ if (cli_add_tree(NULL, my_tree)) return -1; cli_help_add("Show", show_map, show_help); return cli_add_bin_path("/bin"); } The above structure is used to create the tree structure at initialization time. The struct cli_tree or cli_tree_t typedef can be used to setup a new directory tree or argument the default tree. The elements are using a set of macros c_dir, c_file, c_cmd, c_alias and c_end. These macros help fill out the cli_tree_t structure for the given type of item. The developer can create his own tree structure with any commands that are needed and/or call the cli_default_tree_init() routine to get the default structure of commands. If the developer does not wish to call the default CLI routine, then he must call the cli_create_root() function first before adding other nodes. Other nodes can be added and removed at anytime. CLI Map command support ~~~~~~~~~~~~~~~~~~~~~~~ The CLI command has two types of support to handle arguments normal argc/argv and the map system. As shown above the developer creates a directory tree and attaches a function to a command. The function takes the CLI pointer plus the argc/argv arguments and the developer can just parse the arguments to decode the command arguments. Sometimes you have multiple commands or different versions of a command being handled by a single routine, this is were the map support comes into play. The map support defines a set of struct cli_map map[]; to help detect the correct command from the user. In the list of cli_map structures a single structure contains two items a developer defined index value and a command strings. The index value is used on the function to identify the specific type of command found in the list. The string is a special printf like string to help identify the command typed by the user. One of the first things todo in the command routine is to call the cli_mapping() function passing in the CLI pointer and the argc/argv values.The two method can be used at the same time. The cli_mapping() command matches up the special format string with the values in the argc/argv array and returns the developer supplied index value or really the pointer the struct cli_map instance. Now the developer can use the cli_map.index value in a switch() statement to locate the command the user typed or if not found a return of -1. Example: .. code-block:: c static int hello_cmd(int argc, char **argv) { int i, opt; optind = 1; while((opt = getopt(argc, argv, "?")) != -1) { switch(opt) { case '?': cli_usage(); return 0; default: break; } } cli_printf("Hello command said: Hello World!! "); for(i = 1; i < argc; i++) cli_printf("%s ", argv[i]); cli_printf("\n"); return 0; } static int pci_file(struct cli_node *node, char *buff, int len, uint32_t opt) { if (is_file_open(opt)) { FILE *f; if (node->file_data && (node->fflags & CLI_FREE_DATA)) free(node->file_data); node->file_data = calloc(1, 32 * 1024); if (!node->file_data) return -1; node->file_size = 32 * 1024; node->fflags = CLI_DATA_RDONLY | CLI_FREE_DATA; f = popen("lspci", "r"); if (!f) return -1; node->file_size = fread(node->file_data, 1, node->file_size, f); pclose(f); return 0; } return cli_file_handler(node, buff, len, opt); } static struct cli_map show_map[] = { { 10, "show %P" }, { 20, "show %P mac %m" }, { 30, "show %P vlan %d mac %m" }, { 40, "show %P %|vlan|mac" }, { -1, NULL } }; static const char *show_help[] = { "show ", "show mac ", "show vlan mac ", "show [vlan|mac]", NULL }; static int show_cmd(int argc, char **argv) { struct cli_map *m; uint32_t portlist; struct rte_ether_addr mac; m = cli_mapping(Show_info.map, argc, argv); if (!m) return -1; switch(m->index) { case 10: portlist_parse(argv[1], pktgen.nb_ports, &portlist); cli_printf(" Show Portlist: %08x\n", portlist); break; case 20: portlist_parse(argv[1], pktgen.nb_ports, &portlist); pg_ether_aton(argv[3], &mac); cli_printf(" Show Portlist: %08x, MAC: " "%02x:%02x:%02x:%02x:%02x:%02x\n", portlist, mac.addr_bytes[0], mac.addr_bytes[1], mac.addr_bytes[2], mac.addr_bytes[3], mac.addr_bytes[4], mac.addr_bytes[5]); break; case 30: portlist_parse(argv[1], pktgen.nb_ports, &portlist); pg_ether_aton(argv[5], &mac); cli_printf(" Show Portlist: %08x vlan %d MAC: " "%02x:%02x:%02x:%02x:%02x:%02x\n", portlist, atoi(argv[3]), mac.addr_bytes[0], mac.addr_bytes[1], mac.addr_bytes[2], mac.addr_bytes[3], mac.addr_bytes[4], mac.addr_bytes[5]); break; case 40: portlist_parse(argv[1], pktgen.nb_ports, &portlist); pg_ether_aton("1234:4567:8901", &mac); cli_printf(" Show Portlist: %08x %s: ", portlist, argv[2]); if (argv[2][0] == 'm') cli_printf("%02x:%02x:%02x:%02x:%02x:%02x\n", mac.addr_bytes[0], mac.addr_bytes[1], mac.addr_bytes[2], mac.addr_bytes[3], mac.addr_bytes[4], mac.addr_bytes[5]); else cli_printf("%d\n", 101); break; default: cli_help_show_group("Show"); return -1; } return 0; } static struct cli_tree my_tree[] = { c_dir("/data"), c_file("pci", pci_file, "display lspci information"), c_dir("/bin"), c_cmd("show", show_cmd, "show mapping options"), c_cmd("hello", hello_cmd, "Hello-World!!"), c_alias("h", "history", "display history commands"), c_alias("ll", "ls -l", "long directory listing alias"), c_end() }; Here is the cli_tree for this example, note it has a lot more commands. The show_cmd or show command is located a number of lines down. This cli_tree creates in the /bin directory a number of commands, which one is the show command. The show command has four different formats if you look at the show_map[]. The user types one of these commands and cli_mapping() attempts to locate the correct entry in the list. You will also notice another structure called pcap_help, which is an array of strings giving a cleaner and longer help description of each of the commands. These two structure show_map/show_help can be added to the cli_help system to provide help for a command using a simple API. .. code-block::c cli_help_add("Show", show_map, show_help); cli_help_show_group("Show"); or we can use the cli_help_show_all() API to show all added help information. .. code-block:: c cli_help_show_all(NULL); The following is from Pktgen source code to add more help to the global help for the system. .. code-block:: c cli_help_add("Title", NULL, title_help); cli_help_add("Page", page_map, page_help); cli_help_add("Enable", enable_map, enable_help); cli_help_add("Set", set_map, set_help); cli_help_add("Range", range_map, range_help); cli_help_add("Sequence", seq_map, seq_help); cli_help_add("PCAP", pcap_map, pcap_help); cli_help_add("Start", start_map, start_help); cli_help_add("Debug", debug_map, debug_help); cli_help_add("Misc", misc_map, misc_help); cli_help_add("Theme", theme_map, theme_help); cli_help_add("Status", NULL, status_help); Understanding the CLI system ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The command line interface is defined as a fake directory tree with executables, directories and files. The user uses shell like standard commands to move about the directory and execute commands. The CLI is not a powerful as the Bash shell, but has a number of similar concepts. Our fake directory tree has a '/' or root directory which is created when cli_create() is called along with the default sbin directory. The user starts out at the root directory '/' and is allowed to cd to other directories, which could contain more executables, aliases or directories. The max number of directory levels is limited to the number of nodes given at startup. The default directory tree starts out as just root (/) and a sbin directory. Also it contains a file called copyright in root, which can be displayed using the default 'more copyright' command. A number of default commands are predefined in the /sbin directory and are defined above. Other bin directories can be added to the system if needed, but a limit of CLI_MAX_BINS is defined in the cli.h header file. The CLI structure is created at run time adding directories, commands and aliases as needed, which is different from the cmdline interface in DPDK today. The basic concept for a command is similar to a standard Linux executable, meaning the command when executed it is passed the command line in a argc/argv format to be parsed by the function. The function is attached to a command file in the directory tree and is executed when the user types the name of the function along with it arguments. Some examples of the default commands can be seen in the lib/librte_cli/cli_cmds.c file. ================================================ FILE: lib/cli/cli_auto_complete.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /** * @file * CLI tab completion implementation. * * Provides shell-like completion for commands, directories, and files. * When a command has a registered cli_map table, completion can be driven by * the map tokens to offer context-aware suggestions and placeholder hints. */ #include #include #include #include #include "cli.h" #include "cli_input.h" #include "cli_auto_complete.h" /* * gb_get_prev() has side-effects (it can move gb->point across the gap). * Auto-complete should never mutate cursor state just to inspect a character. */ static inline char _gb_peek_prev(const struct gapbuf *gb) { const char *point; if (!gb) return '\0'; point = gb->point; if (point == gb->egap) point = gb->gap; if (point == gb->buf) { if (point == gb->gap) return '\0'; return *point; } return *(point - 1); } static uint32_t _ac_hash_line(const char *s, int arg_index, int at_new_token) { /* 32-bit FNV-1a */ uint32_t h = 2166136261u; if (s) { for (const unsigned char *p = (const unsigned char *)s; *p != '\0'; p++) { h ^= (uint32_t)(*p); h *= 16777619u; } } h ^= (uint32_t)arg_index; h *= 16777619u; h ^= (uint32_t)at_new_token; h *= 16777619u; return h; } static int _str_list_add_unique(char ***list, int *count, const char *s) { if (!list || !count || !s || (*s == '\0')) return -1; for (int i = 0; i < *count; i++) { if (!strcmp((*list)[i], s)) return 0; } char **new_list = realloc(*list, (size_t)(*count + 1) * sizeof(char *)); if (!new_list) return -1; *list = new_list; (*list)[*count] = strdup(s); if (!(*list)[*count]) return -1; (*count)++; return 0; } static void _str_list_free(char **list, int count) { if (!list) return; for (int i = 0; i < count; i++) free(list[i]); free(list); } static void _print_strings(char **items, int item_cnt, const char *match) { uint32_t mlen = 8, csize, ccnt, cnt = 0; uint32_t slen = (match) ? strlen(match) : 0; if (!items || item_cnt <= 0) return; for (int i = 0; i < item_cnt; i++) mlen = RTE_MAX(mlen, (uint32_t)strlen(items[i])); mlen++; csize = mlen; ccnt = CLI_SCREEN_WIDTH / mlen; if (ccnt == 0) ccnt = 1; for (int i = 0; i < item_cnt; i++) { if (slen && strncmp(items[i], match, slen)) continue; if (!cnt) cli_printf("\n"); cli_printf("%-*s", csize, items[i]); if ((++cnt % ccnt) == 0) cli_printf("\n"); } if (cnt % ccnt) cli_printf("\n"); } static int _is_placeholder(const char *tok) { return tok && tok[0] == '%' && tok[1] != '\0'; } static int _is_choice_token(const char *tok) { return tok && tok[0] == '%' && tok[1] == '|'; } static int _choice_token_contains(const char *choice_tok, const char *word) { char tmp[CLI_MAX_PATH_LENGTH + 1]; char *opts[CLI_MAX_ARGVS + 1]; if (!choice_tok || !word || !_is_choice_token(choice_tok)) return 0; snprintf(tmp, sizeof(tmp), "%s", choice_tok + 2); memset(opts, 0, sizeof(opts)); int n = pg_strtok(tmp, "|", opts, CLI_MAX_ARGVS); for (int i = 0; i < n; i++) { if (opts[i] && !strcmp(opts[i], word)) return 1; } return 0; } static int _tok_all_digits(const char *s) { if (!s || *s == '\0') return 0; if (*s == '-') s++; if (*s == '\0') return 0; for (const unsigned char *p = (const unsigned char *)s; *p != '\0'; p++) { if (!isdigit(*p)) return 0; } return 1; } static int _tok_all_hex_with_seps(const char *s) { if (!s || *s == '\0') return 0; for (const unsigned char *p = (const unsigned char *)s; *p != '\0'; p++) { if (isxdigit(*p) || *p == ':' || *p == '-' || *p == '.') continue; return 0; } return 1; } static int _tok_looks_like_ipv4(const char *s) { int dots = 0; if (!s || *s == '\0') return 0; for (const unsigned char *p = (const unsigned char *)s; *p != '\0'; p++) { if (*p == '.') dots++; else if (isdigit(*p) || *p == '/') ; else return 0; } return (dots >= 1); } static int _tok_looks_like_ipv6(const char *s) { int colons = 0; if (!s || *s == '\0') return 0; for (const unsigned char *p = (const unsigned char *)s; *p != '\0'; p++) { if (*p == ':') colons++; else if (isxdigit(*p) || *p == '/' || *p == '.') ; else return 0; } return (colons >= 1); } static int _map_tok_compatible_with_user_tok(const char *map_tok, const char *user_tok) { if (!map_tok || !user_tok) return 0; if (_is_choice_token(map_tok)) return _choice_token_contains(map_tok, user_tok); if (!_is_placeholder(map_tok)) return !strcmp(map_tok, user_tok); switch (map_tok[1]) { case 'd': case 'D': case 'u': case 'U': case 'b': case 'n': return _tok_all_digits(user_tok); case 'h': case 'H': /* Allow 0x prefix, otherwise require hex chars. */ if (!strncmp(user_tok, "0x", 2) || !strncmp(user_tok, "0X", 2)) return _tok_all_hex_with_seps(user_tok + 2); return _tok_all_hex_with_seps(user_tok); case 'm': /* MAC-like: hex digits with common separators (rejects keywords like 'tcp'). */ return _tok_all_hex_with_seps(user_tok); case '4': return _tok_looks_like_ipv4(user_tok); case '6': return _tok_looks_like_ipv6(user_tok); case 'P': case 'C': /* Accept 'all' or a digit-based list like 0,1-3 */ if (!strcmp(user_tok, "all")) return 1; for (const unsigned char *p = (const unsigned char *)user_tok; *p != '\0'; p++) { if (isdigit(*p) || *p == ',' || *p == '-') continue; return 0; } return 1; case 's': case 'c': case 'k': case 'l': return 1; default: return 1; } } static const char * _placeholder_hint(const char *tok) { if (!tok || tok[0] != '%' || tok[1] == '\0') return NULL; /* * Provide human-readable hints for placeholders. * These are displayed only when the completion prefix is empty, and are * printed (not inserted) by the completion engine. */ switch (tok[1]) { case 'd': return "<32bit number>"; case 'D': return "<64bit number>"; case 'h': return "<32bit hex>"; case 'H': return "<64bit hex>"; case 'u': return "<32bit unsigned>"; case 'U': return "<64bit unsigned>"; case 'P': return ""; case 'C': return ""; case 's': return ""; case 'c': return ""; case 'm': return ""; case '4': return ""; case '6': return ""; case 'k': return ""; case 'l': return ""; case 'b': return "<8bit number>"; case 'n': return ""; default: return NULL; } } static int _is_hint_candidate(const char *s) { size_t len; if (!s) return 0; len = strlen(s); return (len >= 3 && s[0] == '<' && s[len - 1] == '>'); } static int _env_collect_var_candidates(const char *prefix, char ***out_list, int *out_cnt) { struct cli_env *env = this_cli ? this_cli->env : NULL; struct env_node **list; int max; int n; if (!env || !out_list || !out_cnt) return 0; max = cli_env_count(env); if (max <= 0) return 0; list = calloc((size_t)max, sizeof(*list)); if (!list) return 0; n = cli_env_get_all(env, list, max); for (int i = 0; i < n; i++) { const char *name; if (!list[i]) continue; name = list[i]->var; if (!name || name[0] == '\0') continue; if (prefix && *prefix && strncmp(name, prefix, strlen(prefix))) continue; if (_str_list_add_unique(out_list, out_cnt, name)) { free(list); return -1; } } free(list); return *out_cnt; } static int _portlist_collect_candidates(const char *prefix, char ***out_list, int *out_cnt) { /* Minimal, but useful: the CLI help documents 'all' as a valid portlist. */ if (prefix && *prefix && strncmp("all", prefix, strlen(prefix))) return *out_cnt; if (_str_list_add_unique(out_list, out_cnt, "all")) return -1; /* Also show the placeholder hint alongside real values when no prefix is typed. */ if (!prefix || !*prefix) { const char *hint = _placeholder_hint("%P"); if (hint && _str_list_add_unique(out_list, out_cnt, hint)) return -1; } return *out_cnt; } static int _map_collect_placeholder_candidates(const char *cmd, char **mtoks, int mtokc, int arg_index, int argc, char **argv, const char *placeholder, const char *prefix, char ***out_list, int *out_cnt) { (void)mtokc; if (!cmd || !mtoks || !argv || !out_list || !out_cnt) return 0; /* env get|set|del : complete existing environment variable names */ if (!strcmp(cmd, "env") && arg_index == 2 && argc >= 2 && argv[1] && (!strcmp(argv[1], "get") || !strcmp(argv[1], "set") || !strcmp(argv[1], "del"))) return _env_collect_var_candidates(prefix, out_list, out_cnt); /* seq/sequence ... : show a helpful hint for the required sequence number */ if ((!strcmp(cmd, "seq") || !strcmp(cmd, "sequence")) && arg_index == 1 && placeholder && !strcmp(placeholder, "%d")) { if (!prefix || !*prefix) { if (_str_list_add_unique(out_list, out_cnt, "")) return -1; } return *out_cnt; } /* %P (portlist) : suggest common values like 'all' */ if (placeholder && placeholder[0] == '%' && placeholder[1] == 'P') return _portlist_collect_candidates(prefix, out_list, out_cnt); return 0; } static int _map_collect_candidates(struct cli_map *maps, int argc, char **argv, int arg_index, const char *prefix, char ***out_list, int *out_cnt) { char fmt_copy[CLI_MAX_PATH_LENGTH + 1]; char *mtoks[CLI_MAX_ARGVS + 1]; if (!maps || argc <= 0 || !argv || !argv[0] || arg_index < 1) return 0; for (int mi = 0; maps[mi].fmt != NULL; mi++) { memset(mtoks, 0, sizeof(mtoks)); snprintf(fmt_copy, sizeof(fmt_copy), "%s", maps[mi].fmt); int mtokc = pg_strtok(fmt_copy, " ", mtoks, CLI_MAX_ARGVS); if (mtokc <= arg_index) continue; /* map must be for this command */ if (!mtoks[0]) continue; if (_is_choice_token(mtoks[0])) { if (!_choice_token_contains(mtoks[0], argv[0])) continue; } else if (strcmp(mtoks[0], argv[0])) { continue; } /* must match already-typed tokens before the arg we are completing */ int ok = 1; for (int i = 0; i < arg_index && i < argc && i < mtokc; i++) { if (!_map_tok_compatible_with_user_tok(mtoks[i], argv[i])) { ok = 0; break; } } if (!ok) continue; const char *cand_tok = mtoks[arg_index]; if (!cand_tok || cand_tok[0] == '\0') continue; /* Placeholder-aware completion (e.g., env var names for env get/set/del). */ if (_is_placeholder(cand_tok) && !_is_choice_token(cand_tok)) { const int before_cnt = *out_cnt; if (_map_collect_placeholder_candidates(argv[0], mtoks, mtokc, arg_index, argc, argv, cand_tok, prefix, out_list, out_cnt) < 0) return -1; /* If no better candidates exist, provide a human hint like "". */ if ((!prefix || !*prefix) && (*out_cnt == before_cnt)) { const char *hint = _placeholder_hint(cand_tok); if (hint && _str_list_add_unique(out_list, out_cnt, hint)) return -1; } continue; } if (_is_choice_token(cand_tok)) { /* cand_tok is like "%|a|b|c" */ char opt_copy[CLI_MAX_PATH_LENGTH + 1]; char *opts[CLI_MAX_ARGVS + 1]; memset(opts, 0, sizeof(opts)); snprintf(opt_copy, sizeof(opt_copy), "%s", cand_tok + 2); int n = pg_strtok(opt_copy, "|", opts, CLI_MAX_ARGVS); for (int oi = 0; oi < n; oi++) { if (!opts[oi]) continue; if (prefix && *prefix && strncmp(opts[oi], prefix, strlen(prefix))) continue; if (_str_list_add_unique(out_list, out_cnt, opts[oi])) return -1; } } else { if (prefix && *prefix && strncmp(cand_tok, prefix, strlen(prefix))) continue; if (_str_list_add_unique(out_list, out_cnt, cand_tok)) return -1; } } return *out_cnt; } static int _map_next_is_user_value(struct cli_map *maps, int argc, char **argv, int arg_index) { char fmt_copy[CLI_MAX_PATH_LENGTH + 1]; char *mtoks[CLI_MAX_ARGVS + 1]; if (!maps || argc <= 0 || !argv || !argv[0] || arg_index < 1) return 0; for (int mi = 0; maps[mi].fmt != NULL; mi++) { memset(mtoks, 0, sizeof(mtoks)); snprintf(fmt_copy, sizeof(fmt_copy), "%s", maps[mi].fmt); int mtokc = pg_strtok(fmt_copy, " ", mtoks, CLI_MAX_ARGVS); if (mtokc <= arg_index) continue; if (!mtoks[0]) continue; if (_is_choice_token(mtoks[0])) { if (!_choice_token_contains(mtoks[0], argv[0])) continue; } else if (strcmp(mtoks[0], argv[0])) { continue; } int ok = 1; for (int i = 0; i < arg_index && i < argc && i < mtokc; i++) { if (!_map_tok_compatible_with_user_tok(mtoks[i], argv[i])) { ok = 0; break; } } if (!ok) continue; const char *tok = mtoks[arg_index]; if (!tok || tok[0] == '\0') continue; if (_is_placeholder(tok) && !_is_choice_token(tok)) return 1; } return 0; } static int _map_current_is_user_value(struct cli_map *maps, int argc, char **argv, int arg_index) { char fmt_copy[CLI_MAX_PATH_LENGTH + 1]; char *mtoks[CLI_MAX_ARGVS + 1]; if (!maps || argc <= 0 || !argv || !argv[0] || arg_index < 1) return 0; for (int mi = 0; maps[mi].fmt != NULL; mi++) { memset(mtoks, 0, sizeof(mtoks)); snprintf(fmt_copy, sizeof(fmt_copy), "%s", maps[mi].fmt); int mtokc = pg_strtok(fmt_copy, " ", mtoks, CLI_MAX_ARGVS); if (mtokc <= arg_index) continue; if (!mtoks[0]) continue; if (_is_choice_token(mtoks[0])) { if (!_choice_token_contains(mtoks[0], argv[0])) continue; } else if (strcmp(mtoks[0], argv[0])) { continue; } int ok = 1; for (int i = 0; i < arg_index && i < argc && i < mtokc; i++) { if (!_map_tok_compatible_with_user_tok(mtoks[i], argv[i])) { ok = 0; break; } } if (!ok) continue; const char *tok = mtoks[arg_index]; if (!tok || tok[0] == '\0') continue; if (_is_placeholder(tok) && !_is_choice_token(tok)) return 1; } return 0; } static int _map_has_tokens_after(struct cli_map *maps, int argc, char **argv, int arg_index) { char fmt_copy[CLI_MAX_PATH_LENGTH + 1]; char *mtoks[CLI_MAX_ARGVS + 1]; if (!maps || argc <= 0 || !argv || !argv[0] || arg_index < 1) return 0; for (int mi = 0; maps[mi].fmt != NULL; mi++) { memset(mtoks, 0, sizeof(mtoks)); snprintf(fmt_copy, sizeof(fmt_copy), "%s", maps[mi].fmt); int mtokc = pg_strtok(fmt_copy, " ", mtoks, CLI_MAX_ARGVS); if (!mtoks[0]) continue; if (_is_choice_token(mtoks[0])) { if (!_choice_token_contains(mtoks[0], argv[0])) continue; } else if (strcmp(mtoks[0], argv[0])) { continue; } int ok = 1; for (int i = 0; i < arg_index && i < argc && i < mtokc; i++) { if (!_map_tok_compatible_with_user_tok(mtoks[i], argv[i])) { ok = 0; break; } } if (!ok) continue; if (mtokc > (arg_index + 1)) return 1; } return 0; } static uint32_t _column_count(struct cli_node **nodes, uint32_t node_cnt, uint32_t *len) { uint32_t i, mlen = 8, cs; if (!nodes || !len) return CLI_SCREEN_WIDTH / mlen; /* Calculate the column size */ for (i = 0; i < node_cnt; i++) mlen = RTE_MAX(mlen, strlen(nodes[i]->name)); mlen++; /* Make sure we have at least a space between */ *len = mlen; cs = CLI_SCREEN_WIDTH / mlen; return cs; } static int _print_nodes(struct cli_node **nodes, uint32_t node_cnt, uint32_t dir_only, char *match, struct cli_node **ret) { struct cli_node *n; uint32_t i, cnt = 0, ccnt, found = 0, slen, csize; if (!node_cnt || !nodes) return 0; ccnt = _column_count(nodes, node_cnt, &csize); slen = (match) ? strlen(match) : 0; /* display the node names */ for (i = 0; i < node_cnt; i++) { n = nodes[i]; if (dir_only && !is_directory(n)) continue; if (slen && strncmp(n->name, match, slen)) continue; if (!cnt) cli_printf("\n"); cli_printf("%-*s", csize, n->name); if ((++cnt % ccnt) == 0) cli_printf("\n"); /* Found a possible match */ if (ret) *ret = n; found++; } /* if not nodes found cnt will be zero and no CR */ if (cnt % ccnt) cli_printf("\n"); return found; } static int qsort_compare(const void *p1, const void *p2) { const struct cli_node *n1, *n2; n1 = *(const struct cli_node *const *)p1; n2 = *(const struct cli_node *const *)p2; return strcmp(n1->name, n2->name); } static int complete_args(int argc, char **argv, uint32_t types) { struct cli_node **nodes = NULL, *node = NULL; struct gapbuf *gb; char *match; uint32_t node_cnt, found = 0, dir_only = 0, slen; if (argc) match = argv[argc - 1]; else match = NULL; gb = this_cli->gb; if (match) { uint32_t stype; uint32_t slashes; char *p; /* Count the number of slashes in the path */ slashes = pg_strcnt(match, '/'); if (slashes) { /* full path to command given */ if (cli_find_node(match, &node)) if (is_executable(node)) return 0; /* if not found get last directory in path */ node = cli_last_dir_in_path(match); if ((slashes == 1) && (match && (match[0] == '/'))) { match++; dir_only++; } } stype = CLI_ALL_TYPE; /* search for all nodes */ if (argc > 1) stype = CLI_OTHER_TYPE; /* search for non-exe nodes */ node_cnt = cli_node_list_with_type(node, stype, (void **)&nodes); p = strrchr(match, '/'); if (p) match = ++p; } else node_cnt = cli_node_list_with_type(NULL, types, (void **)&nodes); if (node_cnt) { struct cli_node *mnode = NULL; if (node_cnt > 1) qsort(nodes, node_cnt, sizeof(void *), qsort_compare); slen = (match) ? strlen(match) : 0; /* * If there is exactly one match, do not print the candidate list. * Just complete the token (shell-like behavior). */ uint32_t match_cnt = 0; for (uint32_t i = 0; i < node_cnt; i++) { struct cli_node *n = nodes[i]; if (dir_only && !is_directory(n)) continue; if (slen && strncmp(n->name, match, slen)) continue; mnode = n; if (++match_cnt > 1) break; } if (match_cnt == 1) found = 1; else if (match_cnt > 1) found = _print_nodes(nodes, node_cnt, dir_only, match, &mnode); /* * _match is a pointer to the last matched node * _found is a flag to determine if pointer is valid */ if (mnode && (found == 1)) { /* Found a possible match */ struct cli_node *node = (struct cli_node *)mnode; char *s; int nlen; s = strrchr(match, '/'); if (s) match = ++s; slen = strlen(match); nlen = (strlen(node->name) - slen); if (nlen > 0) /* Add the rest of the matching command */ gb_str_insert(gb, &node->name[slen], nlen); if (is_directory(node)) gb_str_insert(gb, (char *)(uintptr_t)"/", 1); else gb_str_insert(gb, (char *)(uintptr_t)" ", 1); } } cli_node_list_free(nodes); return found; } void cli_auto_complete(void) { char *argv[CLI_MAX_ARGVS + 1]; char *line = NULL; int argc, size, ret; int at_new_token; int arg_index; struct cli_map *maps = NULL; char **cands = NULL; int cand_cnt = 0; uint64_t now_tsc; uint32_t line_hash; int force_usage = 0; memset(argv, '\0', sizeof(argv)); size = gb_data_size(this_cli->gb); if (!size) return; size = RTE_MIN(size, (int)(CLI_MAX_SCRATCH_LENGTH - 1)); line = calloc(1, (size_t)size + 1); if (!line) return; gb_copy_to_buf(this_cli->gb, line, size); argc = pg_strqtok(line, " \r\n", argv, CLI_MAX_ARGVS); /* Be defensive: some tokenizers may yield empty/NULL trailing tokens. */ while (argc > 0 && (!argv[argc - 1] || argv[argc - 1][0] == '\0')) argc--; at_new_token = 0; if (!gb_point_at_start(this_cli->gb)) { char prev = _gb_peek_prev(this_cli->gb); at_new_token = (prev == ' ' || prev == '\t' || prev == '\n' || prev == '\r'); } /* Determine which argv slot we are completing */ if (argc == 0) arg_index = 0; else arg_index = at_new_token ? argc : (argc - 1); /* Double-tab detection: same line state within a short time window */ now_tsc = rte_get_timer_cycles(); line_hash = _ac_hash_line(line, arg_index, at_new_token); { const uint64_t hz = rte_get_timer_hz(); const uint64_t window_cycles = (hz) ? (hz * 4 / 10) : 0; /* ~400ms */ if (window_cycles && this_cli->ac_last_hash == line_hash && (now_tsc - this_cli->ac_last_tsc) <= window_cycles) force_usage = 1; this_cli->ac_last_hash = line_hash; this_cli->ac_last_tsc = now_tsc; } if (argc == 0) { ret = complete_args(argc, argv, CLI_ALL_TYPE); if (ret) cli_redisplay_line(); free(line); return; } /* * If the user typed an exact executable command token (no trailing space), * treat as "accept command and complete next token". * * - If a map exists: complete the next token from the map. * - Otherwise: fall back to the legacy "-?" help behavior. */ if (argc == 1 && !at_new_token && argv[0]) { struct cli_node *node = NULL; if (!cli_find_node(argv[0], &node) && node && is_executable(node) && node->name[0] != '\0' && !strcmp(node->name, argv[0])) { gb_str_insert(this_cli->gb, (char *)(uintptr_t)" ", 1); cli_redisplay_line(); maps = cli_get_cmd_map(argv[0]); if (maps) { ret = _map_collect_candidates(maps, argc, argv, 1, NULL, &cands, &cand_cnt); if (ret < 0) { _str_list_free(cands, cand_cnt); free(line); return; } if (cand_cnt == 1) { const char *ins = cands[0]; if (_is_hint_candidate(ins)) { _print_strings(cands, cand_cnt, NULL); cli_redisplay_line(); } else { gb_str_insert(this_cli->gb, (char *)(uintptr_t)ins, strlen(ins)); gb_str_insert(this_cli->gb, (char *)(uintptr_t)" ", 1); cli_redisplay_line(); } } else if (cand_cnt > 1) { _print_strings(cands, cand_cnt, NULL); cli_redisplay_line(); } else { /* If next token is user input, don't spam usage; otherwise show usage safely. */ if (!_map_next_is_user_value(maps, argc, argv, 1)) { cli_printf("\n"); cli_maps_show(maps, argc, argv); cli_redisplay_line(); } else if (force_usage) { cli_printf("\n"); cli_maps_show(maps, argc, argv); cli_redisplay_line(); } } _str_list_free(cands, cand_cnt); free(line); return; } /* No map registered: fall back to legacy "-?" help output. */ { int cur_size = gb_data_size(this_cli->gb); cur_size = RTE_MIN(cur_size, (int)(CLI_MAX_SCRATCH_LENGTH - 1)); char *save = calloc(1, (size_t)cur_size + 1); if (!save) { free(line); return; } gb_copy_to_buf(this_cli->gb, save, cur_size); gb_str_insert(this_cli->gb, (char *)(uintptr_t)"-?", 2); cli_execute(); gb_reset_buf(this_cli->gb); gb_str_insert(this_cli->gb, save, cur_size); cli_redisplay_line(); free(save); } free(line); return; } } /* If the first token is a known command with a map, try map-driven completion */ maps = (argc > 0 && argv[0]) ? cli_get_cmd_map(argv[0]) : NULL; if (maps && arg_index >= 1) { const char *prefix = NULL; if (!at_new_token && arg_index < argc && argv[arg_index]) prefix = argv[arg_index]; ret = _map_collect_candidates(maps, argc, argv, arg_index, prefix, &cands, &cand_cnt); if (ret < 0) { _str_list_free(cands, cand_cnt); free(line); return; } /* * If we are sitting on a user-value token with no suggestions (e.g. "set 0"), * treat Tab as end-of-token: insert a space and complete the next map token. */ if (cand_cnt == 0 && !at_new_token && prefix && *prefix && _map_current_is_user_value(maps, argc, argv, arg_index) && _map_has_tokens_after(maps, argc, argv, arg_index)) { gb_str_insert(this_cli->gb, (char *)(uintptr_t)" ", 1); cli_redisplay_line(); _str_list_free(cands, cand_cnt); cands = NULL; cand_cnt = 0; at_new_token = 1; arg_index = argc; prefix = NULL; ret = _map_collect_candidates(maps, argc, argv, arg_index, prefix, &cands, &cand_cnt); if (ret < 0) { _str_list_free(cands, cand_cnt); free(line); return; } } if (cand_cnt == 1) { const char *ins = cands[0]; size_t plen = (prefix) ? strlen(prefix) : 0; if (_is_hint_candidate(ins)) { _print_strings(cands, cand_cnt, prefix); cli_redisplay_line(); } else if (plen <= strlen(ins)) { gb_str_insert(this_cli->gb, (char *)(uintptr_t)&ins[plen], strlen(ins) - plen); gb_str_insert(this_cli->gb, (char *)(uintptr_t)" ", 1); cli_redisplay_line(); } } else if (cand_cnt > 1) { _print_strings(cands, cand_cnt, prefix); cli_redisplay_line(); } else if (at_new_token) { /* If next token is user input, don't spam usage; otherwise show usage safely. */ if (!_map_next_is_user_value(maps, argc, argv, arg_index)) { cli_printf("\n"); cli_maps_show(maps, argc, argv); cli_redisplay_line(); } else if (force_usage) { cli_printf("\n"); cli_maps_show(maps, argc, argv); cli_redisplay_line(); } } _str_list_free(cands, cand_cnt); free(line); return; } /* no space before cursor maybe a command completion request */ if (gb_point_at_start(this_cli->gb) || _gb_peek_prev(this_cli->gb) != ' ') { if (argc == 1) /* Only one word then look for a command */ ret = complete_args(argc, argv, CLI_ALL_TYPE); else /* If more then one word then look for file/dir */ ret = complete_args(argc, argv, CLI_FILE_NODE | CLI_DIR_NODE); /* if we get an error then redisplay the line */ if (ret) cli_redisplay_line(); } else { char *save = calloc(1, (size_t)size + 1); if (!save) { free(line); return; } /* Call function to print out help text, plus save a copy */ gb_copy_to_buf(this_cli->gb, save, size); /* Add the -? to the command */ gb_str_insert(this_cli->gb, (char *)(uintptr_t)"-?", 2); cli_execute(); /* reset the input buffer to remove -? */ gb_reset_buf(this_cli->gb); /* insert the saved string back to the input buffer */ gb_str_insert(this_cli->gb, save, size); cli_redisplay_line(); free(save); } free(line); } ================================================ FILE: lib/cli/cli_auto_complete.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #ifndef _CLI_AUTO_COMPLETE_H_ #define _CLI_AUTO_COMPLETE_H_ /** * @file * CLI auto-complete. * * Handles TAB completion for commands/paths and optionally uses registered * cli_map tables to offer context-aware token hints. */ #include "cli.h" #ifdef __cplusplus extern "C" { #endif /** * Handle the tab key for auto-complete. * * This function reads the current input buffer, computes candidate * completions, and either inserts text (single match) or prints a candidate * list (multiple matches). * * @return * N/A */ void cli_auto_complete(void); #ifdef __cplusplus } #endif #endif /* _CLI_AUTO_COMPLETE_H_ */ ================================================ FILE: lib/cli/cli_cmap.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2013-2026>, Intel Corporation. */ /* * Prints a CPU core map on the form * "S/C/T L" * where * - S is the CPU socket ID * - C is the physical CPU core ID * - T is the hyper-thread ID * - L is the logical core ID * * This tool parses the information from "/proc/cpuinfo" which should * be present on all Linux systems. * * NOTE: this tool has only been tested on systems with x86/x86_64 * CPUs so far. * * Written 2011 by Kenneth Jonsson, WindRiver. * Adapted to Pktgen by Keith Wiles, WindRiver 2013-01-08 */ #include #include #include #include #include #include #include #include "cli_cmap.h" static char *model_name; char * cmap_cpu_model(void) { return model_name; } static const char * as_str(const char *line) { if (*line != ':') return as_str(line + 1); return line + 1; } static unsigned as_int(const char *line) { return atoi(as_str(line)); } static lcore_t * new_lcore(const char *line, lcore_t *rest) { lcore_t *lc = calloc(1, sizeof(lcore_t)); lc->next = rest; lc->u.lid = as_int(line); return lc; } static lcore_t * set_raw_socket_id(const char *line, lcore_t *lc) { lc->u.sid = as_int(line); printf("Socket ID: %u, (%s)\n", lc->u.sid, line); return lc; } static lcore_t * set_raw_core_id(const char *line, lcore_t *lc) { lc->u.cid = as_int(line); return lc; } static lcore_t * set_model_name(const char *line, lcore_t *lc) { if (!model_name) model_name = strdup(as_str(line)); return lc; } static unsigned get_next_thread_id(const lcore_t *lc, unsigned socket_id, unsigned core_id) { if (lc == NULL) return 0; if (lc->u.sid == socket_id && lc->u.cid == core_id) return lc->u.tid + 1; return get_next_thread_id(lc->next, socket_id, core_id); } static lcore_t * set_thread_id_str(const char *unused, lcore_t *lc) { (void)unused; lc->u.tid = get_next_thread_id(lc->next, lc->u.sid, lc->u.cid); return lc; } static lcore_t * ignore_line(const char *unused, lcore_t *lc) { (void)unused; return lc; } static do_line_fn get_matching_action(const char *line) { // clang-format off static struct action actions[] = { {"processor", new_lcore}, {"physical id", set_raw_socket_id}, {"core id", set_raw_core_id}, {"model name", set_model_name}, {"\n", set_thread_id_str}, {NULL, NULL} }; // clang-format on struct action *action; for (action = actions; action->fn != NULL; ++action) if (strncmp(action->desc, line, strlen(action->desc)) == 0) return action->fn; return ignore_line; } /* * Remaps a property value from 'from' to 'to'. This is done for all * logical cores. */ static void remap(lcore_t *lc, unsigned from, unsigned to, getter_fn get, setter_fn set) { if (lc) { if (get(lc) == from) set(lc, to); remap(lc->next, from, to, get, set); } } /* * Returns the first entry that is equal to or as close as possible to * 'v' in the property returned by 'get'. */ static lcore_t * closest_gte(lcore_t *lc, lcore_t *sel, unsigned v, getter_fn get) { if (lc == NULL) return sel; if (get(lc) >= v && (sel == NULL || get(sel) - v > get(lc) - v)) return closest_gte(lc->next, lc, v, get); return closest_gte(lc->next, sel, v, get); } /* * Makes the property returned and set by 'get'/'set' start from zero * and increase by one for each unique value that property has. * Ex: core id "0,1,4,5,0,1,4,5" -> "0,1,2,3,0,1,2,3" */ static void zero_base(lcore_t *head, getter_fn get, setter_fn set) { unsigned id = 0; lcore_t *lc; while ((lc = closest_gte(head, NULL, id, get)) != NULL) { remap(lc, get(lc), id, get, set); ++id; } } static void get_and_free_lcore_info(lcore_t *lc, lc_info_t *get) { if (lc) { get_and_free_lcore_info(lc->next, get + 1); get->word = lc->u.word; free(lc); } } static inline int count_cores(lcore_t *lcores) { int num = 0; while (lcores) { lcores = lcores->next; num++; } return num; } static int my_getline(char **line, size_t *line_sz, int fd) { char *l, c; size_t sz, i; if (*line == NULL) { if (*line_sz == 0) *line_sz = MAX_LINE_SIZE; l = calloc(1, *line_sz); if (l == NULL) return -1; *line = l; } else l = *line; for (i = 0, sz = 0; i < *line_sz; i++) { if (read(fd, &c, 1) != 1) return -1; *l++ = c; sz++; if (c == '\n') break; } *l = '\0'; return sz; } static lc_info_t lcore_info[RTE_MAX_LCORE]; struct cmap * cmap_create(void) { int fd; char *line = NULL; struct cmap *cmap; lc_info_t *lc_info = &lcore_info[0]; size_t line_sz = 0; lcore_t *lcores = NULL; memset(lcore_info, '\0', sizeof(lcore_info)); cmap = calloc(1, sizeof(struct cmap)); if (!cmap) return NULL; if ((fd = open(PROC_CPUINFO, O_RDONLY)) < 0) { fprintf(stderr, "Cannot open %s on this system\n", PROC_CPUINFO); free(cmap); return NULL; } while (my_getline(&line, &line_sz, fd) >= 0) lcores = get_matching_action(line)(line, lcores); if (fd) close(fd); if (line) free(line); zero_base(lcores, cmap_socket_id, cmap_set_socket_id); zero_base(lcores, cmap_core_id, cmap_set_core_id); cmap->linfo = lc_info; cmap->model = model_name; cmap->num_cores = count_cores(lcores); cmap->sid_cnt = cmap_cnt(lcores, cmap_socket_id); cmap->cid_cnt = cmap_cnt(lcores, cmap_core_id); cmap->tid_cnt = cmap_cnt(lcores, cmap_thread_id); get_and_free_lcore_info(lcores, lc_info); return cmap; } void cmap_free(struct cmap *cmap) { free(cmap); } ================================================ FILE: lib/cli/cli_cmap.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /** * @file * CPU core map — parses /proc/cpuinfo into a lcore/socket/core/thread table. * * Used internally by the CLI cpu-info display to map logical cores to their * physical socket, core, and hyper-thread IDs. */ #ifndef __CLI_CMAP_H #define __CLI_CMAP_H #include #define MAX_LINE_SIZE 4096 #define PROC_CPUINFO "/proc/cpuinfo" /** Per-logical-core topology descriptor packed into a single 32-bit word. */ typedef union { struct { uint8_t lid; /**< Logical core ID */ uint8_t sid; /**< CPU socket ID */ uint8_t cid; /**< Physical CPU core ID */ uint8_t tid; /**< Hyper-thread ID */ }; uint32_t word; /**< Raw 32-bit representation */ } lc_info_t; /** Linked-list node for a single logical core entry. */ typedef struct lcore { struct lcore *next; /**< Next lcore in the singly-linked list */ lc_info_t u; /**< Topology info for this lcore */ } lcore_t; /** Aggregated CPU topology table for the whole system. */ struct cmap { uint16_t num_cores; /**< Total number of logical cores */ uint16_t sid_cnt; /**< Number of distinct socket IDs */ uint16_t cid_cnt; /**< Number of distinct physical core IDs */ uint16_t tid_cnt; /**< Number of distinct hyper-thread IDs */ lc_info_t *linfo; /**< Flat array of per-lcore topology info */ char *model; /**< CPU model name string (from /proc/cpuinfo) */ }; typedef lcore_t *(*do_line_fn)(const char *line, lcore_t *); typedef unsigned (*getter_fn)(const lcore_t *); typedef void (*setter_fn)(lcore_t *, unsigned new_val); typedef struct action { const char *desc; do_line_fn fn; } action_t; /** * Create a cmap structure for the current system * * @return * The pointer to the cmap structure or NULL on error */ struct cmap *cmap_create(void); /** * Return the current CPU model string * * @return * Pointer to current CPU model string. */ char *cmap_cpu_model(void); /** * Free up the resources attached to a cmap structure * * @param cmap * A valid cmap pointer */ void cmap_free(struct cmap *cmap); /** * Return the socket id for a given lcore (Internal) * * @param lc * Pointer to the given lcore structure * @return * The socket ID value */ static inline unsigned int cmap_socket_id(const lcore_t *lc) { return lc->u.sid; } /** * Set the socket id for a given lcore (Internal) * * @param lc * Pointer to the given lcore structure * @param v * Set the socket id value * @return * N/A */ static inline void cmap_set_socket_id(lcore_t *lc, unsigned v) { lc->u.sid = v; } /** * Return the core id for a given lcore (Internal) * * @param lc * Pointer to the given lcore structure * @return * The core ID value */ static inline unsigned int cmap_core_id(const lcore_t *lc) { return lc->u.cid; } /** * Set the core id for a given lcore (Internal) * * @param lc * Pointer to the given lcore structure * @param v * Set the core id value * @return * N/A */ static inline void cmap_set_core_id(lcore_t *lc, unsigned v) { lc->u.cid = v; } /** * Return the thread id for a given lcore (Internal) * * @param lc * Pointer to the given lcore structure * @return * The thread ID value */ static inline unsigned int cmap_thread_id(const lcore_t *lc) { return lc->u.tid; } /** * Return the count of unique values for a given topology property. * * Walks the lcore linked list and returns (max_value + 1) for the * property selected by @p get (e.g. socket ID, core ID, or thread ID). * * @param lc * Head of the singly-linked lcore list. * @param get * Getter function that extracts the property to count from an lcore_t. * @return * Number of unique values (max observed value + 1), or 0 if @p get is NULL. */ static inline unsigned int cmap_cnt(lcore_t *lc, getter_fn get) { unsigned cnt = 0; if (!get) return cnt; while (lc) { if (cnt < get(lc)) cnt = get(lc); lc = lc->next; } return cnt + 1; } #endif /*_CLI_CMAP_H */ ================================================ FILE: lib/cli/cli_cmds.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #include #include #include #include #include #include #include #include #include #include #include #include "cli.h" #include "cli_input.h" #include "cli_cmds.h" #include "cli_cmap.h" #include "cli_map.h" #include "cli_file.h" #include "cli_help.h" static int __print_help(struct cli_node *node, char *search) { struct cli_node *cmd; if (!node) node = get_cwd(); else if (!is_directory(node)) return -1; TAILQ_FOREACH (cmd, &node->items, next) { if (is_executable(cmd)) { if (search) { if (strcmp(cmd->name, search) == 0) { cli_printf(" %-16s %s\n", cmd->name, cmd->short_desc); return 1; } } else cli_printf(" %-16s %s\n", cmd->name, cmd->short_desc); } } return 0; } static int chelp_cmd(int argc, char **argv) { struct cli *cli = this_cli; struct cli_node *bin; char *search = NULL; int i, opt, all = 0; optind = 0; while ((opt = getopt(argc, argv, "a?")) != -1) { switch (opt) { case '?': cli_usage(); return 0; case 'a': all = 1; break; default: break; } } if (optind < argc) search = argv[optind]; cli_printf("*** CLI Help ***\n"); cli_printf(" Use -? to show usage for a command\n"); cli_printf(" Use ! to execute a history line\n"); cli_printf(" Use @ to execute a host binary\n"); cli_printf(" Use Up/Down arrows to access history commands\n\n"); cli_printf(" Use 'chelp -a' to list all commands\n"); if (all == 0) { /* Look in the current directory first for a command */ cli_printf("*** Current directory commands ***\n"); return __print_help(NULL, search); } cli_printf("*** All executable commands in path ***\n"); /* Did not find a command in local then look in the bin dirs */ for (i = 0; i < CLI_MAX_BINS; i++) { bin = cli->bins[i]; if (bin == NULL) continue; cli_printf("%s:\n", bin->name); if (__print_help(bin, search)) return 0; } return 0; } static int cd_cmd(int argc, char **argv) { struct cli_node *node; if (argc > 1) { if (!strcmp(argv[1], "-?")) { cli_usage(); return 0; } if (!cli_find_node(argv[1], &node)) { cli_printf("** Invalid directory: %s\n", argv[1]); return -1; } set_cwd(node); } return 0; } static int pwd_cmd(int argc, char **argv) { char *str = cli_cwd_path(); if (argc > 1 && !strcmp(argv[1], "-?")) { cli_usage(); return 0; } /* trim off the trailing '/' if needed */ if (strlen(str) > 1) str[strlen(str) - 1] = '\0'; cli_printf("%s\n", str); return 0; } static int __list_long_dir(struct cli_node *node, uint32_t type __rte_unused, args_t *args) { uint16_t flags = args->arg1.u16[3]; uint16_t spc = args->arg2.u16[0]; if (is_alias(node)) cli_printf(" %*s%-16s %s : %s\n", spc, "", node->name, cli_node_type(node), node->alias_str); else if (is_command(node)) cli_printf(" %*s%-16s %s : %s\n", spc, "", node->name, cli_node_type(node), node->short_desc); else cli_printf(" %*s%-16s %s\n", spc, "", node->name, cli_node_type(node)); if ((flags & CLI_RECURSE_FLAG) && is_directory(node)) { args->arg2.u16[0] += 2; cli_scan_directory(node, __list_long_dir, type, args); args->arg2.u16[0] = spc; } return 0; } static int __list_dir(struct cli_node *node, uint32_t flag __rte_unused, args_t *args) { char buf[CLI_NAME_LEN + 1]; uint16_t cnt = args->arg1.u16[0]; uint16_t mlen = args->arg1.u16[1]; uint16_t col = args->arg1.u16[2]; uint16_t flags = args->arg1.u16[3]; if (!node) return -1; if (is_directory(node)) { char dbuf[CLI_NAME_LEN + 1]; snprintf(dbuf, sizeof(dbuf), "[%s]", node->name); snprintf(buf, sizeof(buf), "%-*s", mlen, dbuf); } else snprintf(buf, sizeof(buf), "%-*s", mlen, node->name); cli_printf("%s", buf); if ((++cnt % col) == 0) cli_printf("\n"); if ((flags & CLI_RECURSE_FLAG) && is_directory(node)) { cli_printf("\n"); args->arg1.u16[0] = 0; cli_scan_directory(node, __list_dir, CLI_ALL_TYPE, args); args->arg1.u16[0] = cnt; cli_printf("\n"); } args->arg1.u16[0] = cnt; return 0; } static int ls_cmd(int argc, char **argv) { struct cli_node *node = get_cwd(); args_t args; uint32_t flags = 0; int opt; optind = 0; while ((opt = getopt(argc, argv, "?rl")) != -1) { switch (opt) { case '?': cli_usage(); return 0; case 'r': flags |= CLI_RECURSE_FLAG; break; case 'l': flags |= CLI_LONG_LIST_FLAG; break; default: break; } } if (optind < argc) if (cli_find_node(argv[optind], &node) == 0) { cli_printf("Invalid directory (%s)!!\n", argv[optind]); return -1; } memset(&args, 0, sizeof(args)); args.arg1.u16[0] = 0; args.arg1.u16[1] = 16; args.arg1.u16[2] = 80 / 16; args.arg1.u16[3] = flags; args.arg2.u16[0] = 0; if (flags & CLI_LONG_LIST_FLAG) cli_scan_directory(node, __list_long_dir, CLI_ALL_TYPE, &args); else cli_scan_directory(node, __list_dir, CLI_ALL_TYPE, &args); cli_printf("\n"); return 0; } static int scrn_cmd(int argc __rte_unused, char **argv __rte_unused) { cli_clear_screen(); return 0; } static int quit_cmd(int argc __rte_unused, char **argv __rte_unused) { cli_quit(); return 0; } static int hist_cmd(int argc, char **argv) { if (argc > 1 && !strcmp(argv[1], "-?")) cli_usage(); else cli_history_list(); return 0; } static int more_cmd(int argc, char **argv) { struct cli_node *node; char buf[513], c; int i, len, n, k, lines = 24; int opt; optind = 0; while ((opt = getopt(argc, argv, "?n:")) != -1) { switch (opt) { case '?': cli_usage(); return 0; case 'n': lines = atoi(optarg); break; default: break; } } if (optind >= argc) return 0; len = (int)(sizeof(buf) - 1); memset(buf, '\0', sizeof(buf)); for (i = optind; i < argc; i++) { k = 0; node = cli_file_open(argv[i], "r"); if (!node) { cli_printf("** (%s) is not a file\n", argv[i]); continue; } do { n = cli_readline(node, buf, len); if (n > 0) cli_printf("%s", buf); /* contains a newline */ if (++k >= lines) { k = 0; c = cli_pause("More", NULL); if ((c == vt100_escape) || (c == 'q') || (c == 'Q')) break; } } while (n > 0); cli_file_close(node); } cli_printf("\n"); return 0; } /* Helper for building log strings. * The macro takes an existing string, a printf-like format string and optional * arguments. It formats the string and appends it to the existing string, while * avoiding possible buffer overruns. */ #define strncatf(dest, fmt, ...) \ do { \ char _buff[1024]; \ snprintf(_buff, sizeof(_buff), fmt, ##__VA_ARGS__); \ strncat(dest, _buff, sizeof(dest) - strlen(dest) - 1); \ } while (0) static __inline__ uint8_t sct(struct cmap *cm, uint8_t s, uint8_t c, uint8_t t) { lc_info_t *lc = cm->linfo; uint8_t i; for (i = 0; i < cm->num_cores; i++, lc++) if (lc->sid == s && lc->cid == c && lc->tid == t) return lc->lid; return 0; } static int core_cmd(int argc __rte_unused, char **argv __rte_unused) { struct cmap *c; int i; c = cmap_create(); cli_printf("CPU : %s", c->model); cli_printf(" %d lcores, %u socket%s, %u core%s per socket and " "%u thread%s per core\n", c->num_cores, c->sid_cnt, c->sid_cnt > 1 ? "s" : "", c->cid_cnt, c->cid_cnt > 1 ? "s" : "", c->tid_cnt, c->tid_cnt > 1 ? "s" : ""); cli_printf("Socket : "); for (i = 0; i < c->sid_cnt; i++) cli_printf("%4d ", i); cli_printf("\n"); for (i = 0; i < c->cid_cnt; i++) { cli_printf(" Core %3d : [%2d,%2d] ", i, sct(c, 0, i, 0), sct(c, 0, i, 1)); if (c->sid_cnt > 1) cli_printf("[%2d,%2d] ", sct(c, 1, i, 0), sct(c, 1, i, 1)); if (c->sid_cnt > 2) cli_printf("[%2d,%2d] ", sct(c, 2, i, 0), sct(c, 2, i, 1)); if (c->sid_cnt > 3) cli_printf("[%2d,%2d] ", sct(c, 3, i, 0), sct(c, 3, i, 1)); cli_printf("\n"); } cmap_free(c); return 0; } static int huge_cmd(int argc __rte_unused, char **argv __rte_unused) { if (system("cat /proc/meminfo | grep -i huge")) return -1; return 0; } #ifdef CLI_DEBUG_CMDS static int sizes_cmd(int argc, char **argv) { if (argc > 1 && !strcmp(argv[1], "-?")) { cli_usage(); return 0; } cli_printf(" sizeof(struct cli) %zu\n", sizeof(struct cli)); cli_printf(" sizeof(struct cli_node) %zu\n", sizeof(struct cli_node)); cli_printf(" sizeof(args_t) %zu\n", sizeof(args_t)); cli_printf(" Total number of Nodes %d\n", this_cli->nb_nodes); cli_printf(" Number History lines %d\n", this_cli->nb_hist); cli_printf(" CLI_DEFAULT_NB_NODES %d\n", CLI_DEFAULT_NB_NODES); cli_printf(" CLI_DEFAULT_HIST_LINES %d\n", CLI_DEFAULT_HIST_LINES); cli_printf(" CLI_MAX_SCRATCH_LENGTH %d\n", CLI_MAX_SCRATCH_LENGTH); cli_printf(" CLI_MAX_PATH_LENGTH %d\n", CLI_MAX_PATH_LENGTH); cli_printf(" CLI_NAME_LEN %d\n", CLI_NAME_LEN); cli_printf(" CLI_MAX_ARGVS %d\n", CLI_MAX_ARGVS); cli_printf(" CLI_MAX_BINS %d\n", CLI_MAX_BINS); return 0; } #endif static int path_cmd(int argc __rte_unused, char **argv __rte_unused) { int i; char *str; cli_printf(" Path = .:"); for (i = 1; i < CLI_MAX_BINS; i++) { if (this_cli->bins[i] == NULL) continue; str = cli_path_string(this_cli->bins[i], NULL); /* trim off the trailing '/' if needed */ if (strlen(str) > 1) str[strlen(str) - 1] = '\0'; cli_printf("%s:", str); } cli_printf("\n"); return 0; } static const char *copyright = " BSD LICENSE\n" "\n" " Copyright(c) <2010-2026> Intel Corporation. All rights reserved.\n" "\n" " Redistribution and use in source and binary forms, with or without\n" " modification, are permitted provided that the following conditions\n" " are met:\n" "\n" " * Redistributions of source code must retain the above copyright\n" " notice, this list of conditions and the following disclaimer.\n" " * Redistributions in binary form must reproduce the above copyright\n" " notice, this list of conditions and the following disclaimer in\n" " the documentation and/or other materials provided with the\n" " distribution.\n" " * Neither the name of Intel Corporation nor the names of its\n" " contributors may be used to endorse or promote products derived\n" " from this software without specific prior written permission.\n" "\n" " THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n" " \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n" " LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n" " A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n" " OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n" " SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n" " LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n" " DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n" " THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n" " (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n" " OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" "\n" " SPDX-License-Identifier: BSD-3-Clause\n"; static int copyright_file(struct cli_node *node, char *buff, int len, uint32_t flags) { if (is_file_open(flags)) { node->file_data = (char *)(uintptr_t)copyright; node->file_size = strlen(copyright); node->fflags = CLI_DATA_RDONLY; if (is_file_eq(flags, (CLI_FILE_APPEND | CLI_FILE_WR))) node->foffset = node->file_size; return 0; } return cli_file_handler(node, buff, len, flags); } static int version_file(struct cli_node *node, char *buff, int len, uint32_t flags) { const char *data = rte_version(); if (is_file_open(flags)) { node->file_data = (char *)(uintptr_t)data; node->file_size = strlen(data); node->fflags = CLI_DATA_RDONLY; if (is_file_eq(flags, (CLI_FILE_APPEND | CLI_FILE_WR))) node->foffset = node->file_size; return 0; } return cli_file_handler(node, buff, len, flags); } static int sleep_cmd(int argc __rte_unused, char **argv) { uint32_t cnt = (atoi(argv[1]) * 4); if (rte_get_timer_hz() == 0) { cli_printf("pktgen_get_timer_hz() returned zero\n"); return 0; } while (cnt--) rte_delay_us_sleep(250 * 1000); return 0; } static int delay_cmd(int argc __rte_unused, char **argv) { int ms = atoi(argv[1]); int cnt = (ms / 1000) * 4; while (cnt--) { rte_delay_us_sleep(250 * 1000); ms -= 250; } if (ms > 0) rte_delay_us_sleep(ms * 1000); return 0; } static int mkdir_cmd(int argc, char **argv) { if (argc != 2) { cli_printf("Must have at least one path/directory\n"); return -1; } if (!cli_add_dir(argv[1], get_cwd())) return -1; return 0; } static int rm_cmd(int argc, char **argv) { struct cli_node *node; if (argc != 2) { cli_printf("usage: rm [dir|file|command]\n"); return -1; } if (!cli_find_node(argv[1], &node)) { cli_printf("Unable to find: %s\n", argv[1]); return -1; } return cli_remove_node(node); } static char * ver_cmd(const char *val __rte_unused) { return (char *)(uintptr_t)rte_version(); } static struct cli_map cli_env_map[] = { {10, "env"}, {20, "env get %s"}, {30, "env set %s %s"}, {40, "env del %s"}, {-1, NULL}}; // clang-format off static const char *cli_env_help[] = { "env - Display current environment variables", "env get - Get the requested variable", "env set - Set the given variable to string", "env del - Delete the given variable", NULL }; // clang-format on static int env_cmd(int argc, char **argv) { struct cli_map *m; m = cli_mapping(cli_env_map, argc, argv); if (!m) return cli_cmd_error("Environment command error:", "Env", argc, argv); switch (m->index) { case 10: cli_env_show(this_cli->env); break; case 20: cli_printf(" \"%s\" = \"%s\"\n", argv[2], cli_env_get(this_cli->env, argv[2])); break; case 30: cli_env_set(this_cli->env, argv[2], argv[3]); break; case 40: cli_env_del(this_cli->env, argv[2]); break; default: cli_help_show_group("Env"); return -1; } return 0; } static int script_cmd(int argc, char **argv) { if (argc <= 1) return -1; for (int i = 1; i < argc; i++) if (cli_execute_cmdfile(argv[1])) return -1; return 0; } static int echo_cmd(int argc, char **argv) { for (int i = 1; i < argc; i++) cli_printf("%s ", argv[i]); cli_printf("\n"); return 0; } static int version_cmd(int argc __rte_unused, char **argv __rte_unused) { cli_printf("Version: %s\n", rte_version()); return 0; } // clang-format off static struct cli_tree cli_default_tree[] = { c_file("copyright", copyright_file, "DPDK copyright information"), c_file("dpdk-version", version_file, "DPDK version"), c_bin("/sbin"), c_cmd("delay", delay_cmd, "delay a number of milliseconds"), c_cmd("sleep", sleep_cmd, "delay a number of seconds"), c_cmd("chelp", chelp_cmd, "CLI help - display information for DPDK"), c_cmd("mkdir", mkdir_cmd, "create a directory"), c_cmd("rm", rm_cmd, "remove a file or directory"), c_cmd("ls", ls_cmd, "ls [-lr] # list current directory"), c_cmd("cd", cd_cmd, "cd # change working directory"), c_cmd("pwd", pwd_cmd, "pwd # display current working directory"), c_cmd("screen.clear", scrn_cmd, "screen.clear # clear the screen"), c_cmd("quit", quit_cmd, "quit # quit the application"), c_alias("exit", "quit", "exit # exit the application"), c_cmd("history", hist_cmd, "history # display the current history"), c_cmd("more", more_cmd, "more # display a file content"), #ifdef CLI_DEBUG_CMDS c_cmd("sizes", sizes_cmd, "sizes # display some internal sizes"), #endif c_cmd("cmap", core_cmd, "cmap # display the core mapping"), c_cmd("hugepages", huge_cmd, "hugepages # display hugepage info"), c_cmd("path", path_cmd, "display the execution path for commands"), c_cmd("env", env_cmd, "Show/del/get/set environment variables"), c_cmd("script", script_cmd, "load and process cli command files"), c_cmd("echo", echo_cmd, "simple echo a string to the screen"), c_cmd("version", version_cmd, "Display version information"), /* The following are environment variables */ c_str("SHELL", NULL, "CLI shell"), c_str("DPDK_VER", ver_cmd, ""), c_end() }; // clang-format on int cli_default_tree_init(void) { int ret = 0; if (this_cli->flags & CLI_DEFAULT_TREE) return ret; this_cli->flags |= CLI_DEFAULT_TREE; /* Add the list of commands/dirs in cli_cmds.c file */ if ((ret = cli_add_tree(NULL, cli_default_tree)) == 0) { cli_help_add("Env", cli_env_map, cli_env_help); } if (ret) { RTE_LOG(ERR, EAL, "Unable to add commands or directories\n"); this_cli->flags &= ~CLI_DEFAULT_TREE; } return ret; } ================================================ FILE: lib/cli/cli_cmds.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #ifndef _CLI_CMDS_H_ #define _CLI_CMDS_H_ /** * @file * CLI built-in command tree. * * Provides helpers to populate the default directory structure and common * built-in commands (e.g., ls/cd/pwd/help/history). */ #ifdef __cplusplus extern "C" { #endif /** * Add the default set of directories and commands. * * @note Uses a thread variable called this_cli * * @return * 0 is ok, -1 is error */ int cli_default_tree_init(void); #ifdef __cplusplus } #endif #endif /* _CLI_CMDS_H_ */ ================================================ FILE: lib/cli/cli_common.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #ifndef _CLI_COMMON_H_ #define _CLI_COMMON_H_ #include #include #include #include #include #include #include #include #include /** * @file * CLI common output helpers. * * Provides cli_printf(), the primary console output routine used throughout * the CLI library, routed through the active cli_scrn file descriptor. */ #ifdef __cplusplus extern "C" { #endif #ifndef RTE_ASSERT #define RTE_ASSERT RTE_VERIFY #endif /** * printf-like routine to write formatted text to the CLI console. * * Output is written to this_scrn->fd_out and flushed immediately. * * @param fmt * printf-compatible format string. * @param ... * Variable arguments for @p fmt. * @return * Number of characters written, as returned by vfprintf(). */ static inline int __attribute__((format(printf, 1, 2))) cli_printf(const char *fmt, ...) { va_list vaList; int n; va_start(vaList, fmt); n = vfprintf(this_scrn->fd_out, fmt, vaList); va_end(vaList); fflush(this_scrn->fd_out); return n; } #ifdef __cplusplus } #endif #endif /* _CLI_COMMON_H_ */ ================================================ FILE: lib/cli/cli_env.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /* Created by: Keith Wiles @ intel */ /** * @file * CLI environment variable implementation. */ #include #include "cli.h" #include "cli_env.h" static int env_free(struct cli_env *env, struct env_node *n) { if (!env || !n) return -1; TAILQ_REMOVE(&env->head, n, next); free((char *)(uintptr_t)n->var); free((char *)(uintptr_t)n->val); free(n); env->count--; return 0; } struct cli_env * cli_env_create(void) { struct cli_env *env; env = (struct cli_env *)calloc(1, sizeof(struct cli_env)); if (!env) return NULL; memset(env, '\0', sizeof(struct cli_env)); TAILQ_INIT(&env->head); return env; } void cli_env_destroy(struct cli_env *env) { struct env_node *n; if (!env) return; while (!TAILQ_EMPTY(&env->head)) { n = TAILQ_FIRST(&env->head); env_free(env, n); } free(env); } static struct env_node * find_env(struct cli_env *env, const char *var) { struct env_node *n; TAILQ_FOREACH (n, &env->head, next) { if (!strcmp(var, n->var)) return n; } return NULL; } static struct env_node * __env_set(struct cli_env *env, const char *var, const char *val) { struct env_node *n; const char *safe_val; if (!env || !var) return NULL; safe_val = (val) ? val : ""; n = find_env(env, var); if (n) { char *new_val = strdup(safe_val); if (!new_val) return NULL; free((char *)(uintptr_t)n->val); n->val = new_val; return n; } n = (struct env_node *)calloc(1, sizeof(struct env_node)); if (!n) return NULL; n->var = strdup(var); n->val = strdup(safe_val); if (!n->var || !n->val) { free((char *)(uintptr_t)n->var); free((char *)(uintptr_t)n->val); free(n); return NULL; } TAILQ_INSERT_TAIL(&env->head, n, next); env->count++; return n; } int cli_env_set(struct cli_env *env, const char *var, const char *val) { return (__env_set(env, var, val) == NULL) ? -1 : 0; } int cli_env_string(struct cli_env *env, const char *var, cli_sfunc_t sfunc, const char *val) { struct env_node *n; n = __env_set(env, var, val); if (!n) return -1; n->sfunc = sfunc; return 0; } const char * cli_env_get(struct cli_env *env, const char *var) { struct env_node *n; n = find_env(env, var); if (!n) return NULL; return (n->sfunc) ? n->sfunc(n->val) : n->val; } int cli_env_del(struct cli_env *env, const char *var) { return env_free(env, find_env(env, var)); } /* strings to be substituted are of the form ${foo} or $(foo) */ void cli_env_substitution(struct cli_env *env, char *line, int sz) { char *p, *s, *e, *t, *tmp; const char *v; size_t remaining; if (!env || !line || sz <= 0) return; tmp = calloc(1, (size_t)sz + 1); if (!tmp) return; /* Determine the end of the string */ e = line + sz; remaining = (size_t)sz; for (p = line, t = tmp; (p[0] != '\0') && (p < e); p++) { /* Look for the '$' then the open bracket */ if (p[0] != '$') goto next; /* find opening bracket */ if ((p[1] != '{') && (p[1] != '(')) goto next; /* find closing bracket */ s = strchr(p, (p[1] == '{') ? '}' : ')'); if (!s) goto next; /* terminate the variable string */ *s = '\0'; v = cli_env_get(env, &p[2]); if (!v) v = "oops!"; if (remaining > 1) { size_t vlen = strnlen(v, remaining - 1); memcpy(t, v, vlen); t += vlen; remaining -= vlen; } p = s; /* Point 'p' past the variable */ continue; next: if (remaining <= 1) break; *t++ = *p; remaining--; } *t = '\0'; snprintf(line, sz, "%s", tmp); free(tmp); } int cli_env_get_all(struct cli_env *env, struct env_node **list, int max_size) { struct env_node *node; int n = 0; if (!env) return 0; TAILQ_FOREACH (node, &env->head, next) { list[n++] = node; if (n == max_size) break; } return n; } void cli_env_show(struct cli_env *env) { struct env_node *node; TAILQ_FOREACH (node, &env->head, next) { if (node->sfunc) cli_printf(" \"%s\" = \"%s\"\n", node->var, node->sfunc(node->val)); else cli_printf(" \"%s\" = \"%s\"\n", node->var, node->val); } } ================================================ FILE: lib/cli/cli_env.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /* Created by Keith Wiles @ intel.com */ #include #ifndef _CLI_ENV_H_ #define _CLI_ENV_H_ /** * @file * CLI environment variables. * * Supports simple key/value variables and dynamic variables backed by a * callback. Variables can be expanded in user input (e.g. $(FOO)). */ #ifdef __cplusplus extern "C" { #endif struct cli; typedef char *(*cli_sfunc_t)(const char *str); /**< Callback to compute a dynamic environment variable string. */ struct env_node { TAILQ_ENTRY(env_node) next; const char *var; /**< Variable name */ const char *val; /**< Variable value (owned by env) */ cli_sfunc_t sfunc; /**< Optional callback to compute value */ }; struct cli_env { TAILQ_HEAD(, env_node) head; /**< link list of vars */ int count; /**< Number of variables */ }; /** * Create an environment variable container. * * @return * NULL on error or cli_env pointer */ struct cli_env *cli_env_create(void); /** * Destroy an environment variable container. * * @param env * Pointer to the environment structure */ void cli_env_destroy(struct cli_env *env); /** * Set an environment variable. * * Adds or replaces a variable with a string value. * * @param env * The cli_env pointer * @param var * Pointer to the variable name const string * @param val * Pointer to the string assigned to the variable * @return * 0 is OK was added or replaced or -1 if not valid */ int cli_env_set(struct cli_env *env, const char *var, const char *val); /** * Set an environment variable with an optional callback. * * If @p sfunc is non-NULL, it may be used to generate a dynamic string value. * * @param env * The cli_env pointer * @param var * Pointer to the variable name const string. * @param sfunc * Pointer to function (optional) * @param val * Pointer to the string assigned to the variable * @return * 0 is OK was added or replaced or -1 if not valid */ int cli_env_string(struct cli_env *env, const char *var, cli_sfunc_t sfunc, const char *val); /** * Get an environment variable value. * * @param env * The cli_env pointer * @param var * The const string variable name * @return * NULL if not found or the const string */ const char *cli_env_get(struct cli_env *env, const char *var); /** * Remove an environment variable. * * @param env * The cli_env pointer * @param var * The const string variable name * @return * 0 is OK or -1 if not found. */ int cli_env_del(struct cli_env *env, const char *var); /** * Perform environment variable substitution on a command line. * * Expands occurrences of $(VAR) using values in @p env. * * @param env * Pointer to the enviroment structure * @param line * Pointer to the line to parse * @param sz * Number of total characters the line can hold. * @return * N/A */ void cli_env_substitution(struct cli_env *env, char *line, int sz); /** * Get the number of variables in the environment. * * @param env * Pointer to environment structure * @return * Number of environment variables */ static inline int cli_env_count(struct cli_env *env) { return env->count; } /** * Get a snapshot list of environment variables. * * @param env * Pointer to environment list * @param list * Array of env_node pointers to be returned * @param max_size * Max size of the list array */ int cli_env_get_all(struct cli_env *env, struct env_node **list, int max_size); /** * Print all environment variables. * * @param env * Pointer to the cli_env structure. */ void cli_env_show(struct cli_env *env); #ifdef __cplusplus } #endif #endif /* _CLI_ENV_H_ */ ================================================ FILE: lib/cli/cli_file.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #include "cli.h" #include "cli_input.h" #include "cli_file.h" struct cli_node * cli_file_open(const char *path, const char *type) { struct cli_node *node; uint32_t flags = 0; if (!path) return NULL; if (!cli_find_node(path, &node)) return NULL; if (!is_file(node)) return NULL; if (type && strlen(type)) { if (strchr(type, 'r')) file_set(flags, CLI_FILE_RD); if (strchr(type, 'w')) file_set(flags, CLI_FILE_WR); if (strchr(type, '+')) file_set(flags, CLI_FILE_APPEND); } else file_set(flags, CLI_FILE_RD); file_set(flags, CLI_FILE_OPEN); if (node->ffunc(node, NULL, 0, flags)) return NULL; return node; } int cli_file_close(struct cli_node *node) { uint32_t flags = CLI_FILE_CLOSE; if (!node) return -1; return node->ffunc(node, NULL, 0, flags); } int cli_file_read(struct cli_node *node, char *buff, int len) { uint32_t flags = CLI_FILE_RD; if (!node || !is_file(node)) return -1; return node->ffunc(node, buff, len, flags); } int cli_file_write(struct cli_node *node, char *buff, int len) { uint32_t flags = CLI_FILE_WR; if (!node || !is_file(node)) return -1; if (is_data_rdonly(node->fflags)) return -1; return node->ffunc(node, buff, len, flags); } int cli_file_seek(struct cli_node *node, int offset, uint32_t whence) { if (!node || !is_file(node)) return -1; switch (whence) { case CLI_SEEK_SET: case CLI_SEEK_CUR: case CLI_SEEK_END: break; default: return -1; } return node->ffunc(node, NULL, offset, whence); } int cli_readline(struct cli_node *node, char *buff, int len) { int i, n; char c; if (!node || !buff || !is_file(node)) return -1; /* Needs to be optimized for performance ??? */ for (i = 0, c = '\0'; i < len && c != '\n'; i++) { n = cli_file_read(node, &c, 1); if (n <= 0) break; buff[i] = c; } buff[i] = '\0'; return i; } /* Add generic function for handling files */ int cli_file_handler(struct cli_node *node, char *buff, int len, uint32_t opt) { char *p; if (!node || !is_file(node)) return -1; if (opt & (CLI_SEEK_SET | CLI_SEEK_CUR | CLI_SEEK_END)) { size_t saved = node->foffset; if (is_seek_set(opt)) { if (len < 0) return -1; node->foffset = len; } else if (is_seek_cur(opt)) { if (len < 0) { len = abs(len); if ((size_t)len > node->file_size) node->foffset = 0; else node->foffset -= len; } else node->foffset += len; } else if (is_seek_end(opt)) { if (len < 0) { len = abs(len); if ((size_t)len > node->file_size) node->foffset = 0; else node->foffset = node->file_size - len; } else node->foffset = node->file_size + len; } if (node->foffset > node->file_size) { if (!(node->fflags & CLI_FILE_APPEND)) { node->foffset = saved; if (node->fflags & (CLI_FREE_DATA | CLI_DATA_EXPAND)) { char *data; data = realloc(node->file_data, node->foffset); if (!data) return -1; node->file_data = data; node->file_size = node->foffset; } else /* TODO: add code to expand the file */ return -1; } else { node->foffset = saved; return -1; } } } else if (is_seek_cur(opt)) node->foffset += len; else if (is_seek_end(opt)) node->foffset += len; else if (is_file_close(opt)) { if (node->file_data && (node->fflags & CLI_FREE_DATA)) { free(node->file_data); node->file_data = NULL; node->file_size = 0; } node->foffset = 0; } else if (is_file_open(opt)) { if (is_file_append(opt)) { node->fflags |= CLI_FILE_APPEND; node->foffset = node->file_size; } if (is_file_wr(opt)) node->fflags |= CLI_FILE_WR; } else if (is_file_rd(opt)) { if (len <= 0) return 0; if (node->foffset >= node->file_size) return 0; len = RTE_MIN(len, (int)(node->file_size - node->foffset)); p = node->file_data + node->foffset; memcpy(buff, p, len); node->foffset += len; } else if (is_file_wr(opt)) { if (is_data_rdonly(node->fflags)) return -1; if (len <= 0) return 0; if ((node->foffset + len) < node->file_size) { p = node->file_data + node->foffset; memcpy(p, buff, len); node->foffset += len; } else { char *new_data = realloc(node->file_data, (node->foffset + len)); if (!new_data) return -1; node->file_data = new_data; node->file_size = node->foffset + len; node->foffset += len; } } return len; } struct cli_node * cli_file_create(const char *path, const char *type) { struct cli_node *node, *parent; char *file, *mypath; char *data = NULL; node = cli_file_open(path, type); if (node) return node; mypath = strdup(path); if (!mypath) return NULL; file = basename(mypath); data = calloc(1, CLI_FILE_SIZE); if (data) { parent = cli_last_dir_in_path(path); if (parent) { node = cli_add_file(file, parent, cli_file_handler, ""); if (node) { node->file_data = data; node->file_size = CLI_FILE_SIZE; node->fflags = CLI_FREE_DATA; if (type && strchr(type, 'r') && !strchr(type, 'w') && !strchr(type, '+')) node->fflags |= CLI_DATA_RDONLY; node->foffset = 0; free(mypath); return node; } } } free(mypath); free(data); return NULL; } int cli_system(char *p) { char buf[1024]; size_t n; FILE *f; f = popen(p, "r"); if (!f) return -1; while ((n = fread(buf, 1, sizeof(buf), f)) > 0) cli_write(buf, n); pclose(f); return n; } ================================================ FILE: lib/cli/cli_file.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #ifndef _CLI_FILE_H_ #define _CLI_FILE_H_ /** * @file * CLI file node helpers. * * Implements in-memory file nodes used by the CLI tree. File nodes can be * backed by a callback for dynamic content or can store data directly. */ #include "cli.h" #ifdef __cplusplus extern "C" { #endif #define CLI_FILE_SIZE 1024 enum { /* File operations opt */ CLI_FILE_RD = 0x0001, /**< Perform a read on the file */ CLI_FILE_WR = 0x0002, /**< Perform a write on the file */ CLI_FILE_APPEND = 0x0004, /**< Append data to the file */ CLI_FILE_OPEN = 0x0008, /**< Open the file */ CLI_FILE_CLOSE = 0x0010, /**< Close the file */ CLI_FILE_CREATE = 0x0020, /**< Create a new file */ /* File seek operations */ CLI_SEEK_SET = 0x0100, /**< Seek to an absolute offset */ CLI_SEEK_CUR = 0x0200, /**< Seek relative to the current position */ CLI_SEEK_END = 0x0400, /**< Seek relative to the end of the file */ /* File information in cli_node.fflags */ CLI_DATA_RDONLY = 0x1000, /**< File data is read-only */ CLI_FREE_DATA = 0x2000, /**< File data buffer must be freed on close */ CLI_DATA_EXPAND = 0x4000 /**< File buffer can be expanded dynamically */ }; #define file_set(f, v) \ do { \ (f) |= (v); \ } while ((0)) #define file_clr(f, v) \ do { \ (f) &= ~(v); \ } while ((0)) /** Test whether any flag in @p cmpflags is set in @p opt. Non-zero if set. */ static inline int is_file_set(uint32_t opt, uint32_t cmpflags) { return opt & cmpflags; } /** Return non-zero if CLI_FILE_RD is set in @p opt. */ static inline int is_file_rd(uint32_t opt) { return is_file_set(opt, CLI_FILE_RD); } /** Return non-zero if CLI_FILE_WR is set in @p opt. */ static inline int is_file_wr(uint32_t opt) { return is_file_set(opt, CLI_FILE_WR); } /** Return non-zero if CLI_FILE_APPEND is set in @p opt. */ static inline int is_file_append(uint32_t opt) { return is_file_set(opt, CLI_FILE_APPEND); } /** Return non-zero if CLI_FILE_OPEN is set in @p opt. */ static inline int is_file_open(uint32_t opt) { return is_file_set(opt, CLI_FILE_OPEN); } /** Return non-zero if CLI_FILE_CLOSE is set in @p opt. */ static inline int is_file_close(uint32_t opt) { return is_file_set(opt, CLI_FILE_CLOSE); } /** Return non-zero if CLI_FILE_CREATE is set in @p opt. */ static inline int is_file_create(uint32_t opt) { return is_file_set(opt, CLI_FILE_CREATE); } /** Return non-zero if CLI_DATA_RDONLY is set in @p flags. */ static inline int is_data_rdonly(uint32_t flags) { return is_file_set(flags, CLI_DATA_RDONLY); } /** Return non-zero if all bits in @p cmpflags are set in @p opt. */ static inline int is_file_eq(uint32_t opt, uint32_t cmpflags) { return ((opt & cmpflags) == cmpflags); } /** Return non-zero if CLI_SEEK_SET is set in @p opt. */ static inline int is_seek_set(uint32_t opt) { return is_file_set(opt, CLI_SEEK_SET); } /** Return non-zero if CLI_SEEK_CUR is set in @p opt. */ static inline int is_seek_cur(uint32_t opt) { return is_file_set(opt, CLI_SEEK_CUR); } /** Return non-zero if CLI_SEEK_END is set in @p opt. */ static inline int is_seek_end(uint32_t opt) { return is_file_set(opt, CLI_SEEK_END); } /** * Open a file. * * @param path * Path string for file * @param type * Type of open string r, w, and/or + characters * @return * Node pointer or NULL on error */ struct cli_node *cli_file_open(const char *path, const char *type); /** * Close a file * * @param node * Pointer to file node * @return * 0 on OK and -1 on error */ int cli_file_close(struct cli_node *node); /** * read data from a file * * @param node * Pointer to file node * @param buff * Pointer to place to put the data * @param len * Max Number of bytes to read * @return * Number of bytes read and -1 on error */ int cli_file_read(struct cli_node *node, char *buff, int len); /** * write data to a file * * @param node * Pointer to file node * @param buff * Pointer to place to get the data * @param len * Max Number of bytes to write * @return * Number of bytes written and -1 on error */ int cli_file_write(struct cli_node *node, char *buff, int len); /** * Seek within a CLI file node. * * @param node * Pointer to file node * @param offset * Byte offset to apply (interpretation depends on @p whence) * @param whence * Seek mode: CLI_SEEK_SET, CLI_SEEK_CUR, or CLI_SEEK_END * @return * Resulting file offset after the seek, or -1 on error */ int cli_file_seek(struct cli_node *node, int offset, uint32_t whence); /** * Read one line from a CLI file node into a buffer. * * Reads characters until a newline or end-of-file. The newline is consumed * but not stored. * * @param node * Pointer to the file node to read from * @param buff * Buffer to store the line data * @param len * Maximum number of bytes the buffer can hold * @return * Number of bytes stored in @p buff (excluding the newline), or -1 on error */ int cli_readline(struct cli_node *node, char *buff, int len); /** * create a data file in memory will be lost at reset. * * @param path * Path string for file * @param type * Type of open string r, w, and/or + characters * @return * Node pointer or NULL on error */ struct cli_node *cli_file_create(const char *path, const char *type); /** * Generic file function for basic file handling * * @param node * Pointer to file node * @param buff * place to put the line data. * @param len * Max buff size * @param opt * Flags for file handling * @return * Number of bytes read not including the newline */ int cli_file_handler(struct cli_node *node, char *buff, int len, uint32_t opt); /** * Execute a host shell command string. * * Passes @p p to the system() call. Used by CLI built-in commands that * need to run external programs. * * @param p * Null-terminated shell command string to execute. * @return * Return value from system(), or -1 on error. */ int cli_system(char *p); #ifdef __cplusplus } #endif #endif /* _CLI_FILE_H_ */ ================================================ FILE: lib/cli/cli_gapbuf.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /* inspired by an email/code written by: Joseph H. Allen, 9/10/89 */ #include #include "cli.h" /* Copy the gap buffer data into a user supplied buffer */ uint32_t gb_copy_to_buf(struct gapbuf *gb, char *dst, uint32_t size) { uint32_t tcnt = 0; uint32_t to_copy; uint32_t left_len, right_len; uint32_t left_copy, right_copy; RTE_ASSERT(gb != NULL); RTE_ASSERT(dst != NULL); /* Always NUL terminate even if size is 0 */ if (size == 0) { dst[0] = '\0'; return 0; } /* Only copy up to the smaller of request size or data size */ to_copy = RTE_MIN(size, gb_data_size(gb)); left_len = (uint32_t)(gb->gap - gb->buf); right_len = (uint32_t)(gb->ebuf - gb->egap); left_copy = RTE_MIN(left_len, to_copy); if (left_copy) { rte_memcpy(dst, gb->buf, left_copy); dst += left_copy; tcnt += left_copy; } right_copy = RTE_MIN(right_len, (to_copy - left_copy)); if (right_copy) { rte_memcpy(dst, gb->egap, right_copy); dst += right_copy; tcnt += right_copy; } *dst = '\0'; return tcnt; } int gb_reset_buf(struct gapbuf *gb) { int size; RTE_ASSERT(gb != NULL); size = gb_buf_size(gb); memset(gb->buf, ' ', size); gb->point = gb->buf; gb->gap = gb->buf; gb->egap = gb->buf + size; gb->ebuf = gb->egap; return 0; } /* release the buffer and allocate a new buffer with correct offsets */ int gb_init_buf(struct gapbuf *gb, int size) { RTE_ASSERT(gb != NULL); free(gb->buf); gb->buf = calloc(1, size); if (!gb->buf) return -1; gb->ebuf = gb->buf + size; return gb_reset_buf(gb); } /* Create the gap buffer structure and init the pointers */ struct gapbuf * gb_create(void) { struct gapbuf *gb; gb = calloc(1, sizeof(struct gapbuf)); if (!gb) return NULL; memset(gb, '\0', sizeof(struct gapbuf)); gb_init_buf(gb, GB_DEFAULT_GAP_SIZE); return gb; } /* Release the gap buffer data and memory */ void gb_destroy(struct gapbuf *gb) { if (gb) { free(gb->buf); free(gb); } } /* Dump out the gap buffer and pointers in a readable format */ void gb_dump(struct gapbuf *gb, const char *msg) { #ifdef CLI_DEBUG_ENABLED char *p; uint32_t i; if (msg) fprintf(stderr, "\n%s Gap: buf_size %u, gap_size %u\n", msg, gb_buf_size(gb), gb_gap_size(gb)); else fprintf(stderr, "\nGap: buf_size %u, gap_size %u\n", gb_buf_size(gb), gb_gap_size(gb)); fprintf(stderr, " buf %p, ", gb->buf); fprintf(stderr, "gap %p, ", gb->gap); fprintf(stderr, "point %p, ", gb->point); fprintf(stderr, "egap %p, ", gb->egap); fprintf(stderr, "ebuf %p\n", gb->ebuf); fprintf(stderr, " "); for (i = 0, p = gb->buf; p < gb->ebuf; i++, p++) fprintf(stderr, "%c", "0123456789"[i % 10]); fprintf(stderr, "\n"); fprintf(stderr, "<"); for (p = gb->buf; p < gb->ebuf; p++) fprintf(stderr, "%c", ((*p >= ' ') && (*p <= '~')) ? *p : '.'); fprintf(stderr, ">\n "); for (p = gb->buf; p <= gb->ebuf; p++) { if ((p == gb->gap) && (p == gb->egap)) fprintf(stderr, "*"); else if (p == gb->gap) fprintf(stderr, "["); else if (p == gb->egap) fprintf(stderr, "]"); else fprintf(stderr, " "); } fprintf(stderr, "\n "); for (p = gb->buf; p <= gb->ebuf; p++) fprintf(stderr, "%c", (p == gb->point) ? '^' : ' '); fprintf(stderr, "\n"); cli_redisplay_line(); #else (void)gb; (void)msg; #endif } ================================================ FILE: lib/cli/cli_gapbuf.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /* inspired by an email/code written by: Joseph H. Allen, 9/10/89 */ #include #include #include #ifndef _CLI_GAPBUF_H_ #define _CLI_GAPBUF_H_ /** * @file * CLI Gap Buffer support * */ #ifdef __cplusplus extern "C" { #endif #define GB_DEFAULT_GAP_SIZE 8 struct gapbuf { char *buf; /**< Pointer to start of the allocated buffer */ char *ebuf; /**< Pointer one past the end of the allocated buffer */ char *point; /**< Current insertion/cursor position */ char *gap; /**< Pointer to the start of the gap */ char *egap; /**< Pointer to the end of the gap (first char after gap) */ }; /** * Create the Gap Buffer structure * * @return * NULL on error or pointer to struct gapbuf */ struct gapbuf *gb_create(void); /** * Destroy * * @param gb * The gapbuf structure pointer. * @return * N/A */ void gb_destroy(struct gapbuf *gb); /** * Allocate buffer and initialize, if buffer exist free and reallocate. * * @param gb * The gapbuf structure pointer. * @param size * Init the gap buffer to the size given * @return * 0 is OK or Error */ int gb_init_buf(struct gapbuf *gb, int size); /** * Reset the gap buffer * * @param gb * The gapbuf structure pointer. * @return * 0 is OK or Error */ int gb_reset_buf(struct gapbuf *gb); /** * Copy the buffer data into a given buffer * * @param gb * The gapbuf structure pointer. * @param dst * Location to copy the data into * @param size * Total number of bytes to copy * @return * Number of bytes copied into the buffer */ uint32_t gb_copy_to_buf(struct gapbuf *gb, char *dst, uint32_t size); /** * Print out a debug list of the Gap buffer and pointers * * @param gb * The gapbuf structure pointer. * @param msg * Message to print out befor dump of buffer data * @return * N/A */ void gb_dump(struct gapbuf *gb, const char *msg); /********************************************************/ /** * Return the number of bytes total in the buffer includes gap size * * @param gb * The gapbuf structure pointer. * @return * The number of bytes in the buffer */ static inline uint32_t gb_buf_size(struct gapbuf *gb) { return gb->ebuf - gb->buf; } /** * Return the gap size in bytes * * @param gb * The gapbuf structure pointer. * @return * Number of bytes in the gap. */ static inline uint32_t gb_gap_size(struct gapbuf *gb) { return gb->egap - gb->gap; } /** * Number of data bytes * * @param gb * The gapbuf structure pointer. * @return * Number of data bytes */ static inline uint32_t gb_data_size(struct gapbuf *gb) { return (gb->ebuf - gb->buf) - (gb->egap - gb->gap); } /** * Return the start of the buffer address * * @param gb * The gapbuf structure pointer. * @return * The pointer to the start of the buffer */ static inline char * gb_start_of_buf(struct gapbuf *gb) { return gb->buf; } /** * Return the pointer to the gap start * * @param gb * The gapbuf structure pointer. * @return * Pointer to the gap start location */ static inline char * gb_start_of_gap(struct gapbuf *gb) { return gb->gap; } /** * Return the pointer to the end of the gap * * @param gb * The gapbuf structure pointer. * @return * The end of the gap pointer */ static inline char * gb_end_of_gap(struct gapbuf *gb) { return gb->egap; } /** * Return the pointer to the end of the buffer * * @param gb * The gapbuf structure pointer. * @return * End of buffer pointer */ static inline char * gb_end_of_buf(struct gapbuf *gb) { return gb->ebuf; } /** * Return the point location * * @param gb * The gapbuf structure pointer. * @return * Pointer to point */ static inline char * gb_point_at(struct gapbuf *gb) { return gb->point; } /** * Is point at start of buffer * * @param gb * The gapbuf structure pointer. * @return * true if point is at start of buffer */ static inline int gb_point_at_start(struct gapbuf *gb) { return (gb->point == gb->buf); } /** * is point at the end of buffer * * @param gb * The gapbuf structure pointer. * @return * true if the point is at the end of buffer */ static inline int gb_point_at_end(struct gapbuf *gb) { return (gb->ebuf == gb->point); } /** * is point at start of gap * * @param gb * The gapbuf structure pointer. * @return * true if the point is at the gap start */ static inline int gb_point_at_gap(struct gapbuf *gb) { return (gb->gap == gb->point); } /** * Set point to a given index into the buffer * * @param gb * The gapbuf structure pointer. * @param idx * Index into the buffer to put point * @return * N/A */ static inline void gb_set_point(struct gapbuf *gb, int idx) { if (idx == -1) { gb->point = gb->ebuf; return; } gb->point = gb->buf + idx; if (gb->point > gb->gap) gb->point += gb->egap - gb->gap; } /** * Get offset of point * * @param gb * The gapbuf structure pointer. * @return * Offset of point from start of buffer */ static inline int gb_point_offset(struct gapbuf *gb) { if (gb->point > gb->egap) return (gb->point - gb->buf) - (gb->egap - gb->gap); else return gb->point - gb->buf; } /** * Return true if point is at end of buffer data. * * @param gb * The gapbuf structure pointer. * @return * True if end of buffer data */ static inline int gb_eof(struct gapbuf *gb) { return (gb->point == gb->gap) ? (gb->egap == gb->ebuf) : (gb->point == gb->ebuf); } /********************************************************/ /** * Move the gap to the location of the point. * * @param gb * The gapbuf structure pointer. * @return * N/A */ static inline void gb_move_gap_to_point(struct gapbuf *gb) { if (gb->point == gb->gap) return; if (gb->point == gb->egap) gb->point = gb->gap; else { int cnt; if (gb->point < gb->gap) { cnt = gb->gap - gb->point; memmove(gb->egap - cnt, gb->point, cnt); gb->egap -= cnt; gb->gap = gb->point; } else if (gb->point > gb->egap) { cnt = gb->point - gb->egap; memmove(gb->gap, gb->egap, cnt); gb->gap += cnt; gb->egap = gb->point; gb->point = gb->gap; } else { /* This case when point is between gap and egap. */ cnt = gb->point - gb->gap; memmove(gb->gap, gb->egap, cnt); gb->egap += cnt; gb->gap += cnt; gb->point = gb->gap; } } } /** * Expand the buffer by the given bytes. * * @param gb * The gapbuf structure pointer. * @param more * The number of bytes to increase the buffer * @return * N/A */ static inline void gb_expand_buf(struct gapbuf *gb, uint32_t more) { if (((gb->ebuf - gb->buf) + more) > gb_buf_size(gb)) { char *old = gb->buf; more = (gb->ebuf - gb->buf) + more + GB_DEFAULT_GAP_SIZE; #ifdef __GNUC__ #if GCC_VERSION >= 120200 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wuse-after-free" #endif #endif gb->buf = (char *)realloc(gb->buf, more); if (gb->buf == NULL) rte_panic("realloc(%d) in %s failed\n", more, __func__); gb->point += (gb->buf - old); gb->ebuf += (gb->buf - old); gb->gap += (gb->buf - old); gb->egap += (gb->buf - old); #ifdef __GNUC__ #if GCC_VERSION >= 120200 #pragma GCC diagnostic pop #endif #endif gb->buf = (char *)realloc(gb->buf, more); } } /** * Expand the Gap by the size given. * * @param gb * The gapbuf structure pointer. * @param size * Increase the gap by the number of bytes given * @return * N/A */ static inline void gb_expand_gap(struct gapbuf *gb, uint32_t size) { if (size > gb_gap_size(gb)) { size += GB_DEFAULT_GAP_SIZE; gb_expand_buf(gb, size); memmove(gb->egap + size, gb->egap, gb->ebuf - gb->egap); gb->egap += size; gb->ebuf += size; } } /** * Get the byte at the point location. * * @param gb * The gapbuf structure pointer. * @return * Byte at point */ static inline char gb_get(struct gapbuf *gb) { if (gb->point == gb->gap) gb->point = gb->egap; return *gb->point; } /** * Get the byte at the point - 1 location. * * @param gb * The gapbuf structure pointer. * @return * Byte at point */ static inline char gb_get_prev(struct gapbuf *gb) { if (gb->point == gb->egap) gb->point = gb->gap; if (gb->point == gb->buf) { if (gb->point == gb->gap) return '\0'; else return *gb->point; } return *(gb->point - 1); } /** * Get the byte at the point + 1 location. * * @param gb * The gapbuf structure pointer. * @return * Byte at point */ static inline char gb_get_next(struct gapbuf *gb) { if (gb->point == gb->gap) gb->point = gb->egap; if (gb->point == gb->ebuf) return *gb->point; return *(gb->point + 1); } /** * Put character at point * * @param gb * The gapbuf structure pointer. * @param c * The character to put at point * @return * N/A */ static inline void gb_put(struct gapbuf *gb, char c) { if (gb->point == gb->gap) gb->point = gb->egap; if (gb->point == gb->ebuf) { gb_expand_buf(gb, 1); gb->ebuf++; } *gb->point = c; } /** * Get the byte at the point location and advance point * * @param gb * The gapbuf structure pointer. * @return * Byte at point */ static inline char gb_getc(struct gapbuf *gb) { if (gb->point == gb->gap) { gb->point = gb->egap + 1; return *gb->egap; } return *(gb->point++); } /** * Move point left and return character at point. * * fmrgetc() (point == ehole ? *(point = hole - 1) : *(--point)) * * @param gb * The gapbuf structure pointer. * @return * Character at point */ static inline char gb_getc_prev(struct gapbuf *gb) { if (gb->point == gb->egap) gb->point = gb->gap; return *(--gb->point); } /** * Put character at point and advance point * * @param gb * The gapbuf structure pointer. * @param c * The character to put at point * @return * N/A */ static inline void gb_putc(struct gapbuf *gb, char c) { gb_move_gap_to_point(gb); if (gb->point == gb->ebuf) { gb_expand_buf(gb, 1); gb->ebuf++; } *(gb->gap++) = c; gb->point++; } /** * Insert the character at point and move point. * * @param gb * The gapbuf structure pointer. * @param c * The character to add to buffer * @return * N/A */ static inline void gb_insert(struct gapbuf *gb, char c) { if (gb->point != gb->gap) gb_move_gap_to_point(gb); if (gb->gap == gb->egap) gb_expand_gap(gb, 1); gb_putc(gb, c); } /** * Delete the character(s) at point * * @param gb * The gapbuf structure pointer. * @param cnt * Number of characters to delete at point. * @return * N/A */ static inline void gb_del(struct gapbuf *gb, int cnt) { if (gb->point != gb->gap) gb_move_gap_to_point(gb); gb->egap += cnt; } /** * Insert a string at point and move point * * @param gb * The gapbuf structure pointer. * @param str * String to insert at point. * @param size * Number of bytes to insert; if zero, strlen(@p str) is used. * @return * Number of bytes inserted */ static inline uint32_t gb_str_insert(struct gapbuf *gb, char *str, uint32_t size) { int len; if (size == 0) size = strlen(str); if (size == 0) return 0; gb_move_gap_to_point(gb); if (size > gb_gap_size(gb)) gb_expand_gap(gb, size); len = size; do { gb_putc(gb, *str++); } while (--size); return len; } /********************************************************/ /** * Left size of the data in the gap buffer * * @param gb * The gapbuf structure pointer. * @return * Number of bytes in the left size of gap buffer */ static inline uint32_t gb_left_data_size(struct gapbuf *gb) { return gb->gap - gb->buf; } /** * Right size of the data in the gap buffer * * @param gb * The gapbuf structure pointer. * @return * Number of bytes in the right size of gap buffer */ static inline uint32_t gb_right_data_size(struct gapbuf *gb) { if (gb_eof(gb)) return 0; return gb->ebuf - gb->egap; } /** * Move point right one byte * * @param gb * The gapbuf structure pointer. * @return * N/A */ static inline void gb_move_right(struct gapbuf *gb) { if (gb->point == gb->gap) gb->point = gb->egap; gb->point = ((gb->point + 1) > gb->ebuf) ? gb->ebuf : (gb->point + 1); } /** * Move point left one byte * * @param gb * The gapbuf structure pointer. * @return * N/A */ static inline void gb_move_left(struct gapbuf *gb) { if (gb->point == gb->egap) gb->point = gb->gap; gb->point = ((gb->point - 1) < gb->buf) ? gb->buf : (gb->point - 1); } #ifdef __cplusplus } #endif #endif /* _CLI_GAPBUF_H_ */ ================================================ FILE: lib/cli/cli_help.c ================================================ /*- * Copyright(c) <2016-2026> Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include "cli.h" #include "cli_input.h" int cli_help_add(const char *group, struct cli_map *map, const char **help_data) { struct help_node *node; if (!group) return -1; node = calloc(1, sizeof(struct help_node)); if (!node) return -1; node->map = map; node->help_data = help_data; snprintf(node->group, sizeof(node->group), "%s", group); /* If a map is provided, register all command tokens it references. */ if (map) { if (cli_register_cmd_maps(map) < 0) { free(node); return -1; } } TAILQ_INSERT_TAIL(&this_cli->help_nodes, node, next); return 0; } static int _show_help_lines(const char **h, int allow_pause) { int j; char key; for (j = 0; h[j] != NULL; j++) { if (strcmp(h[j], CLI_HELP_PAUSE)) { cli_printf("%s\n", h[j]); continue; } if (allow_pause) { key = cli_pause("\n Return to Continue or ESC:", NULL); if ((key == vt100_escape) || (key == 'q') || (key == 'Q')) return -1; } } return 0; } static void _cli_help_title(const char *msg) { scrn_pos(1, 1); scrn_cls(); if (msg) scrn_cprintf(1, -1, "%s\n", msg); } int cli_help_show_all(const char *msg) { struct help_node *n; _cli_help_title(msg); TAILQ_FOREACH (n, &this_cli->help_nodes, next) { if (_show_help_lines(n->help_data, 1)) return -1; _cli_help_title(msg); } return 0; } void cli_help_foreach(void (*func)(void *arg, const char **h), void *arg) { struct help_node *n; TAILQ_FOREACH (n, &this_cli->help_nodes, next) { func(arg, n->help_data); } } struct help_node * cli_help_find_group(const char *group) { struct help_node *n; TAILQ_FOREACH (n, &this_cli->help_nodes, next) { if (!strcmp(group, n->group)) return n; } return NULL; } int cli_help_show_group(const char *group) { struct help_node *n; n = cli_help_find_group(group); if (!n) return -1; return _show_help_lines(n->help_data, 0); } int cli_cmd_error(const char *msg, const char *group, int argc, char **argv) { int n; if (group) cli_help_show_group(group); if (msg) cli_printf("%s:\n", msg); cli_printf(" Invalid line: <"); for (n = 0; n < argc; n++) cli_printf("%s ", argv[n]); cli_printf(">\n"); return -1; } ================================================ FILE: lib/cli/cli_help.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #ifndef _CLI_HELP_H_ #define _CLI_HELP_H_ #include #include #include #include #include #include #include #include /** * @file * CLI help subsystem for grouped command documentation. * * Manages named help groups, each consisting of a cli_map table and an array * of descriptive strings. Groups are registered via cli_help_add() and can be * displayed individually or all at once. */ #ifdef __cplusplus extern "C" { #endif #define CLI_HELP_PAUSE "<>" #define CLI_HELP_NAME_LEN 32 struct help_node { TAILQ_ENTRY(help_node) next; /**< link list of help nodes */ char group[CLI_HELP_NAME_LEN]; struct cli_map *map; const char **help_data; }; /** * Find the help group section defined by the group string. * * @note Uses thread variable this_cli. * * @param group * The group string name to find. * @return * NULL if not found or pointer to struct cli_info. */ struct help_node *cli_help_find_group(const char *group); /** * Show the map table entries * * @param msg * Pointer to a message to print first. * @return * 0 on success or -1 on error */ int cli_help_show_all(const char *msg); /** * Show the help message for the user. * * @note Uses thread variable this_cli. * * @param data * Pointer to the cli_info structure. */ int cli_help_show_group(const char *group); /** * Register a named help group with an optional command map. * * Adds the group to the per-lcore help list and registers all first-token * command names from @p map so that auto-complete can find them. * * @param group * Name string for this help group (e.g. "Range", "Set"). * @param map * Pointer to the cli_map table for this group, or NULL if none. * @param hd * NULL-terminated array of help strings for this group. * @return * 0 on success or -1 on error */ int cli_help_add(const char *group, struct cli_map *map, const char **hd); /** * Find if the last item is a help request. * * @param argc * Number of args in the argv list. * @param argv * List of strings to parser * @return * 1 if true or 0 if false */ static inline int is_help(int argc, char **argv) { if (argc == 0) return 0; return !strcmp("-?", argv[argc - 1]) || !strcmp("?", argv[argc - 1]); } /** * Iterate over the help messages calling a given function. * * @param func * A function to call for all help lines. * @param arg * Argument pointer for function call. * @return * N/A */ void cli_help_foreach(void (*func)(void *arg, const char **h), void *arg); /** * Print an error message and show the help group for a failed command. * * @param msg * Short error description to print. * @param group * Help group name whose entries will be displayed after the error. * @param argc * Number of arguments in @p argv (for context in the error message). * @param argv * Argument strings from the failed command invocation. * @return * Always returns -1 (for use as a one-liner return value). */ int cli_cmd_error(const char *msg, const char *group, int argc, char **argv); #ifdef __cplusplus } #endif #endif /* _CLI_HELP_H_ */ ================================================ FILE: lib/cli/cli_history.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #include "cli.h" struct cli_hist * cli_hist_alloc(void) { struct cli *cli = this_cli; struct cli_hist *hist = NULL; if (!cli) return hist; if (!CIRCLEQ_EMPTY(&cli->free_hist)) { hist = (struct cli_hist *)CIRCLEQ_FIRST(&cli->free_hist); CIRCLEQ_REMOVE(&cli->free_hist, hist, next); } return hist; } void cli_hist_free(struct cli_hist *hist) { struct cli *cli = this_cli; if (!cli || !hist) return; free(hist->line); hist->line = NULL; CIRCLEQ_INSERT_TAIL(&cli->free_hist, hist, next); } void cli_history_add(char *line) { struct cli *cli = this_cli; struct cli_hist *h; if (!cli || !cli->hist_mem || !line) return; /* Do not allow duplicate lines compared to the last line */ if (!CIRCLEQ_EMPTY(&cli->hd_hist)) { h = CIRCLEQ_LAST(&cli->hd_hist); if (strcmp(h->line, line) == 0) return; } h = cli_hist_alloc(); if (!h) { h = CIRCLEQ_FIRST(&cli->hd_hist); CIRCLEQ_REMOVE(&cli->hd_hist, h, next); } free(h->line); h->line = strdup(line); CIRCLEQ_INSERT_TAIL(&cli->hd_hist, h, next); } void cli_history_del(void) { struct cli *cli = this_cli; struct cli_hist *h; if (!cli || !cli->hist_mem) return; if (!CIRCLEQ_EMPTY(&cli->hd_hist)) { h = CIRCLEQ_LAST(&cli->hd_hist); CIRCLEQ_REMOVE(&cli->hd_hist, h, next); } } char * cli_history_line(int lineno) { struct cli *cli = this_cli; struct cli_hist *h; int i = 0; if (!cli || !cli->hist_mem) return NULL; if (!CIRCLEQ_EMPTY(&cli->hd_hist)) { CIRCLEQ_FOREACH (h, &cli->hd_hist, next) { if (i++ == lineno) return h->line; } } return NULL; } char * cli_history_prev(void) { struct cli *cli = this_cli; if (!cli || !cli->hist_mem) return NULL; if (!CIRCLEQ_EMPTY(&cli->hd_hist)) { struct cli_hist *hist; if (!cli->curr_hist) { cli->curr_hist = CIRCLEQ_LAST(&cli->hd_hist); return cli->curr_hist->line; } if (cli->curr_hist == CIRCLEQ_FIRST(&cli->hd_hist)) return cli->curr_hist->line; hist = CIRCLEQ_LOOP_PREV(&cli->hd_hist, cli->curr_hist, next); cli->curr_hist = hist; return hist->line; } return NULL; } char * cli_history_next(void) { struct cli *cli = this_cli; if (!cli || !cli->hist_mem) return NULL; if (!CIRCLEQ_EMPTY(&cli->hd_hist)) { struct cli_hist *hist; if (!cli->curr_hist) return NULL; if (cli->curr_hist == CIRCLEQ_LAST(&cli->hd_hist)) return (char *)(uintptr_t)""; hist = CIRCLEQ_LOOP_NEXT(&cli->hd_hist, cli->curr_hist, next); cli->curr_hist = hist; return hist->line; } return NULL; } void cli_history_clear(void) { struct cli *cli = this_cli; struct cli_hist *h; if (!cli || !cli->hist_mem) return; while (!CIRCLEQ_EMPTY(&cli->hd_hist)) { h = CIRCLEQ_FIRST(&cli->hd_hist); CIRCLEQ_REMOVE(&cli->hd_hist, h, next); cli_hist_free(h); } } void cli_history_delete(void) { struct cli *cli = this_cli; if (cli) { cli_history_clear(); CIRCLEQ_INIT(&cli->hd_hist); cli->hist_mem = NULL; cli->nb_hist = 0; } } int cli_set_history(uint32_t nb_hist) { struct cli *cli = this_cli; size_t size; if (!cli) return -1; if (nb_hist == 0) { cli_history_delete(); return 0; } if (cli->hist_mem && (nb_hist == cli->nb_hist)) return 0; if (cli->hist_mem) cli_history_delete(); cli->nb_hist = nb_hist; size = nb_hist * sizeof(struct cli_hist); cli->hist_mem = calloc(1, size); if (cli->hist_mem) { uint32_t i; struct cli_hist *hist = cli->hist_mem; /* Setup the history support is number of lines given */ for (i = 0; i < nb_hist; i++, hist++) CIRCLEQ_INSERT_TAIL(&cli->free_hist, hist, next); return 0; } return -1; } void cli_history_reset(void) { this_cli->curr_hist = CIRCLEQ_FIRST(&this_cli->hd_hist); } void cli_history_dump(void) { struct cli *cli = this_cli; struct cli_hist *h; int i = 0; if (!cli || !cli->hist_mem || CIRCLEQ_EMPTY(&cli->hd_hist)) return; CIRCLEQ_FOREACH (h, &cli->hd_hist, next) { cli_printf("%4d: %s\n", i++, h->line); } } ================================================ FILE: lib/cli/cli_history.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #ifndef _CLI_HISTORY_H_ #define _CLI_HISTORY_H_ /** * @file * RTE Command line history * */ #include "cli.h" #include #ifdef __cplusplus extern "C" { #endif enum { CLI_DEFAULT_HISTORY = -1, /**< Use the default history line count */ CLI_NO_HISTORY = 0, /**< Disable history entirely */ }; struct cli_hist { CIRCLEQ_ENTRY(cli_hist) next; /**< Circular-queue linkage for history list */ char *line; /**< strdup'd copy of the saved command line */ }; struct cli; /** * Create and allocate a history structure * * @note Uses the thread variable this_cli * * @return * pointer to history structure or NULL on error */ struct cli_hist *cli_hist_alloc(void); /** * Free a CLI history structure and other memory * * @note Uses the thread variable this_cli * * @param hist * Pointer to the history structure * @return * N/A */ void cli_hist_free(struct cli_hist *hist); /** * Add line to history at the end * * @note Uses the thread variable this_cli * * @param line * Pointer to string to add to the history list * @return * N/A */ void cli_history_add(char *line); /** * Delete a history entry * * @note Uses the thread variable this_cli * * @return * N/A */ void cli_history_del(void); /** * Return the history command from line number * * @note Uses the thread variable this_cli * * @param lineno * The line number of the command to return. * @return * Pointer to line or NULL on error */ char *cli_history_line(int lineno); /** * Clear all of the history lines from the list * * @note Uses the thread variable this_cli * * @return * N/A */ void cli_history_clear(void); /** * Delete the history lines and structure * * @note Uses the thread variable this_cli * * @return * N/A */ void cli_history_delete(void); /** * Set the number of lines max in the history list * * @param cli * Pointer to the allocated cli structure * @param nb_hist * Number of lines max in the history list * @return * 0 is ok, -1 is error */ int cli_set_history(uint32_t nb_hist); /** * Return the previous history line * * @note Uses the thread variable this_cli * * @return * pointer to the pervious history line wrap if needed */ char *cli_history_prev(void); /** * Return the next (more recent) history line. * * @note Uses the thread variable this_cli. Wraps around when the end is * reached. * * @return * Pointer to the next history line, or NULL if history is empty. */ char *cli_history_next(void); /** * Reset the current history pointer to the last entry. * * @note Uses the thread variable this_cli */ void cli_history_reset(void); /** * Print all history lines to the CLI console. * * @note Uses the thread variable this_cli. */ void cli_history_dump(void); #ifdef __cplusplus } #endif #endif /* _CLI_HISTORY_H_ */ ================================================ FILE: lib/cli/cli_input.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /** * @file * CLI input handling. * * Wraps VT100/screen I/O and provides the character-by-character input loop * used by the interactive CLI. */ #include #include #include #include #include #include #include #include #include "cli.h" #include "cli_input.h" int cli_yield_io(void) { if (!this_cli) return 1; return this_cli->flags & CLI_YIELD_IO; } /* The CLI write routine, using write() call */ int cli_write(const void *msg, int len) { return scrn_write(msg, len); } int cli_read(char *buf, int len) { return scrn_read(buf, len); } static void handle_input_display(char c) { /* Only allow printable characters */ if ((c >= ' ') && (c <= '~')) { /* Output the character typed */ cli_write(&c, 1); /* Add the character to the buffer */ gb_insert(this_cli->gb, c); if (!gb_point_at_end(this_cli->gb)) cli_set_flag(0); else if (!gb_point_at_start(this_cli->gb)) cli_set_flag(0); } cli_display_line(); } /* Process the input for the CLI from the user */ void cli_input(char *str, int n) { RTE_ASSERT(this_cli->gb != NULL); RTE_ASSERT(str != NULL); while (n--) { char c = *str++; int ret = vt100_parse_input(this_cli->vt, c); if (ret > 0) { /* Found a vt100 key sequence */ vt100_do_cmd(ret); handle_input_display(0); } else if (ret == VT100_DONE) handle_input_display(c); } } /* Poll the I/O routine for characters */ int cli_poll(char *c) { struct pollfd fds; fds.fd = fileno(this_scrn->fd_in); fds.events = POLLIN; fds.revents = 0; if (poll(&fds, 1, 0)) { if ((fds.revents & (POLLERR | POLLNVAL)) == 0) { if ((fds.revents & POLLHUP)) this_cli->quit_flag = 1; else if ((fds.revents & POLLIN)) { int n = read(fds.fd, c, 1); if (n > 0) return 1; } } else cli_quit(); } return 0; } /* Display a prompt and wait for a key press */ char cli_pause(const char *msg, const char *keys) { char prompt[128], c; prompt[0] = '\0'; if (msg) { snprintf(prompt, sizeof(prompt), "%s: ", msg); cli_printf("%s", prompt); } if (!keys) keys = " qQ\n\r" ESC; do { if (cli_poll(&c)) if (strchr(keys, c)) { /* clear the line of the prompt */ cli_printf("\r%*s\r", (int)strlen(prompt), " "); return c; } } while (this_cli->quit_flag == 0); return '\0'; } ================================================ FILE: lib/cli/cli_input.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #ifndef _CLI_INPUT_H_ #define _CLI_INPUT_H_ /** * @file * RTE Command line input interface * */ #ifdef __cplusplus extern "C" { #endif /** * Poll for a input character * * @param c * Pointer to character address. * @return * return non-zero if character was read. */ int cli_poll(char *c); /** * The CLI write routine, using write() call * * @note Uses thread variable this_cli. * * @param msg * The string to be written * @param len * Number of bytes to write or if -1 then strlen(msg) is used. * @return * Number of bytes written. */ int cli_write(const void *msg, int len); /** * The routine to read characters from the user input. * * @param buf * The string buffer to put the read characters. * @param len * The length of the array to put the input. * @return * The number of bytes read. */ int cli_read(char *buf, int len); /** * Query and return the current terminal cursor position (row, column). * * Sends an ANSI cursor position report request (ESC[6n) and reads back the * ESC[{ROW};{COL}R response. Retries until a valid response is received. * * @note Uses thread variable this_cli. * * @param row * Output: 1-based row number of the cursor. * @param col * Output: 1-based column number of the cursor. */ static inline void cli_get_cursor(int *row, int *col) { char buf[32], *p, ch; int r, c, l; again: scrn_cpos(); memset(buf, 0, sizeof(buf)); p = buf; l = sizeof(buf) - 1; do { cli_read(&ch, 1); if (ch == 'R') break; if (ch == '\0') continue; *p++ = ch; } while (l--); p = index(buf, ';'); if (!p) goto again; r = atoi(&buf[2]); c = atoi(++p); if (!r || !c) goto again; *row = r; *col = c; } /** * Move the vt100 cursor to the left one character * * @note Uses thread variable this_cli. * * @return * N/A */ static inline void cli_cursor_left(void) { cli_write(vt100_left_arr, -1); } /** * Move the vt100 cursor to the right one character * * @note Uses thread variable this_cli. * * @return * N/A */ static inline void cli_cursor_right(void) { cli_write(vt100_right_arr, -1); } /** * Save the vt100 cursor location * * @note Uses thread variable this_cli. * * @return * N/A */ static inline void cli_save_cursor(void) { cli_write(vt100_save_cursor, -1); } /** * Restore the cursor to the saved location on the console * * @note Uses thread variable this_cli. * * @return * N/A */ static inline void cli_restore_cursor(void) { cli_write(vt100_restore_cursor, -1); } /** * Print out the left side of the input in the Gap Buffer. * * @note Uses thread variable this_cli. * * @return * N/A */ static inline void cli_display_left(void) { if (gb_left_data_size(this_cli->gb)) cli_write(gb_start_of_buf(this_cli->gb), gb_left_data_size(this_cli->gb)); } /** * Print out the right side of the input in the Gap Buffer. * * @note Uses thread variable this_cli. * * @return * N/A */ static inline void cli_display_right(void) { if (gb_right_data_size(this_cli->gb)) cli_write(gb_end_of_gap(this_cli->gb), gb_right_data_size(this_cli->gb)); } /** * Clear the console screen * * @note Uses thread variable this_cli. * * @return * N/A */ static inline void cli_clear_screen(void) { cli_write(vt100_clear_screen, -1); } /** * clear from cursor to end of line * * @note Uses thread variable this_cli. * * @return * N/A */ static inline void cli_clear_to_eol(void) { cli_write(vt100_clear_right, -1); } /** * Clear the current line or the line given * * @note Uses thread variable this_cli. * * @param lineno * if lineno is -1 then clear the current line else the lineno given. * @return * N/A */ static inline void cli_clear_line(int lineno) { if (lineno > 0) cli_printf(vt100_pos_cursor, lineno, 0); else cli_write("\r", 1); cli_write(vt100_clear_line, -1); } /** * Move the cursor up by the number of lines given * * @note Uses thread variable this_cli. * * @param lineno * Number of lines to move the cursor * @return * N/A */ static inline void cli_move_cursor_up(int lineno) { while (lineno--) cli_printf(vt100_up_arr); } /** * Redraw the CLI prompt at the start of the current line. * * Moves to column 0, invokes the registered prompt function, then clears * to the end of the line. * * @note Uses thread variable this_cli. * * @param t * Continuation flag passed to the prompt function (0 = normal, 1 = continuation). */ static inline void cli_display_prompt(int t) { cli_write("\r", 1); this_cli->plen = this_cli->prompt(t); cli_clear_to_eol(); } /** * Redisplay the current input line with scrolling support. * * Renders the portion of the gap-buffer content visible within the terminal * window width, repositions the cursor to match the logical point offset, * and honours any pending display flags (CLEAR_TO_EOL, DISPLAY_PROMPT, etc.). * * @note Uses thread variable this_cli. */ static inline void cli_display_line(void) { struct gapbuf *gb = this_cli->gb; char buf[gb_data_size(gb) + 16]; int point = gb_point_offset(gb); int len = gb_copy_to_buf(gb, buf, gb_data_size(gb)); int window = (this_scrn->ncols - this_cli->plen) - 1; int wstart, wend; if (cli_tst_flag(DELETE_CHAR)) { cli_clr_flag(DELETE_CHAR); cli_write(" \b", 2); cli_set_flag(CLEAR_TO_EOL); } if (cli_tst_flag(CLEAR_LINE)) { cli_clr_flag(CLEAR_LINE); scrn_bol(); cli_clear_to_eol(); cli_set_flag(DISPLAY_PROMPT); } if (cli_tst_flag(CLEAR_TO_EOL)) { cli_clr_flag(CLEAR_TO_EOL); cli_clear_to_eol(); } if (cli_tst_flag(DISPLAY_PROMPT)) { cli_clr_flag(DISPLAY_PROMPT | PROMPT_CONTINUE); cli_display_prompt(0); } if (point < window) { wstart = 0; if (len < window) wend = point + (len - point); else wend = point + (window - point); } else { wstart = point - window; wend = wstart + window; } scrn_bol(); scrn_cnright(this_cli->plen); cli_write(&buf[wstart], wend - wstart); cli_clear_to_eol(); scrn_bol(); scrn_cnright(this_cli->plen + point); } /** * Print out the complete line in the Gap Buffer. * * @note Uses thread variable this_cli. * * @return * N/A */ static inline void cli_redisplay_line(void) { uint32_t i; this_cli->flags |= DISPLAY_PROMPT; cli_display_line(); gb_move_gap_to_point(this_cli->gb); for (i = 0; i < (gb_data_size(this_cli->gb) - gb_point_offset(this_cli->gb)); i++) cli_cursor_left(); } /** * Add a input text string the cli input parser * * @note Uses thread variable this_cli. * * @param str * Pointer to string to insert * @param n * Number of bytes in string * @return * N/A */ void cli_input(char *str, int n); /** * Set the CLI prompt function pointer * * @param prompt * Function pointer to display the prompt * @return * Return the old prompt function pointer or NULL if one does not exist */ cli_prompt_t cli_set_prompt(cli_prompt_t prompt); /** * Set the I/O file descriptors * * @note Uses thread variable this_cli. * * @param in * File descriptor for input * @param out * File descriptor for output * @return * N/A */ void cli_set_io(FILE *in, FILE *out); /** * Set the I/O to use stdin/stdout * * @note Uses thread variable this_cli. * * @return * 0 on success or non-0 on error */ int cli_stdin_setup(void); /** * Restore the stdin/stdout tty params from setup * * @note Uses thread variable this_cli. * * @return * N/A */ void cli_stdin_restore(void); /** * Pause and wait for input character * * @note Uses thread variable this_cli. * * @param keys * List of keys to force return, if NULL defaults to ESC and q/Q * @return * character that terminated the pause or zero. */ char cli_pause(const char *msg, const char *keys); /** * return true if calling yield should are enabled. * * @note Uses thread variable this_cli. * * @return * non-zero if true else 0 */ int cli_yield_io(void); #ifdef __cplusplus } #endif #endif /* _CLI_INPUT_H_ */ ================================================ FILE: lib/cli/cli_lib.rst ================================================ .. BSD LICENSE Copyright(c) <2016-2026>, Intel Corporation. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Intel Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. CLI library guide ================= CLI stands for "Command Line Interface". This chapter describes the CLI library which is a part of the Data Plane Development Kit (DPDK). The CLI is a workalike replacement for cmdline library in DPDK and has a simpler interface and programming model plus it is dynamic. The primary goal of CLI is to allow the developer to create commands quickly and with very little compile or runtime configuration. Using standard Unix* like constructs which are very familar to the developer. Allowing the developer to construct a set of commands for development or deployment of the application. The CLI design uses a directory like design instead of a single level command line interface. Allowing the developer to use a directory style solution to controlling a DPDK application. The directory style design is nothing new, but it does have some advantages over a single level command structure. One advantage allows the directory path for the command to be part of the information used in executing the command. The next advantage is creating directories to make a hierarchy of commands, plus allowing whole directroy trees to dynamicly come and go as required by the developer. Some of the advantages are: * CLI has no global variable other then the single thread variable called *this_cli* which can only be accessed from the thread which created the CLI instance. * CLI supports commands, files, aliases, directories. - The alias command is just a string using a simple substitution support for other commands similar to the bash shell like alias commands. - Files can be static or dynamic information, can be changed on the fly and saved for later. The file is backed with a simple function callback to allow the developer to update the content or not. * Added support for color and cursor movement APIs similar to Pktgen if needed by the developer. * It is a work alike replacement for cmdline library. Both cmdline and CLI can be used in the same application if care is taken. * Uses a simple fake like directory layout for command and files. Allowing for command hierarchy as path to the command can allow for specific targets to be identified without having to state it on the command line. * Has auto-complete for commands, similar to Unix/Linux autocomplete and provides support for command option help as well. * Callback functions for commands are simply just argc/argv like functions. - The CLI does not convert arguments for the user, it is up to the developer to decode the argv[] values. - Most of the arguments converted in the current cmdline are difficult to use or not required as the developer just picks string type and does the conversion himself. * Dynamically be able to add and remove commands, directories, files and aliases, does not need to be statically compiled into the application. * No weird structures in the code and reduces the line count for testpmd from 12K to 4.5K lines. I convert testpmd to have both CMDLINE and CLI with a command line option. * Two methods to parse command lines, first is the standard argc/argv method in the function. - The second method is to use a map of strings with simple printf like formatting to detect which command line the user typed. - An ID value it returned to the used to indicate which mapping string was found to make the command line to be used in a switch statement. * Environment variable support using the **env** command or using an API. * Central help support if needed (optional). Overview -------- The CLI library is a simple set of APIs which allow the developer to quickly create a set of commands using a simple programming interface already familar to the developer. One of the big advantages of CLI over Cmdline is it is dynamic, which means nodes or items can be added and removed on the fly. Which allows adding new directories, file or commands as needed or removing these items at runtime. The CLI has no global modifiable variables except for the one global pointer which is a thread based variable. Allowing the developer to have multiple CLI instances running at the same time on different threads if needed. Another big advantage is the calling of the backend function to support a command is very familar to developers as it is basically just a argc/argv style command and the developer gets the complete command line. The function as access to the global thread variable called **this_cli** pointing to the struct cli variable. .. code--block:: c /* Show command returns 0 on OK and -1 on error */ int show_cmd(int argc, char **argv); Mapping commands ---------------- One other big advantage is the use of MAP structures, to help identify commands quickly plus allowing the developer to define new versions of commands and be able to identify these new versions using a simple identifier value. The format of the struct cli_map is: .. code-block:: c struct cli_map show_map[] = { /* Index value, Mapping string */ { 10, "show" }, { 20, "show %s" }, { 30, "show %P stats" }, { 40, "show %P %|link|errors|missed stats" }, { 0, NULL} } The map is just an array of struct cli_map entries with a unique index value and mapping string. The index value can be any value the developer wants. As the index value is used to identify the given map string. The map string is a special formatted string similar to sprintf(), but the format values for % is different. Please look at the cli_mapping() function docs for more information. The %s is for any string and %P is used to a portlist format e.g. 1-3,5-7,9 as used for DPDK command line notation. The above array is parsed to match the command line from the user. The first map string that matches the user input will be returned from the call to cli_mapping() function. Constant values are required in the command as in index 30 'stats'. The index 40 is using a variable fixed set of strings option, which means one of these fixed strings must match in that position. Another advantage of CLI is how simple it is to add new directroies, files and commands for user development. To add a command a developer needs to add an entry to the cli_tree structure and create a function using the above prototype format. .. code-block:: c struct cli_tree my_tree[] = { c_dir("/bin"), c_cmd("hello", hello_cmd, "simple hello world command"), c_cmd("show", show_cmd, "Show system information"), c_end() }; The cli_tree structure is made with unions and the c_dir(), c_cmd() and c_end() help initialize the structure easily for the developer. The help and show commands above use the simple argc/argv prototype above. Only two things are required to create a command a cli_tree entry and a function to call. Using the cli_map and other structures are optional to make adding simple commands quick and easy. The call the cli_create() command or one of its helper functions cli_create_XYZ(). If have a function per command then using the mapping structure is optional, unless you want to have CLI parse and map commands to the exact entries. If cli_map is not used then the developer needs to decode the argc/argv to determine the command requests. The argc/argv is exactly like the standard usage in a Unix* system, which allows for using getopt() and other standard functions. The Cmdline structures and text conversions were defined at compile time in most cases, but in CLI the command routine is passed the argc/argv information to convert the strings as needed. The cli variable being a thread Local Storage (TLS) all user routines can access **this_cli** to gain access to the CLI structure if required at all. Environment variables --------------------- The user can also set environment variables using the **env** command. These variables are also parsed in the command line as direct substitutions. Another special file is a string file, which can be used as an environment variable. When the variable is asked for the variable asks a function to return the string. The value of the string normally a system value or a generated value. These types of environment variables can not be set from the command line as a function pointer needs to be given. The c_str() macro helps in setting up these environment variables via the cli_tree structure. The special file backed environment variable can be deleted, but can not be restored without a reboot or some other command puting that variable back into the environment. Environment variables are denoted by a $(foo) like syntax and are expanded at the time of execution each time the command line is executed. Which means history lines with environment variables will be expanded again. Simple Files ------------ The CLI system also has support for simple files along with alias like commands. These simple files are backed by a function call and the other commands can read these files to get constant data or generated data depending on how the backend function works. Alias commands -------------- The alias commands are fixed strings which are executed instead of a function provided by the developer. If the user has more arguments these are appended to the alias string and processed as if typed on the command line. Also the environment variables are expanded at execution time. .. note:: The CLI library was designed to be used in production code and the Cmdline was not validated to the same standard as other DPDK libraries. The goal is to provide a production CLI design. The CLI library supports some of the features of the Cmdline library such as, completion, cut/paste and some other special bindings that make configuration and debug faster and easier. The CLI desin uses some very simple VT100 control strings for displaying data and accepting input from the user. Some of the control strings are used to clear the screen or line and position the cursor on a VT100 compatible terminal. The CLI screen code also supports basic color and many other VT100 commands. The example application also shows how the CLI application can be extended to handle a list of commands and user input. The example presents a simple command prompt **DPDK-cli:/>** similar to a Unix* shell command along with a directory like file system. Some of the **default** commands contained under /sbin directory are: * **ls**: list the current or provided directory files/commands. * **cd**: Change directory command. * **pwd**: print out the current working directory. * **history**: List the current command line history if enabled. * **more**: A simple command to page contents of files. * **help**: display a the help screen. * **quit**: exit the CLI application, also **Ctrl-x** will exit as well. * **mkdir**: add a directory to the current directory. * **delay**: wait for a given number of microseconds. * **sleep**: wait for a given number of seconds. * **rm**: remove a directory, file or command. Removing a file will delete the data. * **cls**: clear the screen and redisplay the prompt. * **version**: Display the current DPDK version being used. * **path**: display the current search path for executable commands. * **cmap**: Display the current system core and socket information. * **hugepages**: Display the current hugepage information. * **sizes**: a collection system structure and buffer sizes for debugging. * **copyright**: a file containing DPDK copyright information. * **env**: a command show/set/modify the environment variables. * **ll**: an alias command to display long ls listing **ls -l** * **h**: alias command for **history** * **hello**: a simple Hello World! command. * **show**: has a number of commands using the map feature. Under the /data directory is: * **pci**: a simple example file for displaying the **lspci** command in CLI. .. note:: To terminate the application, use **Ctrl-x** or the command **quit**. Auto completion --------------- CLI does support auto completion at the file or directory level, meaning the arguments to commands are not expanded as was done in Cmdline code. The CLI auto completion works similar to the standard Unix* system by expanding commands and directory paths. In normal Unix* like commands the user needs to execute the command asking for help information. Special command features ------------------------ Using the '!' followed by a number from the history list of commands you can execute that command again. Or using the UP/Down arrows the user can quickly find and execute or modify a previous command in history. The user can also execute host level commands if enabled using the '@' prefix to a command line e.g. @ls or @lspci or ... line is passed to popen or system function to be executed and the output displayed on the console if any output. Compiling the Application ------------------------- #. Go to example directory: .. code-block:: c export RTE_SDK=/path/to/rte_sdk cd ${RTE_SDK}/examples/cli #. Set the target (a default target is used if not specified). For example: .. code-block:: console export RTE_TARGET=x86_64-native-linux-gcc or export RTE_TARGET=x86_64-native-linuxapp-gcc Refer to the *DPDK Getting Started Guide* for possible RTE_TARGET values. #. Build the application: .. code-block:: console make Running the Application ----------------------- To run the application in linux environment, issue the following command: .. code-block:: console $ ./build/cli .. note:: The example cli application does not require to be run as superuser as it does not startup DPDK by calling rte_eal_init() routine. Which means it also does not use DPDK features except for a few routines not requiring EAL initialization. Refer to the *DPDK Getting Started Guide* for general information on running applications and the Environment Abstraction Layer (EAL) options. Explanation ----------- The following sections provide some explanation of the code. EAL Initialization and cmdline Start ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The first task is the initialization of the Environment Abstraction Layer (EAL), if required for the application. .. code-block:: c int main(int argc, char **argv) { if (cli_create_with_tree(init_tree) ==0) { cli_start(NULL, 0); /* NULL is some init message done only once */ /* 0 means do not use color themes */ cli_destroy(); } The cli_start() function returns when the user types **Ctrl-x** or uses the quit command in this case, the application exits. The cli_create() call takes four arguments and each has a default value if not provided. The API used here is the cli_create_with_tree(), which uses defaults for three of the arguments. .. code-block:: c /** * Create the CLI engine * * @param prompt_func * Function pointer to call for displaying the prompt. * @param tree_func * The user supplied function to init the tree or can be NULL. If NULL then * a default tree is initialized with basic commands. * @param nb_entries * Total number of commands, files, aliases and directories. If 0 then use * the default number of nodes. If -1 then unlimited number of nodes. * @param nb_hist * The number of lines to keep in history. If zero then turn off history. * If the value is CLI_DEFAULT_HISTORY use CLI_DEFAULT_HIST_LINES * @return * 0 on success or -1 */ int cli_create(cli_prompt_t prompt_func, cli_tree_t tree_func, int nb_entries, uint32_t nb_hist); The cli_create_with_tree() has only one argument which is the structure to use in order to setup the initial directory structure. Also the wrapper function int cli_create_with_defaults(void) can be used as well. Consult the cli.h header file for the default values. Also the alias node is a special alias file to allow for aliasing a command to another command. The tree init routine is defined like: .. code-block:: c static struct cli_tree my_tree[] = { c_dir("/data"), c_file("pci", pci_file, "display lspci information"), c_dir("/bin"), c_cmd("hello", hello_cmd, "Hello-World!!"), c_alias("h", "history", "display history commands"), c_alias("ll", "ls -l", "long directory listing alias"), c_end() }; static int init_tree(void) { /* * Root is created already and using system default cmds and dirs, the * developer is not required to use the system default cmds/dirs. */ if (cli_default_tree_init()) return -1; /* Using NULL here to start at root directory */ if (cli_add_tree(NULL, my_tree)) return -1; cli_help_add("Show", show_map, show_help); return cli_add_bin_path("/bin"); } The above structure is used to create the tree structure at initialization time. The struct cli_tree or cli_tree_t typedef can be used to setup a new directory tree or agument the default tree. The elements are using a set of macros c_dir, c_file, c_cmd, c_alias and c_end. These macros help fill out the cli_tree_t structure for the given type of item. The developer can create his own tree structure with any commands that are needed and/or call the cli_default_tree_init() routine to get the default structure of commands. If the developer does not wish to call the default CLI routine, then he must call the cli_create_root() function first before adding other nodes. Other nodes can be added and removed at anytime. CLI Map command support ~~~~~~~~~~~~~~~~~~~~~~~ The CLI command has two types of support to handle arguments normal argc/argv and the map system. As shown above the developer creates a directory tree and attaches a function to a command. The function takes the argc/argv as arguments and the developer can just parse the arguments to decode the command arguments. Sometimes you have multiple commands or different versions of a command being handled by a single routine, this is were the map support comes into play. The map support defines a set of struct cli_map map[]; to help detect the correct command from the user. In the list of cli_map structures a single structure contains two items a developer defined index value and a command strings. The index value is used on the function to identify the specific type of command found in the list. The string is a special printf like string to help identify the command typed by the user. One of the first things todo in the command routine is to call the cli_mapping() function passing in the CLI pointer and the argc/argv values.The two method can be used at the same time. The cli_mapping() command matches up the special format string with the values in the argc/argv array and returns the developer supplied index value or really the pointer the struct cli_map instance. Now the developer can use the cli_map.index value in a switch() statement to locate the command the user typed or if not found a return of -1. Example: .. code-block:: c static int hello_cmd(int argc, char **argv) { int i, opt; optind = 1; while((opt = getopt(argc, argv, "?")) != -1) { switch(opt) { case '?': cli_usage(); return 0; default: break; } } cli_printf("Hello command said: Hello World!! "); for(i = 1; i < argc; i++) cli_printf("%s ", argv[i]); cli_printf("\n"); return 0; } static int pci_file(struct cli_node *node, char *buff, int len, uint32_t opt) { if (is_file_open(opt)) { FILE *f; if (node->file_data && (node->fflags & CLI_FREE_DATA)) free(node->file_data); node->file_data = calloc(1, 32 * 1024); if (!node->file_data) return -1; node->file_size = 32 * 1024; node->fflags = CLI_DATA_RDONLY | CLI_FREE_DATA; f = popen("lspci", "r"); if (!f) return -1; node->file_size = fread(node->file_data, 1, node->file_size, f); pclose(f); return 0; } return cli_file_handler(node, buff, len, opt); } static struct cli_map show_map[] = { { 10, "show %P" }, { 20, "show %P mac %m" }, { 30, "show %P vlan %d mac %m" }, { 40, "show %P %|vlan|mac" }, { -1, NULL } }; static const char *show_help[] = { "show ", "show mac ", "show vlan mac ", "show [vlan|mac]", CLI_HELP_PAUSE, NULL }; static int show_cmd(int argc, char **argv) { struct cli_map *m; uint32_t portlist; struct rte_ether_addr mac; m = cli_mapping(Show_info.map, argc, argv); if (!m) return -1; switch(m->index) { case 10: portlist_parse(argv[1], pktgen.nb_ports, &portlist); cli_printf(" Show Portlist: %08x\n", portlist); break; case 20: portlist_parse(argv[1], pktgen.nb_ports, &portlist); pg_ether_aton(argv[3], &mac); cli_printf(" Show Portlist: %08x, MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", portlist, mac.addr_bytes[0], mac.addr_bytes[1], mac.addr_bytes[2], mac.addr_bytes[3], mac.addr_bytes[4], mac.addr_bytes[5]); break; case 30: portlist_parse(argv[1], pktgen.nb_ports, &portlist); pg_ether_aton(argv[5], &mac); cli_printf(" Show Portlist: %08x vlan %d MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", portlist, atoi(argv[3]), mac.addr_bytes[0], mac.addr_bytes[1], mac.addr_bytes[2], mac.addr_bytes[3], mac.addr_bytes[4], mac.addr_bytes[5]); break; case 40: portlist_parse(argv[1], pktgen.nb_ports, &portlist); pg_ether_aton("1234:4567:8901", &mac); cli_printf(" Show Portlist: %08x %s: ", portlist, argv[2]); if (argv[2][0] == 'm') cli_printf("%02x:%02x:%02x:%02x:%02x:%02x\n", mac.addr_bytes[0], mac.addr_bytes[1], mac.addr_bytes[2], mac.addr_bytes[3], mac.addr_bytes[4], mac.addr_bytes[5]); else cli_printf("%d\n", 101); break; default: cli_help_show_group("Show"); return -1; } return 0; } static struct cli_tree my_tree[] = { c_dir("/data"), c_file("pci", pci_file, "display lspci information"), c_dir("/bin"), c_cmd("show", show_cmd, "show mapping options"), c_cmd("hello", hello_cmd, "Hello-World!!"), c_alias("h", "history", "display history commands"), c_alias("ll", "ls -l", "long directory listing alias"), c_end() }; Here is the cli_tree for this example, note it has a lot more commands. The show_cmd or **show** command is located a number of lines down. The cli_tree creates in the **/bin** directory a number of commands and the show command is one of these. The show command has four different formats if you look at the **show_map[]** structure. The user types one of these commands and cli_mapping() function attempts to locate the correct entry in the list. You will also notice another structure called **show_help**, which is an array of strings giving a cleaner and longer help description of each of the commands. Understanding the CLI system ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The command line interface is defined as a fake directory tree with executables, directorys and files. The user uses shell like standard commands to move about the directory and execute commands. The CLI is not a powerful as the Bash shell, but has a number of similar concepts. Our fake directory tree has a '/' or root directory which is created when cli_create() is called along with the default sbin directory. The user starts out at the root directory '/' and is allowed to cd to other directories, which could contain more executables, aliases or directories. The max number of directory levels is limited to the number of nodes given at startup. The default directory tree starts out as just root (/) and a sbin directory. Also it contains a file called copyright in root, which can be displayed using the default 'more copyright' command. A number of default commands are predefined in the /sbin directory and are defined above. Other bin directories can be added to the system if needed, but a limit of CLI_MAX_BINS is defined in the cli.h header file. ================================================ FILE: lib/cli/cli_map.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /** * @file * CLI map (command pattern) matching. * * Implements the lightweight printf-like token format used to select a * command variant based on argc/argv. */ #include #include #include <_atoip.h> #include "cli.h" int cli_map_list_search(const char *fmt, char *item, int index) { char *buf; int size; char *opts[CLI_MAX_ARGVS + 1]; size = strlen(fmt) + 1; buf = malloc(size); if (!buf) return -1; memset(buf, '\0', size); memset(opts, '\0', sizeof(opts)); snprintf(buf, size, "%s", fmt); pg_strtok(buf, " ", opts, CLI_MAX_ARGVS); /* Skip the %| in the string options */ int ret = pg_stropt(&opts[index][2], item, "|"); free(buf); return ret; } static int is_map_valid(const char *fmt, char *arg) { int ret = 0; char *p; struct rte_ipaddr ip; if (strchr("%bdDuUhHsnc46mkPC|l", fmt[1]) == NULL) return ret; /* validate all of the characters matching the format */ do { ret = 0; switch (fmt[1]) { case '%': ret = 1; break; case 'b': if (isdigit(*arg)) ret = 1; break; case 'd': if (isdigit(*arg)) ret = 1; break; case 'D': if (isdigit(*arg)) ret = 1; break; case 'u': if (isdigit(*arg)) ret = 1; break; case 'U': if (isdigit(*arg)) ret = 1; break; case 'h': if (isxdigit(*arg)) ret = 1; break; case 'H': if (isxdigit(*arg)) ret = 1; break; case 's': if (isprint(*arg)) ret = 1; break; case 'c': if (isprint(*arg)) ret = 1; break; case 'n': if (isdigit(*arg)) ret = 1; break; case '4': p = strchr(arg, '/'); if (_atoip(arg, p ? RTE_IPADDR_V4 | RTE_IPADDR_NETWORK : RTE_IPADDR_V4, &ip, sizeof(ip)) == 4) ret = 1; return ret; case '6': p = strchr(arg, '/'); if (_atoip(arg, p ? RTE_IPADDR_V6 | RTE_IPADDR_NETWORK : RTE_IPADDR_V6, &ip, sizeof(ip)) == 6) ret = 1; return ret; case 'm': if (isxdigit(*arg)) ret = 1; break; case 'k': return 1; /* list of ports or cores or the word all */ case 'P': if (isdigit(*arg) || (*arg == 'a')) ret = 1; break; case 'C': if (isdigit(*arg) || (*arg == 'a')) ret = 1; break; case '|': return (pg_stropt(&fmt[1], arg, "|") == -1) ? 0 : 1; case 'l': ret = 1; break; default: return 0; } arg++; } while (*arg && (ret == 0)); return ret; } struct cli_map * cli_mapping(struct cli_map *maps, int argc, char **argv) { int nb_args, i, j, ok; const char *m; char line[CLI_MAX_PATH_LENGTH + 1], *map[CLI_MAX_ARGVS], *p; memset(line, '\0', sizeof(line)); memset(map, '\0', sizeof(map)); p = line; for (i = 0; (m = maps[i].fmt) != NULL; i++) { snprintf(p, sizeof(line), "%s", m); nb_args = pg_strtok(p, " ", map, CLI_MAX_ARGVS); /* display the cli MAP if present as some help */ if (!strcmp("-?", argv[argc - 1]) || !strcmp("?", argv[argc - 1])) { cli_maps_show(maps, argc, argv); return NULL; } if (nb_args != argc) continue; /* Scan the map entry looking for a valid match */ for (j = 0, ok = 1; ok && (j < argc); j++) { if (map[j][0] == '%') { /* Found a format '%' validate it */ if (!is_map_valid(map[j], argv[j])) ok = 0; /* a constant string match valid */ } else if (!pg_strmatch(map[j], argv[j])) ok = 0; } if (ok) return &maps[i]; } return NULL; } static void decode_map(const char *fmt) { char *argv[CLI_MAX_ARGVS + 1]; char line[CLI_MAX_PATH_LENGTH + 1]; int n, i; memset(argv, '\0', sizeof(argv)); snprintf(line, sizeof(line), "%s", fmt); if (fmt[0] != '%') { cli_printf("%s ", fmt); return; } switch (fmt[1]) { case '%': cli_printf("%% "); break; case 'b': cli_printf("<8bit number> "); break; case 'n': cli_printf(" "); break; case 'u': cli_printf("<32bit unsigned> "); break; case 'U': cli_printf("<64bit unsigned> "); break; case 'd': cli_printf("<32bit number> "); break; case 'D': cli_printf("<64bit number> "); break; case 'h': cli_printf("<32bit hex> "); break; case 'H': cli_printf("<64bit hex> "); break; case 's': cli_printf(" "); break; case 'c': cli_printf(" "); break; case '4': cli_printf(" "); break; case '6': cli_printf(" "); break; case 'm': cli_printf(" "); break; case 'k': cli_printf(" "); break; case 'P': cli_printf(" "); break; case 'C': cli_printf(" "); break; case '|': cli_printf("["); n = pg_strtok(&line[2], "|", argv, CLI_MAX_ARGVS); for (i = 0; i < n; i++) cli_printf("%s%s", argv[i], (i < (n - 1)) ? "|" : ""); cli_printf("] "); break; case 'l': cli_printf(" "); break; default: cli_printf(" "); break; } } void cli_map_show(struct cli_map *m) { int i, nb_args; char line[CLI_MAX_PATH_LENGTH + 1], *map[CLI_MAX_ARGVS + 1], *p; memset(line, '\0', sizeof(line)); memset(map, '\0', sizeof(map)); p = line; snprintf(p, sizeof(line), "%s", m->fmt); nb_args = pg_strtok(p, " ", map, CLI_MAX_ARGVS); cli_printf(" %s ", map[0]); for (i = 1; i < nb_args; i++) decode_map(map[i]); cli_printf("\n"); } void cli_maps_show(struct cli_map *maps, int argc, char **argv) { struct cli_map *m; char line[CLI_MAX_PATH_LENGTH + 1], *map[CLI_MAX_ARGVS + 1]; int nb_args; if (!argc) return; cli_printf("\nUsage:\n"); for (m = maps; m->fmt != NULL; m++) { line[0] = '\0'; map[0] = NULL; snprintf(line, sizeof(line), "%s", m->fmt); nb_args = pg_strtok(line, " ", map, CLI_MAX_ARGVS); if (nb_args && !strcmp(argv[0], map[0])) cli_map_show(m); } } void cli_map_dump(struct cli_map *maps, int argc, char **argv) { int i, nb_args; struct cli_map *m; char line[CLI_MAX_PATH_LENGTH + 1], *map[CLI_MAX_ARGVS + 1], *p; memset(line, '\0', sizeof(line)); memset(map, '\0', sizeof(map)); p = line; m = cli_mapping(maps, argc, argv); if (!m) { cli_printf("Map for %d/", argc); for (i = 0; i < argc; i++) { cli_printf("<%s>", argv[i]); if ((i + 1) < argc) cli_printf(","); } cli_printf("\n"); } snprintf(p, sizeof(line), "%s", m->fmt); nb_args = pg_strtok(p, " ", map, CLI_MAX_ARGVS); cli_printf("%4d - %s == %s\n", m->index, argv[0], map[0]); for (i = 1; i < argc && i < nb_args; i++) cli_printf(" %s == %s\n", argv[i], map[i]); } ================================================ FILE: lib/cli/cli_map.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /** * @file * CLI argument map matching. * * Provides the cli_map structure and pattern-matching logic used to dispatch * CLI commands. Each map entry pairs a format string (with % tokens such as * %d, %4, %m, %P, %|opt1|opt2) with an integer index returned when the * user-supplied argc/argv matches that pattern. */ #ifndef _CLI_MAP_H_ #define _CLI_MAP_H_ #include #ifdef __cplusplus extern "C" { #endif /** One entry in a command dispatch table. */ struct cli_map { int index; /**< Value returned by cli_mapping() on a successful match */ const char *fmt; /**< Space-separated format string; see cli_mapping() for tokens */ }; /** * Parse a string looking for matching mapping. * * @param maps * Array string points with mapping formats using a special % formats * %d - decimal number 32bit * %D - decimal number 64bit * %h - hexadecimal number 32 bit number * %H - hexadecimal number 64 bit number * %P - Portlist * %C - Corelist * %s - string * %c - comma separated list string * %m - MAC address format * %4 - IPv4 address * %6 - IPv6 address * %k - kvargs list of =[,...] strings * %l - list format, if constains space then quote the string first * %| - Fixed list of valid strings * @param argc * Number of arguments in * @param argv * Array of command line string pointers * @return * return pointer map entry or NULL if not found */ struct cli_map *cli_mapping(struct cli_map *maps, int argc, char **argv); /** * Dump out the map entry * * @param maps * The cli_map structure pointer * @return * None */ void cli_map_show(struct cli_map *m); /** * Show the map table entries * * @param maps * The cli_map structure pointer * @return * None */ void cli_maps_show(struct cli_map *maps, int argc, char **argv); /** * Dump out the map table entry matching the argc/argv * * @param maps * The cli_map structure pointer * @param argc * Number of argumemts * @param argv * List of argument strings * @return * None */ void cli_map_dump(struct cli_map *maps, int argc, char **argv); /** * Search a choice token in a cli_map format string for a matching item. * * @param fmt * cli_map format string containing one or more %|opt1|opt2|… tokens. * @param item * The string to look up within the choice token. * @param index * Zero-based index of the choice token within @p fmt to search. * @return * Zero-based position of @p item within the choice list, or -1 if not found. */ int cli_map_list_search(const char *fmt, char *item, int index); #ifdef __cplusplus } #endif #endif /* CLI_MAP_H */ ================================================ FILE: lib/cli/cli_scrn.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /* Created by Keith Wiles @ intel.com */ #include #include #include #include #include #include #include #include #include #include #include #include "cli_scrn.h" #include "cli_input.h" struct cli_scrn *this_scrn; // clang-format off void __attribute__((format(printf, 3, 4))) scrn_printf(int16_t r, int16_t c, const char *fmt, ...) { va_list vaList; /* In some cases a segfault was reported when this_scrn * would become null. */ if (this_scrn && this_scrn->fd_out) { if ((r != 0) && (c != 0)) scrn_pos(r, c); va_start(vaList, fmt); vfprintf(this_scrn->fd_out, fmt, vaList); va_end(vaList); fflush(this_scrn->fd_out); } } // clang-format on void __attribute__((format(printf, 3, 4))) scrn_cprintf(int16_t r, int16_t ncols, const char *fmt, ...) { va_list vaList; char str[512]; if (ncols == -1) ncols = this_scrn->ncols; va_start(vaList, fmt); vsnprintf(str, sizeof(str), fmt, vaList); va_end(vaList); scrn_pos(r, scrn_center_col(ncols, str)); scrn_puts("%s", str); } void __attribute__((format(printf, 4, 5))) scrn_fprintf(int16_t r, int16_t c, FILE *f, const char *fmt, ...) { va_list vaList; if ((r != 0) && (c != 0)) scrn_pos(r, c); va_start(vaList, fmt); vfprintf(f, fmt, vaList); va_end(vaList); fflush(f); } static void scrn_set_io(FILE *in, FILE *out) { struct cli_scrn *scrn = this_scrn; if (scrn) { if (scrn->fd_in && (scrn->fd_in != stdin)) fclose(scrn->fd_in); if (scrn->fd_out && (scrn->fd_out != stdout)) fclose(scrn->fd_in); scrn->fd_in = in; scrn->fd_out = out; } } static int scrn_stdin_setup(void) { struct cli_scrn *scrn = this_scrn; struct termios term; if (!scrn) return -1; memcpy(&term, &scrn->oldterm, sizeof(term)); term.c_lflag &= ~(ICANON | ECHO | ISIG | IEXTEN); if (tcsetattr(fileno(scrn->fd_in), TCSANOW, &term)) { fprintf(stderr, "%s: failed to set tty\n", __func__); return -1; } return 0; } static void scrn_stdin_restore(void) { struct cli_scrn *scrn = this_scrn; if (!scrn) return; if (tcsetattr(fileno(scrn->fd_in), TCSANOW, &scrn->oldterm)) fprintf(stderr, "%s: failed to set tty\n", __func__); } static void handle_winch(int sig) { struct winsize w; if (sig != SIGWINCH) return; ioctl(0, TIOCGWINSZ, &w); this_scrn->nrows = w.ws_row; this_scrn->ncols = w.ws_col; /* Need to refresh the screen */ cli_clear_line(-1); cli_redisplay_line(); } int scrn_create(int scrn_type, int theme) { struct winsize w; struct cli_scrn *scrn = this_scrn; struct sigaction sa; if (!scrn) { printf("*** scrn is NULL exit\n"); exit(-1); } rte_atomic32_set(&scrn->pause, SCRN_SCRN_PAUSED); scrn->theme = theme; scrn->type = scrn_type; if (scrn_type == SCRN_STDIN_TYPE) { if (!scrn->oldterm_saved) { if (tcgetattr(fileno(scrn->fd_in), &scrn->oldterm)) { fprintf(stderr, "%s: tcgetattr failed\n", __func__); exit(-1); } scrn->oldterm_saved = 1; } memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = handle_winch; sigaction(SIGWINCH, &sa, NULL); ioctl(0, TIOCGWINSZ, &w); scrn->nrows = w.ws_row; scrn->ncols = w.ws_col; if (scrn_stdin_setup()) return -1; } else if (scrn_type == SCRN_NOTTY_TYPE) { scrn->nrows = 24; scrn->ncols = 80; } else { fprintf(stderr, "%s: unexpected scrn_type %d\n", __func__, scrn_type); return -1; } scrn_color(SCRN_DEFAULT_FG, SCRN_DEFAULT_BG, SCRN_OFF); return 0; } int scrn_create_with_defaults(int theme) { int scrn_type = isatty(STDIN_FILENO) ? SCRN_STDIN_TYPE : SCRN_NOTTY_TYPE; return scrn_create(scrn_type, (theme) ? SCRN_THEME_ON : SCRN_THEME_OFF); } void scrn_destroy(void) { struct cli_scrn *scrn = this_scrn; if (scrn && (scrn->type == SCRN_STDIN_TYPE)) scrn_stdin_restore(); free(scrn); this_scrn = NULL; } RTE_INIT(scrn_constructor) { struct cli_scrn *scrn; scrn = calloc(1, sizeof(struct cli_scrn)); if (!scrn) { printf("*** unable to allocate cli_scrn structure\n"); exit(-1); } this_scrn = scrn; scrn_set_io(stdin, stdout); } ================================================ FILE: lib/cli/cli_scrn.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ /* Created by Keith Wiles @ intel.com */ #ifndef __CLI_SCRN_H_ #define __CLI_SCRN_H_ #include #ifdef __cplusplus extern "C" { #endif /** * @file * RTE simple cursor and color support for VT100 using ANSI color escape codes. * ***/ #include #include #include #include #include #include #define VT100_INITIALIZE -1 #define vt100_open_square '[' #define vt100_escape 0x1b #define vt100_del 0x7f #define ESC "\033" /* Key codes */ #define vt100_word_left ESC "b" #define vt100_word_right ESC "f" #define vt100_suppr ESC "[3~" #define vt100_tab "\011" /* Action codes for cli_vt100 */ #define vt100_bell "\007" #define vt100_bs "\010" #define vt100_bs_clear "\b \b" /* cursor codes */ #define vt100_cursor_off ESC "[?25l" #define vt100_cursor_on ESC "[?25h" #define vt100_save_cursor ESC "7" #define vt100_restore_cursor ESC "8" #define vt100_line_feed ESC "D" #define vt100_crnl ESC "E" #define vt100_reverse_line_feed ESC "M" #define vt100_up_arr ESC "[A" #define vt100_down_arr ESC "[B" #define vt100_right_arr ESC "[C" #define vt100_left_arr ESC "[D" #define vt100_up_lines ESC "[%dA" #define vt100_down_lines ESC "[%dB" #define vt100_right_columns ESC "[%dC" #define vt100_left_columns ESC "[%dD" #define vt100_home ESC "[H" #define vt100_pos ESC "[%d;%dH" #define vt100_setw ESC "[%d;r" #define vt100_clear_right ESC "[0K" #define vt100_clear_left ESC "[1K" #define vt100_clear_down ESC "[0J" #define vt100_clear_up ESC "[1J" #define vt100_clear_line ESC "[2K" #define vt100_clear_screen ESC "[2J" #define vt100_pos_cursor ESC "[%d;%dH" #define vt100_multi_right ESC "\133%uC" #define vt100_multi_left ESC "\133%uD" /* Result of parsing : it must be synchronized with * vt100_cmds_list[] in vt100_cmds.c */ enum { VT100_INVALID_KEY = 0, VT100_KEY_UP_ARR, VT100_KEY_DOWN_ARR, VT100_KEY_RIGHT_ARR, VT100_KEY_LEFT_ARR, VT100_KEY_BKSPACE, VT100_KEY_RETURN, VT100_KEY_CTRL_A, VT100_KEY_CTRL_E, VT100_KEY_CTRL_K, VT100_KEY_CTRL_Y, VT100_KEY_CTRL_C, VT100_KEY_CTRL_F, VT100_KEY_CTRL_B, VT100_KEY_SUPPR, VT100_KEY_TAB, VT100_KEY_CTRL_D, VT100_KEY_CTRL_L, VT100_KEY_RETURN2, VT100_KEY_META_BKSPACE, VT100_KEY_WLEFT, VT100_KEY_WRIGHT, VT100_KEY_CTRL_W, VT100_KEY_CTRL_P, VT100_KEY_CTRL_N, VT100_KEY_META_D, VT100_KEY_CTRL_X, VT100_MAX_KEYS }; extern const char *vt100_commands[]; enum vt100_parse_state { VT100_INIT, VT100_ESCAPE, VT100_ESCAPE_CSI, VT100_DONE = -1, VT100_CONTINUE = -2 }; #define VT100_BUF_SIZE 8 struct cli_vt100 { int bufpos; /**< Current write offset into @p buf */ char buf[VT100_BUF_SIZE]; /**< Accumulator for multi-byte VT100 escape sequences */ enum vt100_parse_state state; /**< Current parser state for escape-sequence detection */ }; struct vt100_cmds { const char *str; void (*func)(void); }; /** scrn version number */ #define SCRN_VERSION "2.0.0" /* Add more defines for new types */ #define SCRN_STDIN_TYPE 0 #define SCRN_NOTTY_TYPE 1 /** Structure to hold information about the screen and control access. */ struct cli_scrn { rte_atomic32_t pause __rte_cache_aligned; /**< Pause the update of the screen. */ uint16_t nrows; /**< Max number of rows. */ uint16_t ncols; /**< Max number of columns. */ uint16_t theme; /**< Current theme state on or off */ uint16_t type; /**< screen I/O type */ uint16_t no_write; /**< Disable screen write */ uint16_t oldterm_saved; /**< Old terminal setup saved */ struct termios oldterm; /**< Old terminal setup information */ FILE *fd_out; /**< File descriptor for output */ FILE *fd_in; /**< File descriptor for input */ }; /** A single byte to hold port of a Red/Green/Blue color value */ typedef uint8_t scrn_rgb_t; extern struct cli_scrn *this_scrn; /** Enable or disable the screen from being updated */ enum { SCRN_SCRN_RUNNING = 0, SCRN_SCRN_PAUSED = 1 }; /** Enable or disable the theme or color options */ enum { SCRN_THEME_OFF = 0, SCRN_THEME_ON = 1 }; /** ANSI color codes zero based, need to add 30 or 40 for foreground or background color code */ typedef enum { SCRN_BLACK = 0, SCRN_RED = 1, SCRN_GREEN = 2, SCRN_YELLOW = 3, SCRN_BLUE = 4, SCRN_MAGENTA = 5, SCRN_CYAN = 6, SCRN_WHITE = 7, SCRN_RGB = 8, SCRN_DEFAULT_FG = 9, SCRN_DEFAULT_BG = 9, SCRN_NO_CHANGE = 98, SCRN_UNKNOWN_COLOR = 99 } scrn_color_e; /** ANSI color codes zero based for attributes per color */ typedef enum { SCRN_OFF = 0, SCRN_BOLD = 1, SCRN_FAINT = 2, SCRN_ITALIC = 3, SCRN_UNDERSCORE = 4, SCRN_SLOW_BLINK = 5, SCRN_FAST_BLINK = 6, SCRN_REVERSE = 7, SCRN_CONCEALED = 8, SCRN_CROSSOUT = 9, SCRN_DEFAULT_FONT = 10, SCRN_UNDERLINE_OFF = 24, SCRN_BLINK_OFF = 25, SCRN_INVERSE_OFF = 27, SCRN_REVEAL = 28, SCRN_NOT_CROSSED_OUT = 29, /* 30-39 and 40-49 Foreground and Background colors */ SCRN_FRAMED = 51, SCRN_ENCIRCLED = 52, SCRN_OVERLINED = 53, SCRN_NOT_FRAMED = 54, SCRN_NOT_OVERLINED = 55, SCRN_NO_ATTR = 98, SCRN_UNKNOWN_ATTR = 99 } scrn_attr_e; #define SCRN_BLINK SCRN_SLOW_BLINK /** A single byte to hold part of a Red/Green/Blue color value */ typedef uint8_t cli_rgb_t; static inline int scrn_write(const void *str, int len) { if (len <= 0) len = strlen(str); if (this_scrn->no_write == 0 && write(fileno(this_scrn->fd_out), str, len) != len) fprintf(stderr, "%s: Write failed\n", __func__); return len; } static inline int scrn_read(char *buf, int len) { int n = 0; if (!buf || !len) return 0; while (len--) n += read(fileno(this_scrn->fd_in), buf++, 1); return n; } // clang-format off static inline void __attribute__((format(printf, 1, 2))) scrn_puts(const char *fmt, ...) { struct cli_scrn *scrn = this_scrn; FILE *f; va_list vaList; f = (!scrn || !scrn->fd_out) ? stdout : scrn->fd_out; va_start(vaList, fmt); vfprintf(f, fmt, vaList); va_end(vaList); fflush(f); } // clang-format on void scrn_cprintf(int16_t r, int16_t ncols, const char *fmt, ...); void scrn_printf(int16_t r, int16_t c, const char *fmt, ...); void scrn_fprintf(int16_t r, int16_t c, FILE *f, const char *fmt, ...); #define _s(_x, _y) \ static __inline__ void _x \ { \ if (this_scrn && this_scrn->type == SCRN_STDIN_TYPE) \ _y; \ else \ scrn_puts("\n"); \ } /** position cursor to row and column */ _s(scrn_pos(int r, int c), scrn_puts(vt100_pos, r, c)) /** Move cursor to the top left of the screen */ _s(scrn_top(void), scrn_puts("\033H")) /** Move cursor to the Home position */ _s(scrn_home(void), scrn_puts("\033H")) /** Turn cursor off */ _s(scrn_coff(void), scrn_puts("\033[?25l")) /** Turn cursor on */ _s(scrn_con(void), scrn_puts("\033[?25h")) /** Hide cursor */ _s(scrn_turn_on(void), scrn_puts("\033[?25h")) /** Display cursor */ _s(scrn_turn_off(void), scrn_puts("\033[?25l")) /** Save current cursor position */ _s(scrn_save(void), scrn_puts("\0337")) /** Restore the saved cursor position */ _s(scrn_restore(void), scrn_puts("\0338")) /** Clear from cursor to end of line */ _s(scrn_eol(void), scrn_puts("\033[K")) /** Clear from cursor to beginning of line */ _s(scrn_cbl(void), scrn_puts("\033[1K")) /** Clear entire line */ _s(scrn_cel(void), scrn_puts("\033[2K")) /** Clear from cursor to end of screen */ _s(scrn_clw(void), scrn_puts("\033[J")) /** Clear from cursor to beginning of screen */ _s(scrn_clb(void), scrn_puts("\033[1J")) /** Clear the screen, move cursor to home */ _s(scrn_cls(void), scrn_puts("\033[2J")) /** Start reverse video */ _s(scrn_reverse(void), scrn_puts("\033[7m")) /** Stop attribute like reverse and underscore */ _s(scrn_normal(void), scrn_puts("\033[0m")) /** Scroll whole screen up r number of lines */ _s(scrn_scroll(int r), scrn_puts("\033[%d;r", r)) /** Scroll whole screen up r number of lines */ _s(scrn_scroll_up(int r), scrn_puts("\033[%dS", r)) /** Scroll whole screen down r number of lines */ _s(scrn_scroll_down(int r), scrn_puts("\033[%dT", r)) /** Move down nlines plus move to column 1 */ _s(scrn_nlines(int r), scrn_puts("\033[%dE", r)) /** Set window size, from to end of screen */ _s(scrn_setw(int t), scrn_puts("\033[%d;r", t)) /** Cursor position report */ _s(scrn_cpos(void), scrn_puts("\033[6n")) /** Cursor move right characters */ _s(scrn_cnright(int n), scrn_puts("\033[%dC", n)) /** Cursor move left characters */ _s(scrn_cnleft(int n), scrn_puts("\033[%dD", n)) /** New line */ _s(scrn_newline(void), scrn_puts("\033[20h")) /** Move one character right */ _s(scrn_cright(void), scrn_puts("\033[C")) /** Move one character left */ _s(scrn_cleft(void), scrn_puts("\033[D")) /** Move cursor to beginning of line */ _s(scrn_bol(void), scrn_puts("\r")) /** Return the version string */ static __inline__ const char *scrn_version(void) { return SCRN_VERSION; } /** Position the cursor to a line and clear the entire line */ static __inline__ void scrn_clr_line(int r) { scrn_pos(r, 0); scrn_cel(); } /** Position cursor to row/column and clear to end of line */ static __inline__ void scrn_eol_pos(int r, int c) { scrn_pos(r, c); scrn_eol(); } void __set_prompt(void); /** Stop screen from updating until resumed later */ static __inline__ void scrn_pause(void) { if (!this_scrn) printf("**** this scrn is NULL\n"); rte_atomic32_set(&this_scrn->pause, SCRN_SCRN_PAUSED); __set_prompt(); } /** Resume the screen from a pause */ static __inline__ void scrn_resume(void) { rte_atomic32_set(&this_scrn->pause, SCRN_SCRN_RUNNING); __set_prompt(); } /* Is the screen in the paused state */ static __inline__ int scrn_is_paused(void) { return rte_atomic32_read(&this_scrn->pause) == SCRN_SCRN_PAUSED; } /** Output a message of the current line centered */ static __inline__ int scrn_center_col(int16_t ncols, const char *msg) { int16_t s; s = ((ncols / 2) - (strlen(msg) / 2)); return (s <= 0) ? 1 : s; } /** Erase the screen by scrolling it off the display, then put cursor at the bottom */ static __inline__ void scrn_erase(int16_t nrows) { scrn_setw(1); /* Clear the window to full screen. */ scrn_pos(nrows + 1, 1); /* Put cursor on the last row. */ } /** Output a string at a row/column for a number of times */ static __inline__ void scrn_repeat(int16_t r, int16_t c, const char *str, int cnt) { int i; scrn_pos(r, c); for (i = 0; i < cnt; i++) scrn_printf(0, 0, "%s", str); } /** Output a column of strings at a given starting row for a given number of times */ static __inline__ void scrn_col_repeat(int16_t r, int16_t c, const char *str, int cnt) { int i; for (i = 0; i < cnt; i++) { scrn_pos(r++, c); scrn_printf(0, 0, "%s", str); } } /** Set the foreground color + attribute at the current cursor position */ static __inline__ void scrn_fgcolor(scrn_color_e color, scrn_attr_e attr) { scrn_puts("\033[%d;%dm", attr, color + 30); } /** Set the background color + attribute at the current cursor position */ static __inline__ void scrn_bgcolor(scrn_color_e color, scrn_attr_e attr) { scrn_puts("\033[%d;%dm", attr, color + 40); } /** Set the foreground/background color + attribute at the current cursor position */ static __inline__ void scrn_fgbgcolor(scrn_color_e fg, scrn_color_e bg, scrn_attr_e attr) { scrn_puts("\033[%d;%d;%dm", attr, fg + 30, bg + 40); } /** Main routine to set color for foreground and background nd attribute at the current position */ static __inline__ void scrn_color(scrn_color_e fg, scrn_color_e bg, scrn_attr_e attr) { if ((fg != SCRN_NO_CHANGE) && (bg != SCRN_NO_CHANGE)) scrn_fgbgcolor(fg, bg, attr); else if (fg == SCRN_NO_CHANGE) scrn_bgcolor(bg, attr); else if (bg == SCRN_NO_CHANGE) scrn_fgcolor(fg, attr); } /** Setup for 256 RGB color methods. A routine to output RGB color codes if supported */ static __inline__ void scrn_rgb(uint8_t fg_bg, cli_rgb_t r, cli_rgb_t g, cli_rgb_t b) { scrn_puts("\033[%d;2;%d;%d;%dm", fg_bg, r, g, b); } /** Set the foreground color + attribute at the current cursor position */ static __inline__ int scrn_fgcolor_str(char *str, scrn_color_e color, scrn_attr_e attr) { return snprintf(str, 16, ESC "[%d;%dm", attr, color + 30); } /** Set the background color + attribute at the current cursor position */ static __inline__ int scrn_bgcolor_str(char *str, scrn_color_e color, scrn_attr_e attr) { return snprintf(str, 16, ESC "[%d;%dm", attr, color + 40); } /** Set the foreground/background color + attribute at the current cursor position */ static __inline__ int scrn_fgbgcolor_str(char *str, scrn_color_e fg, scrn_color_e bg, scrn_attr_e attr) { return snprintf(str, 16, ESC "[%d;%d;%dm", attr, fg + 30, bg + 40); } /** * Main routine to set color for foreground and background and attribute at * the current position. */ static __inline__ int scrn_color_str(char *str, scrn_color_e fg, scrn_color_e bg, scrn_attr_e attr) { if ((fg != SCRN_NO_CHANGE) && (bg != SCRN_NO_CHANGE)) return scrn_fgbgcolor_str(str, fg, bg, attr); else if (fg == SCRN_NO_CHANGE) return scrn_bgcolor_str(str, bg, attr); else if (bg == SCRN_NO_CHANGE) return scrn_fgcolor_str(str, fg, attr); else return 0; } /** Setup for 256 RGB color methods. A routine to output RGB color codes if supported */ static __inline__ int scrn_rgb_str(char *str, uint8_t fg_bg, scrn_rgb_t r, scrn_rgb_t g, scrn_rgb_t b) { return snprintf(str, 16, ESC "[%d;2;%d;%d;%dm", fg_bg, r, g, b); } /** * Create and initialise the global cli_scrn instance. * * @param scrn_type * Screen I/O type: SCRN_STDIN_TYPE for interactive TTY, * SCRN_NOTTY_TYPE for non-TTY (pipe/file) output. * @param theme * Initial theme state: SCRN_THEME_ON or SCRN_THEME_OFF. * @return * 0 on success or -1 on error. */ int scrn_create(int scrn_type, int theme); /** * Create the global cli_scrn with default stdin/stdout I/O and theme off. * * @param theme * Initial theme state: SCRN_THEME_ON or SCRN_THEME_OFF. * @return * 0 on success or -1 on error. */ int scrn_create_with_defaults(int theme); /** * Destroy the global cli_scrn instance and free its resources. */ void scrn_destroy(void); /** * Create the cli_vt100 structure * * @return * Pointer to cli_vt100 structure or NULL on error */ struct cli_vt100 *vt100_setup(void); /** * Destroy the cli_vt100 structure * * @param * The pointer to the cli_vt100 structure. */ void vt100_free(struct cli_vt100 *vt); /** * Input a new character. * * @param vt * The pointer to the cli_vt100 structure * @param c * The character to parse for cli_vt100 commands * @return * -1 if the character is not part of a control sequence * -2 if c is not the last char of a control sequence * Else the index in vt100_commands[] */ int vt100_parse_input(struct cli_vt100 *vt, uint8_t c); /** * Execute the VT100 command at the given index in vt100_commands[]. * * @param idx * Index into the vt100_commands[] dispatch table (VT100_KEY_* enum value). */ void vt100_do_cmd(int idx); /** * Return the vt100_cmds dispatch table. * * @return * Pointer to the internal vt100_cmds array. */ struct vt100_cmds *vt100_get_cmds(void); #ifdef __cplusplus } #endif #endif /* __CLI_SCRN_H_ */ ================================================ FILE: lib/cli/cli_search.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #include #include #include "cli.h" static int __count_nodes(struct cli_node *node, uint32_t flags, args_t *args) { if (flags & node->type) args->arg1.u32[0]++; return 0; } uint32_t cli_dir_item_count(struct cli_node *node, uint32_t types) { args_t args; if (!node || !is_directory(node)) return 0; memset(&args, '\0', sizeof(args)); cli_scan_directory(node, __count_nodes, types, &args); return args.arg1.u32[0]; } uint32_t cli_path_item_count(uint32_t types) { uint32_t cnt = 0, i; /* Look in the current directory first for a command */ for (i = 0; i < CLI_MAX_BINS; i++) cnt += cli_dir_item_count(this_cli->bins[i], types); return cnt; } uint32_t cli_path_cmd_count(void) { return cli_path_item_count(CLI_EXE_TYPE); } static uint32_t node_list_with_type(uint32_t flags, void **ret) { struct cli_node *n, **nodes, *bin; uint32_t cnt, i, k = 0; cnt = cli_path_item_count(flags); if (cnt) { nodes = calloc(cnt + 1, sizeof(struct cli_node *)); if (!nodes) return 0; for (i = 0; i < CLI_MAX_BINS; i++) { bin = this_cli->bins[i]; if (!bin) continue; /* * Current directory could be a bin directory skip this bin * directory as the cwd has already been searched. The cwd is * the first entry in the bins list. */ if ((i > 0) && (bin == get_cwd())) { continue; } TAILQ_FOREACH (n, &bin->items, next) { if (n->type & flags) nodes[k++] = n; } } *ret = nodes; } return k; } static uint32_t dir_list_with_type(struct cli_node *dir, uint32_t flags, void **ret) { struct cli_node *n, **nodes; uint32_t cnt, k = 0; cnt = cli_dir_item_count(dir, flags); if (cnt) { nodes = calloc(cnt + 1, sizeof(struct cli_node *)); if (!nodes) return 0; TAILQ_FOREACH (n, &dir->items, next) { if (n->type & flags) nodes[k++] = n; } *ret = nodes; } return k; } uint32_t cli_node_list_with_type(struct cli_node *node, uint32_t flags, void **ret) { if (node) return dir_list_with_type(node, flags, ret); else return node_list_with_type(flags, ret); } void cli_node_list_free(void *nodes) { free(nodes); } int cli_scan_directory(struct cli_node *dir, cli_scan_t func, uint32_t flags, args_t *args) { struct cli_node *node; int ret = 0; if (!func) return ret; if (!dir) dir = cli_root_node(); TAILQ_FOREACH (node, &dir->items, next) { if (node->type & flags) { ret = func(node, flags, args); if (ret) break; } } return ret; } int cli_scan_path(const char *path, cli_scan_t func, uint32_t flags, args_t *args) { struct cli_node *node; if (cli_find_node(path, &node)) if (cli_scan_directory(node, func, flags, args)) return 1; return 0; } struct cli_node * cli_search_dir(struct cli_node *dir, const char *name, uint32_t type) { struct cli_node *node; if (!name || (*name == '\0')) return NULL; if (!dir) dir = get_cwd(); else if (!is_directory(dir)) return NULL; /* Process the .. and . directories */ if (!strcmp(name, "..")) return dir->parent; else if (!strcmp(name, ".")) return dir; TAILQ_FOREACH (node, &dir->items, next) { if (pg_strmatch(node->name, name) && (node->type & type)) return node; } return NULL; } int cli_find_node(const char *path, struct cli_node **ret) { struct cli_node *node, *dir; char *my_path = NULL; char *argv[CLI_MAX_ARGVS + 1]; int n, i; if (!path || (*path == '\0')) return 0; if (path[0] == '/' && path[1] == '\0') { node = cli_root_node(); goto _leave; } /* Skip the leading '/' */ my_path = strdup((path[0] == '/') ? &path[1] : path); if (!my_path) return 0; n = pg_strtok(my_path, "/", argv, CLI_MAX_ARGVS); /* handle the special case of leading '/' */ dir = (path[0] == '/') ? get_root() : get_cwd(); /* Follow the directory path if present */ for (i = 0, node = NULL; i < n; i++) { node = cli_search_dir(dir, argv[i], CLI_ALL_TYPE); if (!node) break; /* follow the next directory */ if (is_directory(node) && (i < n)) dir = node; else break; } free(my_path); _leave: if (ret) *ret = node; return (node) ? 1 : 0; } struct cli_node * cli_last_dir_in_path(const char *path) { struct cli_node *node, *dir; char *my_path = NULL; char *argv[CLI_MAX_ARGVS + 1]; int n, i; if (!path || (*path == '\0')) return get_cwd(); if (path[0] == '/' && path[1] == '\0') return cli_root_node(); /* Skip the leading '/' */ my_path = strdup((path[0] == '/') ? &path[1] : path); if (!my_path) return NULL; n = pg_strtok(my_path, "/", argv, CLI_MAX_ARGVS); /* handle the special case of leading '/' */ if (path[0] == '/') dir = this_cli->root.tqh_first; else dir = get_cwd(); /* Follow the directory path if present */ for (i = 0, node = NULL; i < n; i++) { node = cli_search_dir(dir, argv[i], CLI_ALL_TYPE); if (!node) break; /* follow the next directory */ if (is_directory(node) && (i < n)) dir = node; else break; } free(my_path); return dir; } struct cli_node * cli_find_cmd(const char *path) { struct cli_node *cmd, *dir; int i; if (cli_find_node(path, &cmd)) return cmd; for (i = 0; i < CLI_MAX_BINS; i++) { if ((dir = this_cli->bins[i]) == NULL) continue; cmd = cli_search_dir(dir, path, CLI_EXE_TYPE); if (cmd) return cmd; } return NULL; } ================================================ FILE: lib/cli/cli_search.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #ifndef _CLI_SEARCH_H_ #define _CLI_SEARCH_H_ #include #include #include #include #include #include #include /** * @file * CLI node search and directory scanning. * * Provides helpers to locate nodes in the CLI tree (commands/files/directories) * and to enumerate nodes in a directory or along the executable search path. */ #ifdef __cplusplus extern "C" { #endif /** Generic 64-bit argument word that can be interpreted several ways. */ typedef union { void *voidp; /**< Pointer value */ char chr[8]; /**< Up to 8 raw bytes */ uint64_t u64; /**< 64-bit unsigned integer */ uint32_t u32[2]; /**< Two 32-bit unsigned integers */ uint16_t u16[4]; /**< Four 16-bit unsigned integers */ } arg_u; /** Four-word argument block passed to cli_scan_t callbacks. */ typedef struct { arg_u arg1; /**< Argument word 1 */ arg_u arg2; /**< Argument word 2 */ arg_u arg3; /**< Argument word 3 */ arg_u arg4; /**< Argument word 4 */ } args_t; struct cli; struct cli_node; /** Callback invoked by cli_scan_directory() for each matching node. */ typedef int (*cli_scan_t)(struct cli_node *node, uint32_t flags, args_t *args); /** * Scan a directory and call the func with the node found. * * @note Uses a thread variable called this_cli. * * @param dir * Node pointer to directory to scan * @param func * cli_scan_t function pointer * @param flags * Bitmap of node types to include (e.g., CLI_CMD_NODE|CLI_DIR_NODE) * @param args * Optional argument block passed to @p func * @return * 0 on success or -1 on error */ int cli_scan_directory(struct cli_node *dir, cli_scan_t func, uint32_t flags, args_t *args); /** * Find a node by path * * @note Uses a thread variable called this_cli. * * @param path * Path to node * @param ret * Pointer to pointer of a cli_node if found * @return * 1 if found else 0 */ int cli_find_node(const char *path, struct cli_node **ret); /** * Search the local and bin directories for a command * * @note Uses a thread variable called this_cli. * * @param path * String for the command to use * @return * Pointer to the command node or NULL */ struct cli_node *cli_find_cmd(const char *path); /** * Count the number of nodes of the given type(s) in a directory. * * @note Uses a thread variable called this_cli. * * @param n * node or NULL for current working directory * @return * Number of nodes found of this type in the directory */ uint32_t cli_dir_item_count(struct cli_node *node, uint32_t types); /** * Count the number of commands in the execute path * * @note Uses a thread variable called this_cli. * * @return * Number of nodes found of this type in the directory */ uint32_t cli_path_cmd_count(void); /** * Return a list of nodes matching given information * * @note Uses a thread variable called this_cli. * * @param node * Node to start search or use the path list. * @param flags * Type of nodes to return * @param ret * Pointer to an array of pointer for return value * @return * Number of nodes found of this type in the directory */ uint32_t cli_node_list_with_type(struct cli_node *node, uint32_t flags, void **ret); /** * Free a list returned by cli_node_list_with_type(). * * @note Uses a thread variable called this_cli. * * @param node * Pointer to the node to free * @return * N/A */ void cli_node_list_free(void *nodes); /** * Count the number of commands in the execute path * * @note Uses a thread variable called this_cli. * * @param types * The number of nodes to count * @return * Number of nodes found of this type in the directory */ uint32_t cli_path_item_count(uint32_t types); /** * Find and return the last directory node in a given path string. * * @note Uses a thread variable called this_cli. * * @param path * Path string to scan * @return * Pointer to last directory node in path */ struct cli_node *cli_last_dir_in_path(const char *path); /** * Scan a directory for a node matching a name and type. * * @note Uses a thread variable called this_cli. * * @param dir * Pointer to directory node to start with in scanning * @param name * String to match the nodes with * @param type * Type of nodes to include in scan. * @return * Number of nodes found of this type in the directory */ struct cli_node *cli_search_dir(struct cli_node *dir, const char *name, uint32_t type); /** * Scan the directory given by @p path. * * @note Uses a thread variable called this_cli. * * @param path * The path string to use * @param func * The function to call when a match is found. * @param flags * Type of files to include in match * @param args * Arguments to include with function call. * @return * Number of nodes found of this type in the directory */ int cli_scan_path(const char *path, cli_scan_t func, uint32_t flags, args_t *args); #ifdef __cplusplus } #endif #endif /* _CLI_SEARCH_H_ */ ================================================ FILE: lib/cli/cli_vt100.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2016-2026>, Intel Corporation. */ #include "cli.h" #include "cli_input.h" #include "cli_scrn.h" #include "cli_auto_complete.h" static inline void key_up_arr(void) { struct gapbuf *gb = this_cli->gb; char *line; line = cli_history_prev(); if (line) { gb_reset_buf(gb); gb_set_point(gb, gb_str_insert(gb, line, 0)); cli_set_flag(CLEAR_LINE); } } static inline void key_down_arr(void) { struct gapbuf *gb = this_cli->gb; char *line; line = cli_history_next(); if (line) { gb_reset_buf(gb); gb_set_point(gb, gb_str_insert(gb, line, 0)); cli_set_flag(CLEAR_LINE); } } static inline void key_right_arr(void) { struct gapbuf *gb = this_cli->gb; if (!gb_eof(gb)) { scrn_write(vt100_right_arr, 0); gb_move_right(gb); } } static inline void key_left_arr(void) { struct gapbuf *gb = this_cli->gb; if (!gb_point_at_start(gb)) { scrn_write(vt100_left_arr, 0); gb_move_left(gb); } } static inline void key_backspace(void) { struct gapbuf *gb = this_cli->gb; if (!gb_point_at_start(gb)) { cli_cursor_left(); gb_move_left(gb); gb_del(gb, 1); cli_set_flag(DELETE_CHAR); } } static inline void key_return(void) { cli_write("\n", 1); cli_execute(); /* Init buffer must be after execute of command */ gb_reset_buf(this_cli->gb); /* If not quit command then print prompt */ if (!this_cli->quit_flag) this_cli->flags |= DISPLAY_PROMPT; this_cli->curr_hist = NULL; } static inline void key_ctrl_a(void) { cli_printf(vt100_multi_left, gb_left_data_size(this_cli->gb)); gb_set_point(this_cli->gb, 0); } static inline void key_ctrl_e(void) { cli_printf(vt100_multi_right, gb_right_data_size(this_cli->gb)); gb_set_point(this_cli->gb, -1); } static inline void key_ctrl_k(void) { struct cli *cli = this_cli; cli_clear_to_eol(); gb_move_gap_to_point(cli->gb); free(cli->kill); if (gb_right_data_size(cli->gb)) cli->kill = strndup(gb_end_of_gap(cli->gb), gb_right_data_size(cli->gb)); gb_del(cli->gb, gb_right_data_size(cli->gb)); } static inline void key_ctrl_y(void) { struct cli *cli = this_cli; /* Yank and put are Not supported yet */ if (cli->kill) { gb_str_insert(cli->gb, cli->kill, strlen(cli->kill)); cli_clear_line(-1); cli_redisplay_line(); } } static inline void key_ctrl_c(void) { gb_reset_buf(this_cli->gb); cli_clear_line(-1); this_cli->plen = this_cli->prompt(0); this_cli->curr_hist = NULL; } static inline void key_ctrl_f(void) { key_right_arr(); } static inline void key_ctrl_b(void) { key_left_arr(); } // This is the 'super key' which has had many usages in the past. I changed it to // but the back space key as it seems windows or mobaxterm sends this code to delete key. static inline void key_suppr(void) { struct gapbuf *gb = this_cli->gb; if (!gb_point_at_start(gb)) { cli_cursor_left(); gb_move_left(gb); gb_del(gb, 1); cli_set_flag(DELETE_CHAR); } } static inline void key_tab(void) { cli_auto_complete(); } static inline void key_ctrl_d(void) { gb_dump(this_cli->gb, NULL); } static inline void key_ctrl_l(void) { cli_clear_screen(); cli_clear_line(-1); cli_redisplay_line(); } static inline void key_return2(void) { key_return(); } static inline void key_meta_backspace(void) { } /* meta+b or command+b or window+b or super+b */ static inline void key_word_left(void) { do { key_left_arr(); if (gb_get_prev(this_cli->gb) == ' ') break; } while (!gb_point_at_start(this_cli->gb)); } static inline void key_word_right(void) { while (!gb_point_at_end(this_cli->gb)) { key_right_arr(); if (gb_get(this_cli->gb) == ' ') break; } } static inline void key_ctrl_w(void) { key_meta_backspace(); } static inline void key_ctrl_p(void) { key_up_arr(); } static inline void key_ctrl_n(void) { key_down_arr(); } static inline void key_meta_d(void) { } static inline void key_ctrl_x(void) { this_cli->quit_flag = 1; } static inline void key_invalid(void) { } /* Order must be maintained see cli_scrn.h */ static struct vt100_cmds vt100_cmd_list[] = { {"Invalid", key_invalid}, {vt100_up_arr, key_up_arr}, /* Move cursor up one line */ {vt100_down_arr, key_down_arr}, /* Move cursor down on line */ {vt100_right_arr, key_right_arr}, /* Move cursor right */ {vt100_left_arr, key_left_arr}, /* Move cursor left */ {"\177", key_backspace}, /* Cursor Left + delete */ {"\n", key_return}, /* Execute command */ {"\001", key_ctrl_a}, /* Same as key_left_arr */ {"\005", key_ctrl_e}, /* Same as key_right_arr */ {"\013", key_ctrl_k}, /* Kill to end of line */ {"\031", key_ctrl_y}, /* Put the kill buffer not working */ {"\003", key_ctrl_c}, /* Reset line and start over */ {"\006", key_ctrl_f}, /* Same as key_right_arr */ {"\002", key_ctrl_b}, /* Same as key_left_arr */ {vt100_suppr, key_suppr}, /* delete 1 char from the left */ {vt100_tab, key_tab}, /* Auto complete */ {"\004", key_ctrl_d}, /* Debug output if enabled */ {"\014", key_ctrl_l}, /* redraw screen */ {"\r", key_return2}, /* Same as key_return */ {"\033\177", key_meta_backspace}, /* Delete word left */ {vt100_word_left, key_word_left}, /* Word left */ {vt100_word_right, key_word_right}, /* Word right */ {"\027", key_ctrl_w}, /* Same as key_meta_backspace */ {"\020", key_ctrl_p}, /* Same as key_up_arr */ {"\016", key_ctrl_n}, /* Same as key_down_arr */ {"\033\144", key_meta_d}, /* Delete word right */ {"\030", key_ctrl_x}, /* Terminate application */ {NULL, NULL}}; void vt100_do_cmd(int idx) { if (idx < VT100_MAX_KEYS) vt100_cmd_list[idx].func(); } struct vt100_cmds * vt100_get_cmds(void) { return vt100_cmd_list; } static int vt100_find_cmd(char *buf, unsigned int size) { struct vt100_cmds *cmd; size_t cmdlen; int i; for (i = 0, cmd = vt100_get_cmds(); cmd->str; cmd++, i++) { cmdlen = strnlen(cmd->str, VT100_BUF_SIZE); if ((size == cmdlen) && !strncmp(buf, cmd->str, cmdlen)) return i; } return VT100_DONE; } int vt100_parse_input(struct cli_vt100 *vt, uint8_t c) { uint32_t size; RTE_ASSERT(vt != NULL); if ((vt->bufpos == VT100_INITIALIZE) || (vt->bufpos >= VT100_BUF_SIZE)) { vt->state = VT100_INIT; vt->bufpos = 0; } vt->buf[vt->bufpos++] = c; size = vt->bufpos; switch (vt->state) { case VT100_INIT: if (c == vt100_escape) vt->state = VT100_ESCAPE; else { vt->bufpos = VT100_INITIALIZE; return vt100_find_cmd(vt->buf, size); } break; case VT100_ESCAPE: if (c == vt100_open_square) vt->state = VT100_ESCAPE_CSI; else if (c >= '0' && c <= vt100_del) { vt->bufpos = VT100_INITIALIZE; return vt100_find_cmd(vt->buf, size); } break; case VT100_ESCAPE_CSI: if (c >= '@' && c <= '~') { vt->bufpos = VT100_INITIALIZE; return vt100_find_cmd(vt->buf, size); } break; default: vt->bufpos = VT100_INITIALIZE; break; } return VT100_CONTINUE; } struct cli_vt100 * vt100_setup(void) { struct cli_vt100 *vt; vt = calloc(1, sizeof(struct cli_vt100)); if (!vt) return NULL; vt->bufpos = -1; return vt; } void vt100_free(struct cli_vt100 *vt) { if (vt) free(vt); } ================================================ FILE: lib/cli/meson.build ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2020-2026> Intel Corporation sources = files( 'cli_auto_complete.c', 'cli.c', 'cli_cmap.c', 'cli_cmds.c', 'cli_env.c', 'cli_file.c', 'cli_gapbuf.c', 'cli_help.c', 'cli_history.c', 'cli_input.c', 'cli_map.c', 'cli_scrn.c', 'cli_search.c', 'cli_vt100.c') libcli = library('cli', sources, dependencies: [common, utils, dpdk]) cli = declare_dependency(link_with: libcli, include_directories: include_directories('.')) ================================================ FILE: lib/common/cksum.c ================================================ /*- * Copyright(c) <2014-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ /* includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(RTE_VER_MAJOR) && (RTE_VER_MAJOR < 2) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "copyright_info.h" #include "pg_compat.h" #include "port_config.h" #include "pg_inet.h" #include "cksum.h" /** * cksum - Compute a 16 bit ones complement checksum value. * * DESCRIPTION * A wrapper routine to compute the complete 16 bit checksum value for a given * piece of memory, when the data is contiguous. The value is a previous * checksum value to allow the user to build a checksum using different parts * of memory. * * \is * \i Pointer to the data buffer to be checksumed. * \i Number of bytes to checksum. * \i Previous checksum value else give 0. * \ie * * RETURNS: 16 bit checksum value. * * ERRNO: N/A */ uint16_t cksum(void *pBuf, int32_t size, uint32_t cksum) { return cksumDone(cksumUpdate(pBuf, size, cksum)); } /** * cksumUpdate - Calaculate an 16 bit checksum and return the 32 bit value * * DESCRIPTION * Will need to call pktgen_cksumDone to finish computing the checksum. The * value is from any previous checksum call. The routine will not fold the upper * 16 bits into the 32 bit checksum. The pktgen_cksumDone routine will do the * folding of the upper 16 bits into a 16 bit checksum. * * \is * \i the pointer to the data to be checksumed. * \i the number of bytes to include in the checksum calculation. * \i the initial starting checksum value allowing the developer to * ckecksum different pieces of memory to get a final value. * \ie * * RETURNS: unsigned 32 bit checksum value. * * ERRNO: N/A */ uint32_t cksumUpdate(void *pBuf, int32_t size, uint32_t cksum) { uint32_t nWords; uint16_t *pWd = (uint16_t *)pBuf; for (nWords = (size >> 5); nWords > 0; nWords--) { cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; cksum += *pWd++; } /* handle the odd number size */ for (nWords = (size & 0x1f) >> 1; nWords > 0; nWords--) cksum += *pWd++; /* Handle the odd byte length */ if (size & 1) cksum += *pWd & htons(0xFF00); return cksum; } /** * cksumDone - Finish up the ckecksum value by folding the checksum. * * DESCRIPTION * Fold the carry bits back into the checksum value to complete the 16 bit * checksum value. This routine is called after all of the pktgen_cksumUpdate * calls have been completed and the 16bit result is required. * * \is * \i the initial 32 bit checksum and returns a 16bit folded value. * \ie * * RETURNS: 16 bit checksum value. * * ERRNO: N/A */ uint16_t cksumDone(uint32_t cksum) { /* Fold at most twice */ cksum = (cksum & 0xFFFF) + (cksum >> 16); cksum = (cksum & 0xFFFF) + (cksum >> 16); return ~((uint16_t)cksum); } /** * pseudoChecksum - Compute the Pseudo Header checksum. * * DESCRIPTION * The pseudo header checksum is done in IP for TCP/UDP by computing the values * passed into the routine into a return value, which is a 32bit checksum. The * 32bit value contains any carry bits and will be added to the final value. * * \is * \i Source IP address. * \i Destination IP address. * \i The protocol type. * \i Length of the data packet. * \i Previous checksum value if needed. * \ie * * RETURNS: 32bit checksum value. * * ERRNO: N/A */ uint32_t pseudoChecksum(uint32_t src, uint32_t dst, uint16_t pro, uint16_t len, uint32_t sum) { /* Compute the Pseudo Header checksum */ return sum + (src & 0xFFFF) + (src >> 16) + (dst & 0xFFFF) + (dst >> 16) + ntohs(len) + ntohs(pro); } /** * pseudoIPv6Checksum - Compute the Pseudo Header checksum. * * DESCRIPTION * The pseudo header checksum is done in IP for TCP/UDP by computing the values * passed into the routine into a return value, which is a 32bit checksum. The * 32bit value contains any carry bits and will be added to the final value. * * \is * \i Source IP address pointer. * \i Destination IP address pointer. * \i The protocol type. * \i Length of the data packet TCP data. * \i Previous checksum value if needed. * \ie * * RETURNS: 32bit checksum value. * * ERRNO: N/A */ uint32_t pseudoIPv6Checksum(uint16_t *src, uint16_t *dst, uint8_t next_hdr, uint32_t total_len, uint32_t sum) { uint32_t len = htonl(total_len), i; sum = (sum + (uint16_t)next_hdr + (len & 0xFFFF) + (len >> 16)); for (i = 0; i < 8; i++) { sum += src[i]; sum += dst[i]; } return sum; } ================================================ FILE: lib/common/cksum.h ================================================ /*- * Copyright(c) <2014-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef __CKSUM_H #define __CKSUM_H #ifdef __cplusplus extern "C" { #endif uint16_t cksum(void *pBuf, int32_t size, uint32_t cksum); uint32_t cksumUpdate(void *pBuf, int32_t size, uint32_t cksum); uint16_t cksumDone(uint32_t cksum); uint32_t pseudoChecksum(uint32_t src, uint32_t dst, uint16_t proto, uint16_t len, uint32_t cksum); uint32_t pseudoIPv6Checksum(uint16_t *src, uint16_t *dst, uint8_t next_hdr, uint32_t total_len, uint32_t sum); #ifdef __cplusplus } #endif #endif /* __CKSUM_H */ ================================================ FILE: lib/common/cmdline_parse_args.c ================================================ /*- * Copyright(c) <2015-2026>-2016 Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #ifndef __linux__ #include #endif #include #include "cmdline_parse.h" #include "cmdline_parse_args.h" struct cmdline_token_ops cmdline_token_args_ops = { .parse = cmdline_parse_args, .complete_get_nb = NULL, .complete_get_elt = NULL, .get_help = cmdline_get_help_args, }; static char orig_cmdline[256]; void cmdline_args_free(int argc, char **argv) { int i; if (argc <= 0) return; for (i = 0; i < argc; i++) if (argv[i]) free(argv[i]); } int cmdline_parse_args(cmdline_parse_token_hdr_t *tk __rte_unused, const char *buf, void *res, unsigned tk_len __rte_unused) { unsigned int token_len = 0, len = 0; char args_str[XARGS_TOKEN_SIZE + 1]; cmdline_args_t *pl; if (!buf) buf = " "; if (res == NULL) return -1; pl = res; pl->argc = 1; /* Leave the zero entry empty */ snprintf(orig_cmdline, sizeof(orig_cmdline), "%s", buf); do { while (!cmdline_isendoftoken(buf[token_len]) && (token_len < XARGS_TOKEN_SIZE)) { token_len++; len++; } if (token_len == 0) break; if (token_len >= XARGS_TOKEN_SIZE) return -1; snprintf(args_str, token_len + 1, "%s", buf); buf += token_len; if (*buf == ' ') { buf++; len++; } token_len = 0; pl->argv[pl->argc++] = strdup(args_str); } while ((*buf != '\n') && (pl->argc < XARGS_MAX_TOKENS)); pl->argv[pl->argc] = NULL; pl->cmdline = orig_cmdline; return len; } int cmdline_get_help_args(__rte_unused cmdline_parse_token_hdr_t *tk, char *dstbuf, unsigned int size) { int ret; ret = snprintf(dstbuf, size, "argc/argv list of arguments"); if (ret < 0) return -1; return 0; } ================================================ FILE: lib/common/cmdline_parse_args.h ================================================ /*- * Copyright(c) <2015-2026>-2016 Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #ifndef _PARSE_XARGS_H_ #define _PARSE_XARGS_H_ #ifdef __cplusplus extern "C" { #endif /* size of a parsed string */ #define XARGS_TOKEN_SIZE 256 #define XARGS_MAX_TOKENS 32 typedef struct cmdline_args { char *cmdline; int argc; char *argv[XARGS_MAX_TOKENS + 1]; } cmdline_args_t; struct cmdline_token_args { struct cmdline_token_hdr hdr; struct cmdline_args args; }; typedef struct cmdline_token_args cmdline_parse_token_args_t; extern struct cmdline_token_ops cmdline_token_args_ops; int cmdline_parse_args(cmdline_parse_token_hdr_t *tk, const char *srcbuf, void *res, unsigned tk_len); int cmdline_get_help_args(cmdline_parse_token_hdr_t *tk, char *dstbuf, unsigned int size); #define TOKEN_ARGS_INITIALIZER(structure, field) \ { \ /* hdr */ \ { \ &cmdline_token_args_ops, /* ops */ \ offsetof(structure, field), /* offset */ \ }, /* args */ \ { \ 0, \ }, \ } void cmdline_args_free(int argc, char **argv); #ifdef __cplusplus } #endif #endif /* _PARSE_XARGS_H_ */ ================================================ FILE: lib/common/copyright_info.c ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010-2024 by Keith Wiles @ intel.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "copyright_info.h" #define COPYRIGHT_MSG_SHORT "Copyright(c) <2010-2026>, Intel Corporation" #define COPYRIGHT_MSG COPYRIGHT_MSG_SHORT ". All rights reserved." #define POWERED_BY_DPDK "Powered by" /** * * pg_print_copyright - Print out the copyright notices. * * DESCRIPTION * Output the copyright notices. * * RETURNS: N/A * * SEE ALSO: */ void print_copyright(const char *appname, const char *created_by) { printf("\n*** %s\n", COPYRIGHT_MSG); printf("*** %s created by: %s >>> %s %s <<<\n\n", appname, created_by, POWERED_BY_DPDK, rte_version()); } /** * Function returning string for Copyright message." * @return * string */ const char * copyright_msg(void) { return COPYRIGHT_MSG; } /** * Function returning short string for Copyright message." * @return * string */ const char * copyright_msg_short(void) { return COPYRIGHT_MSG_SHORT; } /** * Function returning string for Copyright message." * @return * string */ const char * powered_by(void) { return POWERED_BY_DPDK; } ================================================ FILE: lib/common/copyright_info.h ================================================ /*- * Copyright(c) <2014-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2013-2024 by Keith Wiles @ intel.com */ #ifndef _COPYRIGHT_INFO_H #define _COPYRIGHT_INFO_H #ifdef __cplusplus extern "C" { #endif /** * Print out a copyright string * * @param appname * The name of the application * @param created_by * The created_by string */ void print_copyright(const char *appname, const char *created_by); /** * Function returning string for Copyright message. * @return * string */ const char *copyright_msg(void); /** * Function returning short string for Copyright message. * @return * string */ const char *copyright_msg_short(void); /** * Function returning string for Copyright message. * @return * string */ const char *powered_by(void); #ifdef __cplusplus } #endif #endif /* _COPYRIGHT_INFO_H */ ================================================ FILE: lib/common/coreinfo.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright (c) 2023-2026 Intel Corporation */ #include #include #include #include #include #include #include #include "coreinfo.h" #define MAX_LINE_SZ 4096 #define CI_SCT_FORMAT "S:%04d,C:%04d,T:%04d" /* socket_id/core_id/thread_id */ #define CI_LCORE_FORMAT "L:%04d" /* logical core id */ #define CI_NUM_LCORES "num_lcores" #define CI_NUM_CORES "num_cores" #define CI_NUM_CPU_CORES "num_cpu_cores" #define CI_NUM_SOCKETS "num_sockets" #define CI_NUM_THREADS "num_threads" #define CI_NUM_SIBLINGS "num_siblings" #define CI_MODEL_NAME "model_name" #define CI_LOGICAL_ID "processor" #define CI_MODEL_ID "model name" #define CI_SOCKET_ID "physical id" #define CI_CORE_ID "core id" #define CI_CPU_CORES_ID "cpu cores" #define CI_SIBLINGS_ID "siblings" #define CI_TERMINAL_ID "\n" typedef struct { uint16_t socket_id; uint16_t core_id; uint16_t thread_id; } lcore_t; typedef struct { char *model_name; /**< CPU model string */ uint16_t max_socket_id; /**< Max sockets */ uint16_t max_threads; /**< Max threads per socket */ coreinfo_t core; /**< Core information */ hmap_t *map; /**< coreinfo map */ } coreinfo_data_t; static coreinfo_data_t coreinfo_data, *cid = &coreinfo_data; typedef void (*do_line_fn)(const char *line); typedef unsigned (*getter_fn)(void); typedef void (*setter_fn)(unsigned new_val); static char * get_value(char *line) { if (*line == '\0' || *line == '\n') return line; line[strlen(line) - 1] = '\0'; /* remove newline */ while (*line != ':') line++; *line++ = '\0'; while (*line == ' ') line++; return line; } static void set_lcore_id(const char *line) { cid->core.lcore_id = atoi(line); } static void set_socket_id(const char *line) { cid->core.socket_id = atoi(line); } static void set_max_socket_id(const char *line) { uint16_t socket_id; socket_id = atoi(line); if (socket_id > cid->max_socket_id) cid->max_socket_id = socket_id; } static void set_core_id(const char *line) { cid->core.core_id = atoi(line); } static void set_model_name(const char *line) { if (!cid->model_name) cid->model_name = strdup(line); } static void set_siblings(const char *line) { if (hmap_kvp_lookup(cid->map, NULL, CI_NUM_SIBLINGS) != NULL) return; if (hmap_add_value(cid->map, NULL, CI_NUM_SIBLINGS, (uint16_t)atoi(line)) < 0) printf("%s: Failed to add %s to map\n", __func__, CI_NUM_SIBLINGS); } static void set_cpu_cores(const char *line) { if (hmap_kvp_lookup(cid->map, NULL, CI_NUM_CPU_CORES) != NULL) return; if (hmap_add_value(cid->map, NULL, CI_NUM_CPU_CORES, (uint16_t)atoi(line)) < 0) printf("%s: Failed to add %s to map\n", __func__, CI_NUM_CPU_CORES); } static void lcore_terminator(const char *unused __attribute__((unused))) { coreinfo_t *ci; char buffer[64]; uint16_t num_cpu_cores = 0, num_sockets = 0, val = 0; ci = calloc(1, sizeof(coreinfo_t)); if (!ci) { printf("%s: Failed to allocate memory for core info\n", __func__); return; } ci->socket_id = cid->core.socket_id; ci->core_id = cid->core.core_id; ci->lcore_id = cid->core.lcore_id; hmap_get_value(cid->map, NULL, CI_NUM_CPU_CORES, &num_cpu_cores); hmap_get_value(cid->map, NULL, CI_NUM_SOCKETS, &num_sockets); if (num_sockets == 0) num_sockets = 1; ci->thread_id = (cid->core.lcore_id / num_cpu_cores) / num_sockets; cid->core.thread_id = ci->thread_id; if (cid->core.thread_id > cid->max_threads) cid->max_threads = cid->core.thread_id; snprintf(buffer, sizeof(buffer), CI_LCORE_FORMAT, cid->core.lcore_id); hmap_add_value(cid->map, NULL, buffer, (void *)ci); snprintf(buffer, sizeof(buffer), CI_SCT_FORMAT, cid->core.socket_id, cid->core.core_id, cid->core.thread_id); hmap_add_value(cid->map, NULL, buffer, cid->core.lcore_id); if (hmap_get_value(cid->map, NULL, CI_NUM_LCORES, &val) < 0) val = 0; val++; hmap_update_value(cid->map, NULL, CI_NUM_LCORES, val); } static void ignore_line(const char *line __attribute__((unused))) { } static do_line_fn get_matching_action(const char *line) { // clang-format off static struct action { const char *desc; do_line_fn fn; } actions[] = { {CI_LOGICAL_ID, set_lcore_id}, /* lcore ID */ {CI_SOCKET_ID, set_socket_id}, /* Socket ID */ {CI_CORE_ID, set_core_id}, /* Core ID */ {CI_MODEL_ID, set_model_name}, /* Model Name */ {CI_SIBLINGS_ID, set_siblings}, /* Number of siblings */ {CI_CPU_CORES_ID, set_cpu_cores}, /* Number of CPU cores */ {CI_TERMINAL_ID, lcore_terminator}, /* move to next lcore ID */ {NULL, NULL} }; // clang-format on struct action *action; for (action = actions; action->fn != NULL; action++) { if (!strncmp(action->desc, line, strlen(action->desc))) return action->fn; } return ignore_line; } coreinfo_t * coreinfo_get(uint16_t lcore_id) { coreinfo_t *ci = NULL; char buffer[64]; uint16_t num_lcores = 0, num_sockets = 0, num_cores = 0, num_threads = 0; hmap_get_value(cid->map, NULL, CI_NUM_LCORES, &num_lcores); hmap_get_value(cid->map, NULL, CI_NUM_CPU_CORES, &num_cores); hmap_get_value(cid->map, NULL, CI_NUM_SOCKETS, &num_sockets); hmap_get_value(cid->map, NULL, CI_NUM_THREADS, &num_threads); snprintf(buffer, sizeof(buffer), CI_LCORE_FORMAT, lcore_id); if (hmap_get_value(cid->map, NULL, buffer, (void **)&ci) < 0) ci = NULL; return ci; } static int coreinfo_create(void) { FILE *f; char *line, *value; size_t line_sz = MAX_LINE_SZ; cid->map = hmap_create("core_map", 0, NULL); if (cid->map == NULL) printf("%s: Failed to create lcore map\n", __func__); line = malloc(line_sz); if (!line) { printf("%s: Failed to allocate memory for line\n", __func__); return -1; } if ((f = fopen(PROC_CPUINFO, "r")) == NULL) { fprintf(stderr, "Cannot open %s on this system\n", PROC_CPUINFO); free(line); hmap_destroy(cid->map); return -1; } /* find number of sockets */ do { if (getline(&line, &line_sz, f) == EOF) break; value = get_value(line); if (value != NULL && strncmp(line, CI_SOCKET_ID, strlen(CI_SOCKET_ID)) == 0) set_max_socket_id(value); } while (1); hmap_add_value(cid->map, NULL, CI_NUM_SOCKETS, (uint16_t)(cid->max_socket_id + 1)); rewind(f); /* process the rest of the information */ do { if (getline(&line, &line_sz, f) == EOF) break; value = get_value(line); if (value != NULL) get_matching_action(line)(value); } while (1); hmap_add_value(cid->map, NULL, CI_NUM_THREADS, (uint16_t)(cid->max_threads + 1)); free(line); fclose(f); return 0; } static uint16_t coreinfo_cnt(ci_type_t typ) { uint16_t cnt = 0; switch (typ) { case CI_NUM_LCORES_TYPE: hmap_get_value(cid->map, NULL, CI_NUM_LCORES, &cnt); break; case CI_NUM_CORES_TYPE: hmap_get_value(cid->map, NULL, CI_NUM_CORES, &cnt); break; case CI_NUM_SOCKETS_TYPE: hmap_get_value(cid->map, NULL, CI_NUM_SOCKETS, &cnt); break; case CI_NUM_THREADS_TYPE: hmap_get_value(cid->map, NULL, CI_NUM_THREADS, &cnt); break; case CI_NUM_SIBLINGS_TYPE: hmap_get_value(cid->map, NULL, CI_NUM_SIBLINGS, &cnt); break; case CI_NUM_CPU_CORES_TYPE: hmap_get_value(cid->map, NULL, CI_NUM_CPU_CORES, &cnt); default: break; } return cnt; } uint16_t coreinfo_lcore_cnt(void) { return coreinfo_cnt(CI_NUM_LCORES_TYPE); } uint16_t coreinfo_core_cnt(void) { return coreinfo_cnt(CI_NUM_CORES_TYPE); } uint16_t coreinfo_socket_cnt(void) { return coreinfo_cnt(CI_NUM_SOCKETS_TYPE); } uint16_t coreinfo_thread_cnt(void) { return coreinfo_cnt(CI_NUM_THREADS_TYPE); } uint16_t coreinfo_siblings_cnt(void) { return coreinfo_cnt(CI_NUM_SIBLINGS_TYPE); } uint16_t coreinfo_cpu_cores_cnt(void) { return coreinfo_cnt(CI_NUM_CPU_CORES_TYPE); } RTE_INIT(coreinfo_init) { memset(&coreinfo_data, 0, sizeof(coreinfo_data)); coreinfo_create(); } ================================================ FILE: lib/common/coreinfo.h ================================================ /*- * Copyright(c) <2014-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2014 by Keith Wiles @ intel.com */ #ifndef __COREINFO_H #define __COREINFO_H #include #ifdef __cplusplus extern "C" { #endif #define PROC_CPUINFO "/proc/cpuinfo" // clang-format off typedef enum { CI_NUM_LCORES_TYPE, CI_NUM_SOCKETS_TYPE, CI_NUM_CORES_TYPE, CI_NUM_THREADS_TYPE, CI_NUM_SIBLINGS_TYPE, CI_NUM_CPU_CORES_TYPE } ci_type_t; // clang-format on typedef struct coreinfo_s { __extension__ union { struct { uint16_t lcore_id; /* Logical core ID */ uint16_t core_id; /* Physical CPU core ID */ uint16_t socket_id; /* CPU socket ID */ uint16_t thread_id; /* Hyper-thread ID */ }; uint64_t word; }; } coreinfo_t; coreinfo_t *coreinfo_get(uint16_t lcore_id); uint16_t coreinfo_lcore_cnt(void); uint16_t coreinfo_core_cnt(void); uint16_t coreinfo_socket_cnt(void); uint16_t coreinfo_thread_cnt(void); uint16_t coreinfo_siblings_cnt(void); uint16_t coreinfo_cpu_cores_cnt(void); #ifdef __cplusplus } #endif #endif /*_COREINFO_H */ ================================================ FILE: lib/common/lscpu.c ================================================ /*- * Copyright(c) <2014-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2014 by Keith Wiles @ intel.com */ #include #include #include #include #include #include #include "utils.h" #include "lscpu.h" static lscpu_t *lscpu; static __inline__ void num_cpus(__attribute__((unused)) action_t *act, char *line) { lscpu->num_cpus = atoi(line); } static __inline__ void threads_per_core(__attribute__((unused)) action_t *act, char *line) { lscpu->threads_per_core = atoi(line); } static __inline__ void cores_per_socket(__attribute__((unused)) action_t *act, char *line) { lscpu->cores_per_socket = atoi(line); } static __inline__ void numa_nodes(__attribute__((unused)) action_t *act, char *line) { lscpu->numa_nodes = atoi(line); } static __inline__ void cpu_mhz(action_t *act, char *line) { if ((act->flags & ONLY_ONCE_FLAG) == 0) { lscpu->cpu_mhz = pg_strdupf(lscpu->cpu_mhz, line); act->flags |= ONLY_ONCE_FLAG; } } static void numa_nodeX_cpus(action_t *act, char *line) { int n, i; char *arr[32], *p; int first, last; memset(arr, 0, sizeof(arr)); n = pg_strparse(line, ",", arr, (sizeof(arr) / sizeof(char *))); if (n > 0) for (i = 0; i < n; i++) { if (arr[i] == NULL) continue; if ((p = strchr(arr[i], '-'))) p++; /* Point to second port number. */ else p = arr[i]; /* Only one port number, make the same port. */ first = atoi(arr[i]); last = atoi(p); while (first <= last) lscpu->numa_cpus[act->arg][first++] = 1; } } static __inline__ void cache_size(action_t *act, char *line) { if ((act->flags & ONLY_ONCE_FLAG) == 0) { lscpu->cache_size = pg_strdupf(lscpu->cache_size, line); act->flags |= ONLY_ONCE_FLAG; } } static __inline__ void model_name(action_t *act, char *line) { if ((act->flags & ONLY_ONCE_FLAG) == 0) { lscpu->model_name = pg_strdupf(lscpu->model_name, line); act->flags |= ONLY_ONCE_FLAG; } } static __inline__ void cpu_flags(action_t *act, char *line) { if ((act->flags & ONLY_ONCE_FLAG) == 0) { lscpu->cpu_flags = pg_strdupf(lscpu->cpu_flags, line); act->flags |= ONLY_ONCE_FLAG; } } static action_t * lscpu_match_action(char *line) { static action_t actions[] = {{"CPU(s)", num_cpus, 0, 0}, {"Thread(s) per core", threads_per_core, 0, 0}, {"Core(s) per socket", cores_per_socket, 0, 0}, {"NUMA node(s)", numa_nodes, 0, 0}, {"CPU MHz", cpu_mhz, 0, 0}, {"NUMA node0 CPU(s)", numa_nodeX_cpus, 0, 0}, {"NUMA node1 CPU(s)", numa_nodeX_cpus, 1, 0}, {"NUMA node2 CPU(s)", numa_nodeX_cpus, 2, 0}, {"NUMA node3 CPU(s)", numa_nodeX_cpus, 3, 0}, {NULL, NULL, 0, 0}}; action_t *act; for (act = actions; act->str != NULL; ++act) if (strncmp(act->str, line, strlen(act->str)) == 0) break; return act; } static action_t * cpu_proc_match_action(char *line) { static action_t actions[] = {{"cache size", cache_size, 0, 0}, {"model name", model_name, 0, 0}, {"flags", cpu_flags, 0, 0}, {NULL, NULL, 0, 0}}; action_t *act; for (act = actions; act->str != NULL; ++act) if (strncmp(act->str, line, strlen(act->str)) == 0) break; return act; } static void lscpu_info_get(const char *lscpu_path) { FILE *f = popen(lscpu_path, "r"); char *line = NULL, *p; action_t *act; size_t line_size; if (f == NULL) { printf("Unable to run 'lscpu' command\n"); return; } line_size = 0; while (getline(&line, &line_size, f) > 0) { line[strlen(line) - 1] = '\0'; act = lscpu_match_action(line); if (act->str) { p = strchr(line, ':'); if (p) p++; act->func(act, pg_strtrim(p)); } } pclose(f); free(line); } static void cpu_proc_info(const char *proc_path) { FILE *f = popen(proc_path, "r"); char *line = NULL, *p; action_t *act; size_t line_size; if (f == NULL) { printf("Unable to run 'CPU proc' command\n"); return; } line_size = 0; while (getline(&line, &line_size, f) > 0) { line[strlen(line) - 1] = '\0'; act = cpu_proc_match_action(line); if (act->str) { p = strchr(line, ':'); if (p) p++; act->func(act, pg_strtrim(p)); } } pclose(f); free(line); } lscpu_t * lscpu_info(const char *lscpu_path, const char *proc_path) { if (lscpu == NULL) lscpu = calloc(1, sizeof(lscpu_t)); if (lscpu_path == NULL) lscpu_path = LSCPU_PATH; if (proc_path == NULL) proc_path = CPU_PROC_PATH; lscpu_info_get(lscpu_path); cpu_proc_info(proc_path); return lscpu; } ================================================ FILE: lib/common/lscpu.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2014-2018 by Keith Wiles @ intel.com */ #ifndef __LSCPU_H #define __LSCPU_H #ifdef __cplusplus extern "C" { #endif typedef struct action_s { const char *str; void (*func)(struct action_s *action, char *line); int arg; int flags; } action_t; #define ONLY_ONCE_FLAG 0x0001 #define MAX_LINE_SIZE 1024 typedef struct { int num_cpus; int threads_per_core; int cores_per_socket; int numa_nodes; char *cpu_mhz; char *model_name; char *cpu_flags; char *cache_size; short numa_cpus[RTE_MAX_NUMA_NODES][RTE_MAX_LCORE]; } lscpu_t; #define LSCPU_PATH "/usr/bin/lscpu" #define CPU_PROC_PATH "cat /proc/cpuinfo" lscpu_t *lscpu_info(const char *lscpu_path, const char *proc_path); #ifdef __cplusplus } #endif #endif ================================================ FILE: lib/common/meson.build ================================================ sources = files( 'copyright_info.c', 'port_config.c', 'cmdline_parse_args.c', 'lscpu.c', 'utils.c', 'coreinfo.c', 'pg_strings.c', 'cksum.c', 'pg_inet.c', ) headers = files( 'pg_inet.h', 'port_config.h', 'cmdline_parse_args.h', 'lscpu.h', 'utils.h', 'coreinfo.h', 'pg_strings.h', 'cksum.h', ) libcommon = library('common', sources, dependencies: [hmap, dpdk]) common = declare_dependency(link_with: libcommon, include_directories: include_directories('.')) ================================================ FILE: lib/common/pg_compat.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /** * @file * * Compat file for pktgen */ #ifndef PG_COMPAT_H_ #define PG_COMPAT_H_ #include #include #include #ifdef __cplusplus extern "C" { #endif #define PG_JUMBO_ETHER_MTU 9216 // 9K total size of the Ethernet jumbo frame #define PG_JUMBO_DATAROOM_SIZE 9000 // 9K data room size in the Ethernet jumbo frame #define PG_JUMBO_HEADROOM_SIZE \ (PG_JUMBO_ETHER_MTU - \ PG_JUMBO_DATAROOM_SIZE) // 9K headroom size in the Ethernet jumbo frame #ifndef RTE_ETHDEV_QUEUE_STAT_CNTRS #define RTE_ETHDEV_QUEUE_STAT_CNTRS 16 #endif static __inline__ int pg_socket_id(void) { int sid = rte_socket_id(); return (sid == -1) ? 0 : sid; } static __inline__ int pg_eth_dev_socket_id(int pid) { int sid = rte_eth_dev_socket_id(pid); return (sid == -1) ? 0 : sid; } #ifdef __cplusplus } #endif #endif /* PG_COMPAT_H_ */ ================================================ FILE: lib/common/pg_delay.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /** * @file * * Compat file for pktgen */ #ifndef __DELAY_H_ #define __DELAY_H_ #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif /* __DELAY_H_ */ ================================================ FILE: lib/common/pg_ether.h ================================================ /*- * BSD LICENSE * * Copyright(c) <2010-2026>-2014 Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #ifndef _ETHER_H_ #define _ETHER_H_ /** * @file * * Ethernet Helpers in RTE */ #ifdef __cplusplus extern "C" { #endif #include #ifdef __cplusplus } #endif #endif /* _ETHER_H_ */ ================================================ FILE: lib/common/pg_inet.c ================================================ /*- * BSD LICENSE * * Copyright 2016 6WIND S.A. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #include #include #include "pg_inet.h" ================================================ FILE: lib/common/pg_inet.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _PG_INET_H #define _PG_INET_H #ifdef RTE_MACHINE_CPUFLAG_SSE4_2 #include #else #include #endif #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define IPv4_VERSION 4 #define IPv6_VERSION 6 #define PG_IPADDR_V4 0x01 #define PG_IPADDR_V6 0x02 #define PG_IPADDR_NETWORK 0x04 #define PG_INADDRSZ 4 #define PG_IN6ADDRSZ 16 #define PG_PREFIXMAX 128 #define PG_V4PREFIXMAX 32 struct pg_ipaddr { uint8_t family; union { struct in_addr ipv4; struct rte_ipv6_addr ipv6; }; unsigned int prefixlen; /* in case of network only */ }; #define PG_ISFRAG(off) ((off) & (PG_OFF_MF | PG_OFF_MASK)) #define PG_OFF_MASK 0x1fff #define PG_OFF_MF 0x2000 #define PG_OFF_DF 0x4000 /****************************************************************************** * struct rte_ipv4_hdr.proto values in the IP Header. * 1 ICMP Internet Control Message [RFC792] * 2 IGMP Internet Group Management [RFC1112] * 4 IP IP in IP (encapsulation) [RFC2003] * 6 TCP Transmission Control [RFC793] * 17 UDP User Datagram [RFC768,JBP] * 41 IPv6 Ipv6 [Deering] * 43 IPv6-Route Routing Header for IPv6 [Deering] * 44 IPv6-Frag Fragment Header for IPv6 [Deering] * 47 GRE Generic Routing Encapsulation [RFC2784,2890] * 58 IPv6-ICMP ICMP for IPv6 [RFC1883] * 59 IPv6-NoNxt No Next Header for IPv6 [RFC1883] * 60 IPv6-Opts Destination Options for IPv6 [RFC1883] */ #define PG_IPPROTO_NONE 0 #define PG_IPPROTO_IP IPPROTO_IP #define PG_IPPROTO_ICMP IPPROTO_ICMP #define PG_IPPROTO_IGMP IPPROTO_IGMP #define PG_IPPROTO_IPV4 IPPROTO_IPV4 #define PG_IPPROTO_TCP IPPROTO_TCP #define PG_IPPROTO_UDP IPPROTO_UDP #define PG_IPPROTO_IPV6 IPPROTO_IPV6 #define PG_IPPROTO_IPV6_ROUTE 43 #define PG_IPPROTO_IPV6_FRAG 44 #define PG_IPPROTO_GRE IPPROTO_GRE #define PG_IPPROTO_ICMPV6 IPPROTO_ICMPV6 #define PG_IPPROTO_IPV6_ICMP IPPROTO_ICMPV6 #define PG_IPPROTO_IPV6_NONXT 59 #define PG_IPPROTO_IPV6_OPTS 60 #define PG_IPPROTO_RAW IPPROTO_RAW #define PG_IPPROTO_USR_DEF 255 #define PG_IPPROTO_MAX 256 #define PG_IPPROTO_L4_GTPU_PORT 2152 // clang-format off #define IPv4(a, b, c, d) \ ((uint32_t)(((a) & 0xff) << 24) | (((b) & 0xff) << 16) | (((c) & 0xff) << 8) | ((d) & 0xff)) // clang-format on /************************************************************************* * * GTP -U Header * * +---+----+-+-+-+-+--+------------+-----------------+------------------+ * | |8-6 |5|4|3|2|1 | 8-15 | 16-23 | 24-31 | * +---+----+-+-+-+-+--+------------+-----------------+------------------+ * |0 |Veri|P|*|E|S|PN| Message | | * | |son | | | | | | Type | Total length | * +---+----+-+-+-+-+--+------------+------------------------------------+ * |32 | TEID (only present if T=1) | * | | | * +---+----------------------------+-----------------+------------------+ * |64 | Sequence number |N-PDU number |Next extension | * | | | |header type | * +---+----------------------------+-----------------+------------------+ ***************************************************************************/ #define GTPu_VERSION 0x20 #define GTPu_PT_FLAG 0x10 #define GTPu_E_FLAG 0x04 #define GTPu_S_FLAG 0x02 #define GTPu_PN_FLAG 0x01 typedef struct gtpuHdr_s { uint8_t version_flags; uint8_t msg_type; uint16_t tot_len; uint32_t teid; // uint16_t seq_no; /**< Optional fields if E, S or PN flags set */ // uint8_t npdu_no; // uint8_t next_ext_hdr_type; } __attribute__((__packed__)) gtpuHdr_t; /* IP overlay header for the pseudo header */ typedef struct ipOverlay_s { uint32_t node[2]; uint8_t pad0; /* overlays ttl */ uint8_t proto; /* Protocol type */ uint16_t len; /* Protocol length, overlays cksum */ uint32_t src; /* Source address */ uint32_t dst; /* Destination address */ } __attribute__((__packed__)) ipOverlay_t; typedef struct ipv6Overlay_s { uint8_t saddr[16]; uint8_t daddr[16]; uint32_t tcp_length; uint16_t zero; uint8_t pad; uint8_t next_header; } __attribute__((__packed__)) ipv6Overlay_t; typedef unsigned int seq_t; /* TCP Sequence type */ /* The UDP/IP Pseudo header */ typedef struct udpip_s { ipOverlay_t ip; /* IPv4 overlay header */ struct rte_udp_hdr udp; /* UDP header for protocol */ } __attribute__((__packed__)) udpip_t; /* The GTP-U/UDP/IP Pseudo header */ typedef struct gtpuUdpIp_s { ipOverlay_t ip; /* IPv4 overlay header */ struct rte_udp_hdr udp; /* UDP header for protocol */ gtpuHdr_t gtpu; /* GTP-U header */ } __attribute__((__packed__)) gtpuUdpIp_t; /* The UDP/IPv6 Pseudo header */ typedef struct udpipv6_s { ipv6Overlay_t ip; /* IPv6 overlay header */ struct rte_udp_hdr udp; /* UDP header for protocol */ } __attribute__((__packed__)) udpipv6_t; typedef struct tcp_flags_s { const char *name; uint16_t bit; } tcp_flags_t; /* TCP Flag bits */ enum { RS0_FLAG = 0x800, RS1_FLAG = 0x400, RS2_FLAG = 0x200, RS3_FLAG = 0x100, CWR_FLAG = 0x080, ECE_FLAG = 0x040, URG_FLAG = 0x020, ACK_FLAG = 0x010, PSH_FLAG = 0x008, RST_FLAG = 0x004, SYN_FLAG = 0x002, FIN_FLAG = 0x001 }; // clang-format off #define TCP_FLAGS_LIST \ { \ {"rs0", RS0_FLAG}, {"rs1", RS1_FLAG}, {"rs2", RS2_FLAG}, {"rs3", RS3_FLAG}, \ {"cwr", CWR_FLAG}, {"ece", ECE_FLAG}, {"urg", URG_FLAG}, {"ack", ACK_FLAG}, \ {"psh", PSH_FLAG}, {"rst", RST_FLAG}, {"syn", SYN_FLAG}, {"fin", FIN_FLAG}, \ {NULL, 0}} // clang-format on /* TCP header length and flags */ #define TCP_HDR_LENGTH_MASK 0xF000 #define TCP_FLAGS_MASK 0x0FFF /* The TCP/IPv4 Pseudo header */ typedef struct tcpip_s { ipOverlay_t ip; /* IPv4 overlay header */ struct rte_tcp_hdr tcp; /* TCP header for protocol */ } __attribute__((__packed__)) tcpip_t; /* The GTPu/TCP/IPv4 Pseudo header */ typedef struct gtpuTcpIp_s { ipOverlay_t ip; /* IPv4 overlay header */ struct rte_tcp_hdr tcp; /* TCP header for protocol */ gtpuHdr_t gtpu; /* GTP-U header */ } __attribute__((__packed__)) gtpuTcpIp_t; /* The TCP/IPv6 Pseudo header */ typedef struct tcpipv6_s { ipv6Overlay_t ip; /* IPv6 overlay header */ struct rte_tcp_hdr tcp; /* TCP header for protocol */ } __attribute__((__packed__)) tcpipv6_t; /* ICMPv4 Packet data structures */ union icmp_data { struct { uint16_t ident; /* Identifier */ uint16_t seq; /* Sequence Number */ uint32_t data; /* Data for sequence number */ } echo; struct { uint32_t gateway; /* Gateway Address */ uint16_t ip[10]; /* IP information */ uint16_t transport[4]; /* Transport information */ } redirect; struct { uint32_t nextHopMtu; /* Only if code NEED_FRAG or unused */ uint16_t ip[10]; /* IP information */ uint16_t transport[4]; /* Transport information */ } failing_pkt; struct { uint16_t ident; /* Identifier */ uint16_t seq; /* Sequence Number */ uint32_t originate; /* originate time */ uint32_t receive; /* receive time */ uint32_t transmit; /* transmit time */ } timestamp; struct { uint32_t multicast; /* Multicast information */ uint8_t s_qrv; /* s_grv value */ uint8_t qqic; /* qqoc value */ uint16_t numberOfSources; /* number of Source routes */ uint16_t source_addr[1]; /* source address */ } igmp; struct { uint8_t pointer; /* Parameter pointer */ uint8_t unused[3]; /* Not used */ } param; /* Parameter Problem */ struct { uint8_t numAddrs; /* Number of address */ uint8_t addrEntrySize; /* Size of each entry */ uint16_t lifetime; /* lifetime value */ uint32_t advert[1]; /* advertized values */ } advertise; /* Address mask */ struct { uint16_t ident; /* Identifier */ uint16_t seq; /* Sequence Number */ uint32_t dmask; /* Mask data */ } mask; } __attribute__((__packed__)); #define ICMP4_TIMESTAMP_SIZE 12 /* ICMPv4 Message Types */ #define ICMP4_ECHO_REPLY 0 #define ICMP4_DEST_UNREACHABLE 3 #define ICMP4_SOURCE_QUENCH 4 #define ICMP4_REDIRECT 5 #define ICMP4_ECHO 8 #define ICMP4_ROUTER_ADVERT 9 #define ICMP4_ROUTER_SOLICIT 10 #define ICMP4_TIME_EXCEEDED 11 #define ICMP4_PARAMETER_PROBLEM 12 #define ICMP4_TIMESTAMP 13 #define ICMP4_TIMESTAMP_REPLY 14 #define ICMP4_INFO_REQUEST 15 #define ICMP4_INFO_REPLY 16 #define ICMP4_MASK_REQUEST 17 #define ICMP4_MASK_REPLY 18 /* MPLS header * MPLS Header Format * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Label | EXP |S| TTL | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * Label: 20-bit label value * EXP: Experimental (QoS and ECN) * - 3-bit Traffic Class field for QoS priority * - Explicit Congestion Notification * S: Bottom-of-Stack. If set, the current label is the last in the stack. * TTL: Time-to-Live */ /* Set/clear Bottom of Stack flag */ #define MPLS_SET_BOS(mpls_label) \ do { \ mpls_label |= (1 << 8); \ } while ((0)) #define MPLS_CLR_BOS(mpls_label) \ do { \ mpls_label &= ~(1 << 8); \ } while ((0)) typedef struct mplsHdr_s { uint32_t label; /**< MPLS label */ } __attribute__((__packed__)) mplsHdr_t; /* Q-in-Q (802.1ad) header * Q-in-Q Header Format * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | 0x88A8 | PCP |D| VID | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | 0x8100 | PCP |D| VID | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * 0x88A8, 0x8100: EtherType associated with the tag. 0x88A8 means outer VLAN * tag, 0x8100 means inner VLAN tag. * PCP: Priority code point. Class of Service indicator * D: Drop eligible indicator * VID: VLAN identifier */ typedef struct qinqHdr_s { uint16_t qinq_tci; /**< Outer tag PCP, DEI, VID */ uint16_t vlan_tpid; /**< Must be RTE_ETHER_TYPE_VLAN (0x8100) */ uint16_t vlan_tci; /**< Inner tag PCP, DEI, VID */ uint16_t eth_proto; /**< EtherType of encapsulated frame */ } __attribute__((__packed__)) qinqHdr_t; /* GRE header * * GRE Header Format * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |C| |K|S| Reserved0 | Ver | Protocol Type | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Checksum (optional) | Reserved1 (optional) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Key (optional) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Sequence Number (optional) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * C: 1 if the Checksum and the Reserved1 fields are present and the Checksum * field contains valid information * K: 1 if the Key field is present * S: the Sequence Number field is present * Reserved0: must be 0 * Ver: Version Number, must be 0 * Protocol Type: EtherType of encapsulated packet. When IPv4 is being carried * as the GRE payload, the Protocol Type field MUST be set to 0x800. * Checksum: the IP (one's complement) checksum sum of all the 16 bit words in * the GRE header and the payload packet * Reserved1: must be 0 * Key: 32bit number determined by the encapsulator * Sequence Number: for packet ordering purposes */ /* GRE header */ typedef struct greHdr_s { uint8_t reserved0_0 : 4; /**< must be 0 */ uint8_t seq_present : 1; /**< Sequence Number present */ uint8_t key_present : 1; /**< Key present */ uint8_t unused : 1; uint8_t chk_present : 1; /**< Checksum and Reserved1 present */ uint8_t version : 3; /**< Version Number */ uint8_t reserved0_1 : 5; /**< must be 0 */ uint16_t eth_type; /**< EtherType of encapsulated packet */ uint32_t extra_fields[3]; /**< Room for Checksum+Reserved1, Key and Sequence Number fields if present */ } __attribute__((__packed__)) greHdr_t; /* the GRE/IPv4 header */ typedef struct greIp_s { struct rte_ipv4_hdr ip; /* Outer IPv4 header */ greHdr_t gre; /* GRE header for protocol */ } greIp_t; /* the GRE/Ethernet header */ typedef struct greEther_s { struct rte_ipv4_hdr ip; /* Outer IPv4 header */ greHdr_t gre; /* GRE header */ struct rte_ether_hdr ether; /* Inner Ethernet header */ } greEther_t; /* Common defines for Ethernet */ #define ETH_HW_TYPE 1 /* Ethernet hardware type */ #define ETH_HDR_SIZE 14 /* Ethernet MAC header length */ #define ETH_ADDR_SIZE 6 /* Ethernet MAC address length */ #define IPV6_ADDR_LEN 16 /* IPv6 Address length */ #define ETH_VLAN_ENCAP_LEN 4 /* 802.1Q VLAN encap. length */ /* Extra EtherTypes */ #define ETHER_TYPE_MPLS_UNICAST 0x8847 #define ETHER_TYPE_MPLS_MULTICAST 0x8848 #define ETHER_TYPE_Q_IN_Q 0x88A8 #define ETHER_TYPE_TRANSP_ETH_BR 0x6558 /* Transparent Ethernet Bridge */ /* RARP and ARP opcodes */ enum { ARP_REQUEST = 1, ARP_REPLY = 2, RARP_REQUEST = 3, RARP_REPLY = 4, GRATUITOUS_ARP = 5 }; typedef union { uint16_t _16[3]; uint8_t _8[6]; } mac_e; typedef union { uint16_t _16[2]; uint32_t _32; } ip4_e; typedef struct pkt_hdr_s { struct rte_ether_hdr eth; /**< Ethernet header */ union { struct rte_ipv4_hdr ipv4; /**< IPv4 Header */ struct rte_ipv6_hdr ipv6; /**< IPv6 Header */ tcpip_t tip; /**< TCP + IPv4 Headers */ udpip_t uip; /**< UDP + IPv4 Headers */ gtpuUdpIp_t guip; /**< GTP-U + UDP + IPv4 Header */ struct rte_icmp_hdr icmp; /**< ICMP + IPv4 Headers */ tcpipv6_t tip6; /**< TCP + IPv6 Headers */ udpipv6_t uip6; /**< UDP + IPv6 Headers */ uint8_t pad[128 - sizeof(struct rte_ether_hdr)]; /* Force 128 bytes */ } u; } pkt_hdr_t; typedef struct ipv4_5tuple { uint32_t ip_dst; uint32_t ip_src; uint16_t port_dst; uint16_t port_src; uint8_t proto; } __attribute__((__packed__)) ipv4_5tuple_t; typedef struct l3_4route_s { ipv4_5tuple_t key; uint8_t ifid; } __attribute__((__packed__)) l3_4route_t; typedef struct ipv6_5tuple_s { uint8_t dst[IPV6_ADDR_LEN]; uint8_t src[IPV6_ADDR_LEN]; uint16_t sport; uint16_t dport; uint8_t proto; } __attribute__((__packed__)) ipv6_5tuple_t; typedef struct l3_6route_s { ipv6_5tuple_t key; uint8_t ifid; } __attribute__((__packed__)) l3_6route_t; /*********************************************************************************/ /** * Use crc32 instruction to perform a 6 byte hash. * * @param data * Data to perform hash on. * @param data_len * How many bytes to use to calculate hash value. (Not Used) * @param init_val * Value to initialize hash generator. * @return * 32bit calculated hash value. */ static inline uint32_t rte_hash6_crc(const void *data, __attribute__((unused)) uint32_t data_len, uint32_t init_val) { #ifdef RTE_MACHINE_CPUFLAG_SSE4_2 const uint32_t *p32 = (const uint32_t *)data; const uint16_t val = *(const uint16_t *)p32; return _mm_crc32_u32(val, _mm_crc32_u32(*p32++, init_val)); #else return rte_jhash(data, data_len, init_val); #endif } /* ethAddrCopy( u16_t * to, u16_t * from ) - Swap two Ethernet addresses */ static __inline__ void ethAddrCopy(void *t, void *f) { uint16_t *d = (uint16_t *)t; uint16_t *s = (uint16_t *)f; *d++ = *s++; *d++ = *s++; *d = *s; } /* ethSwap(u16_t * to, u16_t * from) - Swap two 16 bit values */ static __inline__ void uint16Swap(void *t, void *f) { uint16_t *d = (uint16_t *)t; uint16_t *s = (uint16_t *)f; uint16_t v; v = *d; *d = *s; *s = v; } /* ethAddrSwap( u16_t * to, u16_t * from ) - Swap two ethernet addresses */ static __inline__ void ethAddrSwap(void *t, void *f) { uint16_t *d = (uint16_t *)t; uint16_t *s = (uint16_t *)f; uint16Swap(d++, s++); uint16Swap(d++, s++); uint16Swap(d, s); } /* inetAddrCopy( void * t, void * f ) - Copy IPv4 address */ static __inline__ void inetAddrCopy(void *t, void *f) { uint32_t *d = (uint32_t *)t; uint32_t *s = (uint32_t *)f; *d = *s; } /* inetAddrSwap( void * t, void * f ) - Swap two IPv4 addresses */ static __inline__ void inetAddrSwap(void *t, void *f) { uint32_t *d = (uint32_t *)t; uint32_t *s = (uint32_t *)f; uint32_t v; v = *d; *d = *s; *s = v; } /* inet6AddrIsUnspecified( void * a ) - Unspecified Address */ static __inline__ int inet6AddrIsUnspecified(void *a) { return !((const uint32_t *)(a))[0] && !((const uint32_t *)(a))[1] && !((const uint32_t *)(a))[2] && !((const uint32_t *)(a))[3]; } /* inet6AddrAdd - Apply paddr3 = paddr1 + paddr2 */ static __inline__ void inet6AddrAdd(void *paddr1, void *paddr2, void *paddr3) { int i; uint8_t *addr1 = (uint8_t *)paddr1, *addr2 = (uint8_t *)paddr2, *addr3 = (uint8_t *)paddr3; uint16_t n = 0; for (i = 15; i >= 0; --i) { n = (uint16_t)addr1[i] + addr2[i] + (n >> 8); addr3[i] = n & 0xff; } } #ifndef _MASK_SIZE_ #define _MASK_SIZE_ /* mask_size(uint32_t mask) - return the number of bits in mask */ static __inline__ int mask_size(uint32_t mask) { if (mask == 0) return 0; else if (mask == 0xFF000000) return 8; else if (mask == 0xFFFF0000) return 16; else if (mask == 0xFFFFFF00) return 24; else if (mask == 0xFFFFFFFF) return 32; else { int i; for (i = 0; i < 32; i++) if ((mask & (1 << (31 - i))) == 0) break; return i; } } #endif /* size_to_mask( int len ) - return the mask for the mask size */ static __inline__ uint32_t size_to_mask(int len) { uint32_t mask = 0; if (len == 0) mask = 0x00000000; else if (len == 8) mask = 0xFF000000; else if (len == 16) mask = 0xFFFF0000; else if (len == 24) mask = 0xFFFFFF00; else if (len == 32) mask = 0xFFFFFFFF; else { int i; for (i = 0; i < len; i++) mask |= (1 << (31 - i)); } return mask; } #ifndef _NTOP4_ #define _NTOP4_ /* char * inet_ntop4(char * buff, int len, unsigned long ip_addr, unsigned long mask) - Convert IPv4 * address to ascii */ static __inline__ char * inet_ntop4(char *buff, int len, unsigned long ip_addr, unsigned long mask) { char lbuf[64]; inet_ntop(AF_INET, &ip_addr, buff, len); if (mask != 0xFFFFFFFF) { snprintf(lbuf, sizeof(lbuf), "%s/%d", buff, mask_size(mask)); snprintf(buff, len, "%s", lbuf); } return buff; } #endif /* const char * inet_ntop6(char * buff, int len, uint8_t *ip6, unsigned prefixlen) - Convert IPv6 * address to ascii */ static __inline__ const char * inet_ntop6(char *buff, int len, uint8_t *ip6, unsigned prefixlen) { char lbuf[64]; unsigned nprint = 0, ntrim = 0; inet_ntop(AF_INET6, ip6, buff, len); strcpy(lbuf, buff); if (prefixlen > PG_PREFIXMAX) { prefixlen ^= (nprint = prefixlen >> 8) << 8; ntrim = prefixlen != PG_PREFIXMAX ? (prefixlen >= 100 ? 3 : prefixlen >= 10 ? 2 : 1) + 1 : 0; if (nprint < strlen(lbuf) + ntrim) { nprint -= ntrim; snprintf(buff, len, "%.*s+%02ld", nprint - 3, lbuf, strlen(lbuf) - nprint + 3); } } if (prefixlen != PG_PREFIXMAX) { snprintf(lbuf, sizeof(lbuf), "%s/%d", buff, prefixlen); snprintf(buff, len, "%s", lbuf); } return buff; } #ifndef _MTOA_ #define _MTOA_ /* char * inet_mtoa(char * buff, int len, struct rte_ether_addr * eaddr) - Convert MAC address to * ascii */ static __inline__ char * inet_mtoa(char *buff, int len, struct rte_ether_addr *eaddr) { snprintf(buff, len, "%02x:%02x:%02x:%02x:%02x:%02x", eaddr->addr_bytes[0], eaddr->addr_bytes[1], eaddr->addr_bytes[2], eaddr->addr_bytes[3], eaddr->addr_bytes[4], eaddr->addr_bytes[5]); return buff; } #endif /* convert a MAC address from network byte order to host 64bit number */ static __inline__ uint64_t inet_mtoh64(struct rte_ether_addr *eaddr, uint64_t *value) { *value = ((uint64_t)eaddr->addr_bytes[5] << 0) + ((uint64_t)eaddr->addr_bytes[4] << 8) + ((uint64_t)eaddr->addr_bytes[3] << 16) + ((uint64_t)eaddr->addr_bytes[2] << 24) + ((uint64_t)eaddr->addr_bytes[1] << 32) + ((uint64_t)eaddr->addr_bytes[0] << 40); return *value; } /* convert a host 64bit number to MAC address in network byte order */ static __inline__ struct rte_ether_addr * inet_h64tom(uint64_t value, struct rte_ether_addr *eaddr) { eaddr->addr_bytes[5] = ((value >> 0) & 0xFF); eaddr->addr_bytes[4] = ((value >> 8) & 0xFF); eaddr->addr_bytes[3] = ((value >> 16) & 0xFF); eaddr->addr_bytes[2] = ((value >> 24) & 0xFF); eaddr->addr_bytes[1] = ((value >> 32) & 0xFF); eaddr->addr_bytes[0] = ((value >> 40) & 0xFF); return eaddr; } /** * Convert an IPv4/v6 address into a binary value. * * @param buf * Location of string to convert * @param flags * Set of flags for converting IPv4/v6 addresses and netmask. * @param res * Location to put the results * @param ressize * Length of res in bytes. * @return * 0 on OK and -1 on error */ int _atoip(const char *buf, int flags, void *res, unsigned ressize); #ifdef __cplusplus } #endif #endif /* _PG_INET_H */ ================================================ FILE: lib/common/pg_strings.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ #include "pg_strings.h" char * pg_strtrim(char *str) { if (!str || !*str) return str; /* trim white space characters at the front */ while (isspace(*str)) str++; /* Make sure the string is not empty */ if (*str) { char *p = &str[strlen(str) - 1]; /* trim trailing white space characters */ while ((p >= str) && isspace(*p)) p--; p[1] = '\0'; } return str; } int pg_strtok(char *str, const char *delim, char **entries, int maxtokens) { int i = 0; char *saved; if (!str || !delim || !entries || !maxtokens) return -1; do { entries[i] = pg_strtrim(strtok_r(str, delim, &saved)); str = NULL; } while (entries[i] && (++i < maxtokens)); return i; } int pg_strqtok(char *str, const char *delim, char *argv[], int maxtokens) { char *p, *start_of_word, *s; int argc = 0; enum { INIT, WORD, STRING_QUOTE, STRING_TICK, STRING_BRACKET } state = WORD; if (!str || !delim || !argv || maxtokens == 0) return -1; /* Remove white space from start and end of string */ s = pg_strtrim(str); start_of_word = s; for (p = s; (argc < maxtokens) && (*p != '\0'); p++) { int c = (unsigned char)*p; if (c == '\\') { start_of_word = ++p; continue; } switch (state) { case INIT: if (c == '"') { state = STRING_QUOTE; start_of_word = p + 1; } else if (c == '\'') { state = STRING_TICK; start_of_word = p + 1; } else if (c == '{') { state = STRING_BRACKET; start_of_word = p + 1; } else if (!strchr(delim, c)) { state = WORD; start_of_word = p; } break; case STRING_QUOTE: if (c == '"') { *p = 0; argv[argc++] = start_of_word; state = INIT; } break; case STRING_TICK: if (c == '\'') { *p = 0; argv[argc++] = start_of_word; state = INIT; } break; case STRING_BRACKET: if (c == '}') { *p = 0; argv[argc++] = start_of_word; state = INIT; } break; case WORD: if (strchr(delim, c)) { *p = 0; argv[argc++] = start_of_word; state = INIT; start_of_word = p + 1; } break; default: break; } } if ((state != INIT) && (argc < maxtokens)) argv[argc++] = start_of_word; if ((argc == 0) && (p != str)) argv[argc++] = str; return argc; } int pg_stropt(const char *list, char *str, const char *delim) { char *argv[STR_MAX_ARGVS + 1], *buf; size_t n, i; if (!list || !str || !delim) return -1; if ((list[0] == '%') && (list[1] == '|')) list += 2; if (!*list) return -1; n = strlen(list) + 2; buf = alloca(n); if (buf) { snprintf(buf, n, "%s", list); n = pg_strtok(buf, delim, argv, STR_MAX_ARGVS); for (i = 0; i < n; i++) if (pg_strmatch(argv[i], str)) return i; } return -1; } int pg_parse_corelist(const char *corelist, uint8_t *lcores, int len) { int idx = 0; unsigned count = 0; char *end = NULL; int min, max, k; char cl_buf[128], *cl = cl_buf; if (corelist == NULL) return -1; memset(lcores, 0, len); strlcpy(cl, corelist, sizeof(cl_buf)); /* Remove all blank characters ahead and after */ cl = pg_strtrim(cl); /* Get list of cores */ min = RTE_MAX_LCORE; k = 0; do { while (isblank(*cl)) cl++; if (*cl == '\0') return -1; errno = 0; idx = strtoul(cl, &end, 10); if (errno || end == NULL) return -1; while (isblank(*end)) end++; if (*end == '-') min = idx; else if ((*end == ',') || (*end == '\0')) { max = idx; if (min == RTE_MAX_LCORE) min = idx; for (idx = min; idx <= max; idx++) { lcores[idx] = 1; count++; } min = RTE_MAX_LCORE; } else return -1; cl = end + 1; if (k >= len) return -1; } while (*end != '\0'); return count; } ================================================ FILE: lib/common/pg_strings.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /** * @file * * String-related utility functions */ #ifndef __STRINGS_H_ #define __STRINGS_H_ #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define __numbits(_v) __builtin_popcount(_v) enum { STR_MAX_ARGVS = 64, /**< Max number of args to support */ STR_TOKEN_SIZE = 128, }; /** * Remove leading and trailing white space from a string. * * @param str * String to be trimmed, must be null terminated * @return * pointer to the trimmed string or NULL if is Null or * if string is empty then return pointer to */ char *pg_strtrim(char *str); /** * Parse a string into a argc/argv list using a set of delimiters, but does * not handle quoted strings within the string being parsed. * * @param str * String to be tokenized and will be modified, must be null terminated * @param delim * A null terminated list of delimitors * @param entries * A pointer to an array to place the token pointers * @param max_entries * Max number of tokens to be placed in * @return * The number of tokens in the array. */ int pg_strtok(char *str, const char *delim, char **entries, int maxtokens); /** * Parse a string into a argc/argv list using a set of delimiters, but does * handle quoted strings within the string being parsed * * @param str * String to be tokenized and will be modified, null terminated * @param delim * A null terminated list of delimitors * @param entries * A pointer to an array to place the token pointers * @param max_entries * Max number of tokens to be placed in * @return * The number of tokens in the array. */ int pg_strqtok(char *str, const char *delim, char **entries, int maxtokens); /** * Parse a string looking for using delim character. * * @param list * A string list of options with delim character between them. * @param str * String to search for in * @param delim * A character string to use as a delim values * @return * The index in the list of option strings, -1 if not found */ int pg_stropt(const char *list, char *str, const char *delim); /** * Parse a corelist string and return a list of the lcores. * * @param corelist * The string defining a set of cores e.g. "1-8,22,25-29" * @param lcores * The pointer to a uint16_t array to update with the core id. * @param len * The length of the uint16_t array. * @return * -1 error or number of cores in the string. */ int pg_parse_corelist(const char *corelist, uint8_t *lcores, int len); /** * Helper routine to compare two strings exactly * * @param s1 * Pointer to first string. * @param s2 * Pointer to second string. * @return * 0 failed to compare and 1 strings are equal. */ static inline int pg_strmatch(const char *s1, const char *s2) { if (!s1 || !s2) return 0; while ((*s1 != '\0') && (*s2 != '\0')) { if (*s1++ != *s2++) return 0; } if (*s1 != *s2) return 0; return 1; } /** * Count the number of characters in a string * * @param s * Null terminated string to search * @param c * character to count * @return * Number of times the character is in string. */ static inline int pg_strcnt(char *s, char c) { return (s == NULL || *s == '\0') ? 0 : pg_strcnt(s + 1, c) + (*s == c); } /** * Convert a string Ethernet MAC address to the binary form * * @param a * String containing the MAC address in two forms * XX:XX:XX:XX:XX:XX or XXXX:XXXX:XXX * @param e * pointer to a struct rte_ether_addr to place the return value. If the value * is null then use a static location instead. * @return * Pointer to the struct rte_ether_addr structure; */ static inline struct rte_ether_addr * pg_ether_aton(const char *a, struct rte_ether_addr *e) { int i; char *end; unsigned long o[RTE_ETHER_ADDR_LEN]; static struct rte_ether_addr rte_ether_addr; if (!e) e = &rte_ether_addr; i = 0; do { errno = 0; o[i] = strtoul(a, &end, 16); if (errno != 0 || end == a || (end[0] != ':' && end[0] != 0)) return NULL; a = end + 1; } while (++i != sizeof(o) / sizeof(o[0]) && end[0] != 0); /* Junk at the end of line */ if (end[0] != 0) return NULL; /* Support the format XX:XX:XX:XX:XX:XX */ if (i == RTE_ETHER_ADDR_LEN) { while (i-- != 0) { if (o[i] > UINT8_MAX) return NULL; e->addr_bytes[i] = (uint8_t)o[i]; } /* Support the format XXXX:XXXX:XXXX */ } else if (i == RTE_ETHER_ADDR_LEN / 2) { while (i-- != 0) { if (o[i] > UINT16_MAX) return NULL; e->addr_bytes[i * 2] = (uint8_t)(o[i] >> 8); e->addr_bytes[i * 2 + 1] = (uint8_t)(o[i] & 0xff); } /* unknown format */ } else return NULL; return e; } #ifndef _MTOA_ #define _MTOA_ /* char * inet_mtoa(char * buff, int len, struct rte_ether_addr * eaddr) - Convert MAC address to * ascii */ static __inline__ char * inet_mtoa(char *buff, int len, struct rte_ether_addr *eaddr) { snprintf(buff, len, "%02x:%02x:%02x:%02x:%02x:%02x", eaddr->addr_bytes[0], eaddr->addr_bytes[1], eaddr->addr_bytes[2], eaddr->addr_bytes[3], eaddr->addr_bytes[4], eaddr->addr_bytes[5]); return buff; } #endif #ifndef _MASK_SIZE_ #define _MASK_SIZE_ /* mask_size(uint32_t mask) - return the number of bits in mask */ static __inline__ int mask_size(uint32_t mask) { if (mask == 0) return 0; else if (mask == 0xFF000000) return 8; else if (mask == 0xFFFF0000) return 16; else if (mask == 0xFFFFFF00) return 24; else if (mask == 0xFFFFFFFF) return 32; else { int i; for (i = 0; i < 32; i++) if ((mask & (1 << (31 - i))) == 0) break; return i; } } #endif #ifndef _NTOP4_ #define _NTOP4_ /* char * inet_ntop4(char * buff, int len, unsigned long ip_addr, unsigned long mask) - Convert IPv4 * address to ascii */ static __inline__ char * inet_ntop4(char *buff, int len, unsigned long ip_addr, unsigned long mask) { char lbuf[64]; inet_ntop(AF_INET, &ip_addr, buff, len); if (mask != 0xFFFFFFFF) { snprintf(lbuf, sizeof(lbuf), "/%d", mask_size(mask)); rte_strlcat(buff, lbuf, len); } return buff; } #endif #ifdef __cplusplus } #endif #endif /* __STRINGS_H_ */ ================================================ FILE: lib/common/port_config.c ================================================ /*- * Copyright(c) <2014-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2014 by Keith Wiles @ intel.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "port_config.h" #define PORT_STRING_SIZE 256 /** * * get_portdesc - Parse the lspci command output to find ports. * * DESCRIPTION * Parse the lspci command output to find valid ports to use. * * RETURNS: Number of ports found. * * SEE ALSO: */ uint32_t get_portdesc(uint8_t **portdesc, uint32_t num, int verbose) { FILE *fd; uint32_t idx; char buff[PORT_STRING_SIZE], *p; if ((num <= 0) || (portdesc == NULL)) return 0; /* Only parse the Ethernet cards on the PCI bus. */ fd = popen("lspci -D | grep Ethernet", "r"); if (fd == NULL) rte_panic("*** Unable to do lspci may need to be installed"); if (verbose) fprintf(stdout, "\n** Bit Mask: All ports in system\n"); idx = 0; while (fgets(buff, sizeof(buff), fd)) { const char *s = "ethernet controller"; int n = strlen(s); p = &buff[0]; /* add a null at the end of the string. */ p[strlen(buff) - 1] = 0; if (verbose) fprintf(stdout, " 0x%016" PRIx64 ": %s\n", (1UL << idx), buff); /* remove both "Ethernet Controller" strings*/ p = strcasestr(buff, s); if (p) memmove(p, p + n, (strlen(buff) - ((p - buff) + n)) + 1); p = strcasestr(buff, s); n++; if (p) /* Try to remove the two strings to make the line shorter */ memmove(p, p + n, (strlen(buff) - ((p - buff) + n)) + 1); memmove(&buff[12], &buff[14], (strlen(buff) - 2) + 1); portdesc[idx] = (uint8_t *)strdup(&buff[5]); /* portdesc[idx] needs to be NULL or we lose memory. */ if (++idx >= num) break; } pclose(fd); if (verbose) fprintf(stdout, "\nFound %d ports\n", idx); return idx; } /** * * free_portdesc - Free the allocated memory for port descriptions. * * DESCRIPTION * Free the allocated memory for the port descriptions * * RETURNS: N/A * * SEE ALSO: */ void free_portdesc(uint8_t **portdesc, uint32_t num) { uint32_t i; for (i = 0; i < num; i++) { if (portdesc[i]) free((char *)portdesc[i]); portdesc[i] = NULL; } } /** * * create_blocklist - Create a port blocklist. * * DESCRIPTION * Create a port blocklist from the port and port descriptions. * * RETURNS: Number of ports in list. * * SEE ALSO: */ uint32_t create_blocklist(uint64_t portmask, struct rte_pci_addr *portlist, uint32_t port_cnt, uint8_t *desc[]) { uint32_t i, idx; char pci_addr_str[32]; if ((portmask == 0) || (portlist == NULL) || (port_cnt == 0) || (desc == NULL)) return 0; fprintf(stdout, "Ports: Port Mask: %016" PRIx64 " blocklisted = --, not-blocklisted = ++\n", portmask); idx = 0; for (i = 0; i < port_cnt; i++) { memset(pci_addr_str, 0, sizeof(pci_addr_str)); if ((portmask & (1UL << i)) == 0) { fprintf(stdout, "-- %s\n", desc[i]); snprintf(pci_addr_str, sizeof(pci_addr_str), "%s", desc[i]); rte_devargs_add(RTE_DEVTYPE_BLOCKED, pci_addr_str); idx++; } else { snprintf(pci_addr_str, sizeof(pci_addr_str), "%s", desc[i]); rte_devargs_add(RTE_DEVTYPE_ALLOWED, pci_addr_str); fprintf(stdout, "++ %s\n", desc[i]); } } if (desc) fprintf(stdout, "\n"); return idx; } ================================================ FILE: lib/common/port_config.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2013-2024 by Keith Wiles @ intel.com */ #ifndef _PORT_CONFIG_H #define _PORT_CONFIG_H #ifdef __cplusplus extern "C" { #endif uint32_t get_portdesc(uint8_t **portdesc, uint32_t num, int verbose); void free_portdesc(uint8_t **portdesc, uint32_t num); uint32_t create_blocklist(uint64_t portmask, struct rte_pci_addr *portlist, uint32_t port_cnt, uint8_t *desc[]); #ifdef __cplusplus } #endif #endif /* _PORT_CONFIG_H */ ================================================ FILE: lib/common/utils.c ================================================ /*- * Copyright(c) <2014-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2014 by Keith Wiles @ intel.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #define MAX_PARSE_SIZE 256 /****************************************************************************** * pg_strtrim - Remove leading and trailing white space from a string. * * SYNOPSIS * char * pg_strtrim * ( * char * str * ) * * DESCRIPTION * Remove leading and trailing white space from a string. * * RETURNS: pointer to the trimmed string or NULL is Null. * * ERRNO: N/A * * \NOMANUAL */ static char * _strtrim(char *str) { char *p; int len; if ((str != NULL) && (len = strlen(str))) { /* skip white spaces at the front of the string */ for (; *str != 0; str++) if ((*str != ' ') && (*str != '\t') && (*str != '\r') && (*str != '\n')) break; len = strlen(str); if (len == 0) return str; /* Trim trailing characters */ for (p = &str[len - 1]; p > str; p--) { if ((*p != ' ') && (*p != '\t') && (*p != '\r') && (*p != '\n')) break; *p = '\0'; } } return str; } uint32_t pg_strparse(char *str, const char *delim, char **entries, uint32_t max_entries) { uint32_t i; char *saved; if ((str == NULL) || (delim == NULL) || (entries == NULL) || (max_entries == 0)) return 0; memset(entries, '\0', (sizeof(char *) * max_entries)); for (i = 0; i < max_entries; i++) { entries[i] = strtok_r(str, delim, &saved); str = NULL; if (entries[i] == NULL) /* We are done. */ break; entries[i] = _strtrim(entries[i]); } return i; } static uint32_t skip_lst(char f, const char *lst) { for (; *lst != '\n'; lst++) if (f == *lst) return 1; return 0; } char * pg_strccpy(char *t, char *f, const char *lst) { if ((t == NULL) || (f == NULL)) return NULL; *t = '\0'; if (*f == '\0') return t; while (*f != '\0') { if (skip_lst(*f, lst)) f++; else *t++ = *f++; } *t = '\0'; return t; } ================================================ FILE: lib/common/utils.h ================================================ /*- * Copyright(c) <2010-2026>, Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ /* Created 2014 by Keith Wiles @ intel.com */ #ifndef _UTILS_H_ #define _UTILS_H_ #ifdef __cplusplus extern "C" { #endif /** * The function is a wrapper around strdup() and will free the previous string * if the pointer is present. */ static __inline__ char * pg_strdupf(char *str, char *new) { if (str) free(str); return (new == NULL) ? NULL : strdup(new); } /** * Trim a set of characters like "[]" or "{}" from the start and end of string. * * @param str * A null terminated string to be trimmed. * @param set * The string is a set of two character values to be removed from the * . Removes only one set at a time, if you have more then one set to * remove then you must call the routine for each set. The string must * be two characters and can be any characters you * want to call a set. * @return * Pointer to the trimmed string or NULL on error */ static __inline__ char * pg_strtrimset(char *str, const char *set) { int len; len = strlen(set); if ((len == 0) || (len & 1)) return NULL; for (; set && (set[0] != '\0'); set += 2) { if (*str != *set) continue; if (*str == *set++) str++; len = strlen(str); if (len && (str[len - 1] == *set)) str[len - 1] = '\0'; } return str; } uint32_t pg_strparse(char *s, const char *delim, char **entries, uint32_t max_entries); char *pg_strccpy(char *t, char *f, const char *str); #ifdef __cplusplus } #endif #endif /* _UTILS_H_ */ ================================================ FILE: lib/hmap/README.md ================================================ # HMAP - Hash Map Library ## Overview HMAP is a high-performance, type-safe hash map implementation optimized for DPDK applications. It provides a flexible key-value store with support for multiple data types, arrays, and efficient memory management. ## Key Features - **Type-safe storage**: Native support for 12+ scalar types and their arrays - **Prefix-based organization**: Group related keys using optional prefixes - **Zero-copy arrays**: Direct pointer access to array data - **Cache-aligned structures**: Optimized for modern CPU architectures - **Thread-safe options**: Built-in mutex support for concurrent access - **Unified 64-bit storage**: Efficient memory layout with minimal overhead - **Auto-managed keys**: Keys and prefixes are automatically duplicated and freed ## Important: String Value Lifetime Requirements **The caller is responsible for managing the lifetime of string VALUES, but NOT keys/prefixes.** ### Memory Ownership Model HMAP uses a mixed memory ownership model: - **Keys and Prefixes**: HMAP-managed - automatically duplicated and freed by hmap - **String Values**: Caller-managed - must remain valid for entry lifetime - **String Arrays**: Caller-managed - neither the array nor its elements are copied - **Scalar Values**: Copied by value into hmap's internal storage - **Scalar Arrays**: Caller-managed - pointer is stored, data must remain valid ### Benefits This design provides: - **Flexible keys**: Use dynamic strings, constants, or temporaries for keys/prefixes - **Efficient values**: No allocation overhead for scalar values - **Predictable performance**: No hidden allocations except for keys/prefixes - **Simple cleanup**: Free your value allocations after hmap_destroy() ### String Value Examples ```c // ✓ CORRECT: String literals (most common and recommended) hmap_add_value(hmap, "config", "app_name", "MyApp"); // String literal - valid forever // ✓ CORRECT: Keys can be dynamic (automatically duplicated by hmap) char key_buf[32]; snprintf(key_buf, sizeof(key_buf), "port_%d", port_num); hmap_add_value(hmap, "network", key_buf, port_config); // key_buf duplicated internally // ✓ CORRECT: Caller-managed dynamic string value char *description = strdup("My application description"); hmap_add_value(hmap, "config", "description", description); // ... use hmap ... hmap_destroy(hmap); free(description); // Free AFTER hmap is destroyed // ✓ CORRECT: Static/global strings static char global_string[] = "Global Value"; hmap_add_value(hmap, "config", "global", global_string); // ✗ WRONG: Stack variable (will be invalid after function returns) void bad_example() { char local_buffer[256]; snprintf(local_buffer, sizeof(local_buffer), "Value %d", 42); hmap_add_value(hmap, "test", "value", local_buffer); // DANGLING POINTER! } // ✗ WRONG: Freeing string while still in hmap char *temp = strdup("temp"); hmap_add_value(hmap, "test", "temp", temp); free(temp); // WRONG - hmap still has pointer to freed memory! char *result; hmap_get_value(hmap, "test", "temp", &result); // result now points to freed memory! ``` ### String Array Examples ```c // ✓ CORRECT: Caller-managed string array char **allowed_ips = malloc(3 * sizeof(char *)); int allocated = 1; if (allocated) { allowed_ips[0] = strdup("192.168.1.1"); allowed_ips[1] = strdup("10.0.0.1"); allowed_ips[2] = strdup("172.16.0.1"); } else { // Or use string literals allowed_ips[0] = "192.168.1.1"; allowed_ips[1] = "10.0.0.1"; allowed_ips[2] = "172.16.0.1"; } hmap_add_array(hmap, "security", "allowed_ips", allowed_ips, 3); // ... use hmap ... hmap_destroy(hmap); // Free AFTER hmap is destroyed only when entries are allocated by caller if (allocated) { for (int i = 0; i < 3; i++) free(allowed_ips[i]); } free(allowed_ips); ``` ### Key and Prefix Examples ```c // ✓ CORRECT: String literals (most common and recommended) hmap_add_value(hmap, "config", "port", 8080); // ✓ CORRECT: Static/global strings static const char *prefix = "settings"; static const char *key = "timeout"; hmap_add_value(hmap, prefix, key, 30); // ✓ CORRECT: Caller-managed dynamic strings char *my_key = strdup("dynamic_key"); hmap_add_value(hmap, "prefix", my_key, value); // ... later, AFTER removing from hmap or destroying hmap: free(my_key); // ✗ WRONG: Local stack variable (will be invalid after function returns) void bad_example() { char key[32]; snprintf(key, sizeof(key), "key_%d", 42); hmap_add_value(hmap, "test", key, 123); // DANGLING POINTER! } ``` ### Complete Memory Management Example ```c hmap_t *config = hmap_create("app-config", 64, NULL); // Add string value (caller-managed) char *app_name = strdup("MyApp"); // can be a string literal or dynamic string hmap_add_value(config, "app", "name", app_name); // Add string array (caller-managed) with allocated elements char **ips = malloc(2 * sizeof(char *)); ips[0] = strdup("192.168.1.1"); ips[1] = strdup("10.0.0.1"); hmap_add_array(config, "security", "ips", ips, 2); // Add scalar values (copied by hmap, no management needed) hmap_add_value(config, "app", "port", (uint32_t)8080); hmap_add_value(config, "app", "timeout", (uint32_t)30); // ... use the hmap ... // Destroy hmap first hmap_destroy(config); // Then free your allocated memory free(app_name); // because string values are caller-managed for (int i = 0; i < 2; i++) free(ips[i]); // free each string in the array free(ips); ``` ## Performance Optimization ### Memory Allocation Behavior HMAP uses a **flat array with linear probing** for optimal cache performance: - ✓ No per-entry allocation - entries occupy pre-allocated array slots - ✓ Cache-friendly 32-byte KVP structures - ✓ Only allocates when resizing the array **Key Insight**: The main allocation overhead comes from resizing, not per-entry operations. ### Best Practice: Pre-Size Your HashMap **Simply set appropriate capacity at creation to eliminate all resizing:** ```c // ✗ BAD: Default capacity causes 5+ reallocations for 1000 entries hmap_t *hmap = hmap_create("config", 0, HMAP_USE_DEFAULT_CAPACITY); for (int i = 0; i < 1000; i++) hmap_add_value(hmap, "test", keys[i], i); // Multiple resizes! // ✓ GOOD: Pre-size for expected capacity - ZERO reallocations hmap_t *hmap = hmap_create("config", 0, 1024); for (int i = 0; i < 1000; i++) hmap_add_value(hmap, "test", keys[i], i); // No resizing needed ``` **Capacity Guidelines**: - Set `max_capacity` to ~1.3x expected maximum entries - Hash table performs best at 60-80% full - Example: For 1000 entries, use capacity of 1300-1500 ### Allocation Tracking Enable allocation tracking to measure and validate performance: ```c // Compile with -DHMAP_TRACK_ALLOCS to enable tracking #ifdef HMAP_TRACK_ALLOCS uint64_t allocs, reallocs, bytes; hmap_get_global_alloc_stats(&allocs, &reallocs, &bytes); printf("Allocations: %lu, Reallocations: %lu, Total bytes: %lu\n", allocs, reallocs, bytes); // Per-hmap tracking uint64_t hmap_allocs = hmap_get_alloc_count(hmap); printf("This hmap performed %lu allocations\n", hmap_allocs); #endif ``` ### Performance Comparison | Approach | Allocs (1000 inserts) | Performance | Use Case | |----------|----------------------|-------------|----------| | Default capacity | 5-7 (resizing) | Good | General use | | **Pre-sized** | **1** | **Excellent** | **Recommended** | ## Basic Usage ### Creating a Hash Map ```c #include "hmap.h" // Create a hash map with default capacity (1024) hmap_t *hmap = hmap_create("my-config", 0, HMAP_DEFAULT_CAPACITY); if (!hmap) { // Handle error } // Create with custom capacity hmap_t *hmap2 = hmap_create("large-map", 0, 4096); ``` ### Adding Values Using C11 `_Generic` macros for type-safe, readable code: ```c // Scalar values - type is automatically inferred hmap_add_value(hmap, "app", "name", "my-application"); hmap_add_value(hmap, "settings", "port", 8080); hmap_add_value(hmap, "stats", "counter", -42L); hmap_add_value(hmap, "metrics", "ratio", 3.14159); hmap_add_value(hmap, "flags", "debug", true); // Add without prefix (global keys) hmap_add_value(hmap, NULL, "version", 1); // Arrays - type is automatically inferred from array pointer uint32_t ports[] = {8080, 8081, 8082, 8083}; char *names[] = {"alice", "bob", "charlie"}; double ratios[] = {1.5, 2.5, 3.75}; hmap_add_array(hmap, "config", "ports", ports, 4); hmap_add_array(hmap, "users", "names", names, 3); hmap_add_array(hmap, "stats", "ratios", ratios, 3); ``` ### Retrieving Values Using C11 `_Generic` macros for type-safe retrieval: ```c // Scalar values - type is automatically inferred from pointer uint32_t port; char *name; uint8_t debug; // Boolean: 0=false, 1=true (stored as uint8_t) double ratio; hmap_get_value(hmap, "settings", "port", &port); hmap_get_value(hmap, "app", "name", &name); hmap_get_value(hmap, "flags", "debug", &debug); hmap_get_value(hmap, "metrics", "ratio", &ratio); // Arrays - type is automatically inferred from pointer uint32_t *ports; char **names; int port_count = hmap_get_array(hmap, "config", "ports", &ports); int name_count = hmap_get_array(hmap, "users", "names", &names); // Access array elements for (int i = 0; i < port_count; i++) { printf("Port[%d] = %u\n", i, ports[i]); } ``` ### Updating Values ```c // Update existing value hmap_kvp_t *kvp = hmap_kvp_lookup(hmap, "settings", "port"); if (kvp) { hmap_val_t new_val = {.u64 = 9090}; hmap_kvp_update(hmap, kvp, &new_val); } else { // Key not found hmap_add_value(hmap, "settings", "port", 9090); } ``` ### Checking Existence ```c hmap_kvp_t *kvp = hmap_kvp_lookup(hmap, "settings", "port"); if (kvp) { printf("Key exists with type: %d\n", kvp->type); } else { printf("Key not found\n"); } ``` ### Iteration ```c // List all entries (unsorted) hmap_list(hmap, stdout, false); // List all entries (sorted by key) hmap_list(hmap, stdout, true); // Iterate programmatically hmap_kvp_t *kvp_list[HMAP_DEFAULT_CAPACITY]; int count = hmap_kvp_list(hmap, kvp_list, HMAP_DEFAULT_CAPACITY, false); for (int i = 0; i < count; i++) { hmap_kvp_t *kvp = kvp_list[i]; printf("Prefix: %s, Key: %s, Type: %d\n", kvp->prefix ? kvp->prefix : "(none)", kvp->key, kvp->type); } ``` ### Cleanup ```c // Destroy the hash map and free all resources hmap_destroy(hmap); ``` ## Complete Example ```c #include #include "hmap.h" int main(void) { // Create hash map hmap_t *config = hmap_create("app-config", 0, 64); if (!config) return -1; // Add configuration values - keys and prefixes are automatically duplicated by hmap // String values need dynamic allocation (caller-managed) char *app_name = strdup("MyApp"); hmap_add_value(config, "app", "name", app_name); // Scalar values are copied by hmap (no management needed) hmap_add_value(config, "app", "version", (uint32_t)2); hmap_add_value(config, "server", "port", (uint32_t)8080); hmap_add_value(config, "server", "ssl", (uint8_t)1); // Boolean as uint8_t: 0=false, 1=true hmap_add_value(config, "server", "timeout", (uint32_t)30); // Add array of allowed IPs (caller-managed allocation) char **allowed_ips = malloc(3 * sizeof(char *)); allowed_ips[0] = strdup("192.168.1.1"); allowed_ips[1] = strdup("10.0.0.1"); allowed_ips[2] = strdup("172.16.0.1"); hmap_add_array(config, "security", "allowed_ips", allowed_ips, 3); // Retrieve and display values using simplified API char *retrieved_name; uint32_t version, port, timeout; uint8_t ssl; // Boolean: 0=false, 1=true hmap_get_value(config, "app", "name", &retrieved_name); hmap_get_value(config, "app", "version", &version); hmap_get_value(config, "server", "port", &port); hmap_get_value(config, "server", "ssl", &ssl); hmap_get_value(config, "server", "timeout", &timeout); printf("Application: %s v%u\n", retrieved_name, version); printf("Server: port=%u, ssl=%s, timeout=%us\n", port, ssl ? "enabled" : "disabled", timeout); // Retrieve and display allowed IPs char **ips; int ip_count = hmap_get_array(config, "security", "allowed_ips", &ips); printf("Allowed IPs (%d):\n", ip_count); for (int i = 0; i < ip_count; i++) { printf(" %d. %s\n", i + 1, ips[i]); } // List all configuration printf("\nComplete configuration:\n"); hmap_list(config, stdout, true); // Cleanup: Destroy hmap (automatically frees keys/prefixes), then free caller-managed memory hmap_destroy(config); // Free caller-managed string value free(app_name); // Free caller-managed string array and its elements for (int i = 0; i < 3; i++) free(allowed_ips[i]); free(allowed_ips); return 0; } ``` ## Supported Types ### Scalar Types - **Strings**: `char *` - Null-terminated C strings - **Integers**: `int8_t`, `int16_t`, `int32_t`, `int64_t` - **Unsigned**: `uint8_t`, `uint16_t`, `uint32_t`, `uint64_t` - **Floating**: `double` - 64-bit IEEE 754 - **Boolean**: `uint8_t` - Stored as 0 (false) or 1 (true), use uint8_t type - **Pointer**: `void *` - Generic pointer ### Array Types All scalar types have corresponding array versions: - `char **` - String arrays - `uint32_t *`, `int64_t *`, etc. - Numeric arrays - `uint8_t *` - Boolean arrays (0=false, 1=true) - `void **` - Pointer arrays ## Advanced Features ### Custom Hash Functions ```c // Define custom hash function uint32_t my_hash(const char *prefix, const char *key) { // Your hash implementation return custom_hash_value; } // Create map with custom functions hmap_funcs_t funcs = { .hash_fn = my_hash, .cmp_fn = default_cmp, .free_fn = default_free }; hmap_t *hmap = hmap_create_with_funcs("custom", 0, 128, &funcs); ``` ### Prefix-Based Organization Prefixes allow logical grouping of related keys: ```c // Group by functional area hmap_add_u32(hmap, "network", "port", 8080); hmap_add_u32(hmap, "network", "timeout", 30); hmap_add_bool(hmap, "network", "ssl", true); hmap_add_string(hmap, "database", "host", "localhost"); hmap_add_u32(hmap, "database", "port", 5432); hmap_add_string(hmap, "database", "name", "mydb"); // Retrieve by prefix uint32_t net_port, db_port; hmap_get_value(hmap, "network", "port", &net_port); // 8080 hmap_get_value(hmap, "database", "port", &db_port); // 5432 ``` ### Memory Management HMAP manages memory automatically: - **Strings**: Copies are made internally, freed on destroy - **Arrays**: Stores pointers only - caller manages array memory - **String arrays**: Deep copies made for string array elements - **Updates**: Old values automatically freed when updated ```c // String is copied internally hmap_add_string(hmap, "app", "name", "MyApp"); // Safe to free or modify original string // Array pointer is stored (not copied) uint32_t ports[] = {8080, 8081}; hmap_add_u32_array(hmap, "config", "ports", ports, 2); // Caller must keep 'ports' valid while in hmap // String array elements are deep copied char *names[] = {"alice", "bob"}; hmap_add_string_array(hmap, "users", "names", names, 2); // Safe to free 'names' array after adding ``` ### Performance Considerations - **Initial Capacity**: Choose based on expected entries to minimize rehashing - **Cache Alignment**: Structures optimized for 64-byte cache lines - **Open Addressing**: Fast lookups with good cache locality - **Type-Tagged Storage**: Zero overhead for most types (64-bit values) - **Count in KVP**: Arrays have zero allocation overhead for metadata ## Error Handling Most functions return: - `0` on success, `-1` on failure (add/update/get operations) - `NULL` on failure (creation, lookup operations) - Array count or `-1` on failure (array get operations) ```c if (hmap_add_u32(hmap, "config", "port", 8080) != 0) { fprintf(stderr, "Failed to add port\n"); } hmap_kvp_t *kvp = hmap_kvp_lookup(hmap, "config", "port"); if (!kvp) { fprintf(stderr, "Key not found\n"); } ``` ## Best Practices 1. **Use the generic macros**: `hmap_add_value()`, `hmap_add_array()`, `hmap_get_value()`, and `hmap_get_array()` provide type-safe, readable code 2. **Choose meaningful prefixes**: Group related configuration logically 3. **Set appropriate capacity**: Avoid rehashing by estimating entry count 4. **Check return values**: Always verify operations succeeded 5. **Manage array lifetimes**: Keep array data valid while referenced by hmap 6. **Use sorted listings**: Enable sorting for better debugging/inspection 7. **Destroy when done**: Always call `hmap_destroy()` to prevent leaks ## API Reference ### Creation & Destruction - `hmap_create()` - Create with default functions - `hmap_create_with_funcs()` - Create with custom functions - `hmap_create_with_ext_storage()` - Create with external storage - `hmap_destroy()` - Free all resources ### Adding Values - **`hmap_add_value(hmap, prefix, key, value)`** - Type-safe scalar setter - Supports: `char*`, `bool`, `uint64_t`, `uint32_t`, `uint16_t`, `uint8_t`, `int64_t`, `int32_t`, `int16_t`, `int8_t`, `double`, `float`, `void*` - Returns: `0` on success, `-1` on failure - **`hmap_add_array(hmap, prefix, key, array, count)`** - Type-safe array setter - Supports arrays of all scalar types above - Returns: `0` on success, `-1` on failure ### Getting Values - **`hmap_get_value(hmap, prefix, key, &value)`** - Type-safe scalar getter - Type inferred from value pointer - Returns: `0` on success, `-1` on failure - **`hmap_get_array(hmap, prefix, key, &array)`** - Type-safe array getter - Type inferred from array pointer - Returns: Array count on success, `-1` on failure ### Lookup & Update - `hmap_kvp_lookup()` - Find key-value pair - `hmap_kvp_update()` - Update existing value ### Inspection - `hmap_list()` - Print to file stream - `hmap_kvp_list()` - Get array of pointers - `hmap_iterate()` - Callback-based iteration - `hmap_get_funcs()` - Get function pointers ### Implementation Details The generic macros use C11 `_Generic` to dispatch to internal type-specific functions (prefixed with `_hmap_`). These internal functions are not part of the public API and should not be called directly. ## See Also - [JCFG Library](../jcfg/) - JSON configuration parser built on HMAP - [Examples](../../../examples/) - Complete usage examples - [hmap.h](hmap.h) - Full API documentation ================================================ FILE: lib/hmap/hmap.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright (c) 2016-2026 Intel Corporation */ #include // for free, calloc, malloc, qsort #include // for strlen, strncmp, strerror, strnlen #include // for EEXIST #include // for TAILQ_FOREACH_SAFE #include // for strlcpy #include #include "hmap.h" #include "hmap_log.h" #include "hmap_helper.h" /* Allocation tracking for performance measurement */ #ifdef HMAP_TRACK_ALLOCS static _Atomic uint64_t hmap_alloc_count = 0; static _Atomic uint64_t hmap_realloc_count = 0; static _Atomic uint64_t hmap_total_bytes = 0; #define HMAP_ALLOC_INC() (__atomic_add_fetch(&hmap_alloc_count, 1, __ATOMIC_SEQ_CST)) #define HMAP_REALLOC_INC() (__atomic_add_fetch(&hmap_realloc_count, 1, __ATOMIC_SEQ_CST)) #define HMAP_BYTES_ADD(n) (__atomic_add_fetch(&hmap_total_bytes, (n), __ATOMIC_SEQ_CST)) #else #define HMAP_ALLOC_INC() \ do { \ } while (0) #define HMAP_REALLOC_INC() \ do { \ } while (0) #define HMAP_BYTES_ADD(n) \ do { \ } while (0) #endif int hmap_set_funcs(hmap_t *hmap, hmap_funcs_t *fns) { // clang-format off hmap_funcs_t default_funcs = { .hash_fn = _hmap_hash, .cmp_fn = _hmap_cmp, .free_fn = _hmap_free }; // clang-format on if (!hmap) return -1; if (!fns) fns = &default_funcs; /* * Test each function pointer to make sure the user passed a valid function pointer, * if they did not use the default function. */ hmap->fns.hash_fn = (fns->hash_fn) ? fns->hash_fn : default_funcs.hash_fn; hmap->fns.free_fn = (fns->free_fn) ? fns->free_fn : default_funcs.free_fn; hmap->fns.cmp_fn = (fns->cmp_fn) ? fns->cmp_fn : default_funcs.cmp_fn; return 0; } hmap_funcs_t * hmap_get_funcs(hmap_t *hmap) { return (hmap) ? &hmap->fns : NULL; } /* Hmap's need a hash function, an equality function, and a destructor */ hmap_t * hmap_create(const char *name, uint32_t max_capacity, hmap_funcs_t *funcs) { hmap_t *hmap = NULL; if (!name) name = "HMAP"; if (strnlen(name, HMAP_MAX_NAME_SIZE) >= HMAP_MAX_NAME_SIZE) HMAP_NULL_RET("Name is greater than or equal to %d bytes\n", HMAP_MAX_NAME_SIZE); hmap = calloc(1, sizeof(hmap_t)); if (!hmap) HMAP_ERR_GOTO(err_leave, "Failed to allocate hmap structure\n"); HMAP_ALLOC_INC(); HMAP_BYTES_ADD(sizeof(hmap_t)); strlcpy(hmap->name, name, sizeof(hmap->name)); /* * if max_capacity is zero, set to HMAP_DEFAULT_CAPACITY * if max_capacity is less than HMAP_STARTING_CAPACITY, set to HMAP_STARTING_CAPACITY */ if (max_capacity == HMAP_USE_DEFAULT_CAPACITY) max_capacity = HMAP_DEFAULT_CAPACITY; if (max_capacity < HMAP_STARTING_CAPACITY) max_capacity = HMAP_STARTING_CAPACITY; hmap->capacity = max_capacity / 4; if (hmap->capacity == 0) hmap->capacity = HMAP_STARTING_CAPACITY; hmap->max_capacity = max_capacity; hmap->alloc_count = 0; /* Start out with the initial capacity */ hmap->map = calloc(hmap->capacity, sizeof(struct hmap_kvp)); if (!hmap->map) HMAP_ERR_GOTO(err_leave, "Failed to allocate KVP for %d capacity\n", hmap->capacity); HMAP_ALLOC_INC(); HMAP_BYTES_ADD(hmap->capacity * sizeof(struct hmap_kvp)); hmap->alloc_count++; if (hmap_set_funcs(hmap, funcs) < 0) HMAP_ERR_GOTO(err_leave, "Failed to set function pointers\n"); if (hmap_mutex_create(&hmap->mutex, PTHREAD_MUTEX_RECURSIVE_NP) < 0) HMAP_ERR_GOTO(err_leave, "mutex init(hmap->mutex) failed: %s\n", strerror(errno)); hmap_list_lock(); TAILQ_INSERT_TAIL(&hmap_registry, hmap, next); hmap_list_unlock(); return hmap; err_leave: if (hmap) { free(hmap->map); free(hmap); } return NULL; } int hmap_destroy(hmap_t *hmap) { if (!hmap) return 0; hmap_lock(hmap); if (hmap->map) { struct hmap_kvp *kvp = hmap->map; for (uint32_t i = 0; i < hmap->capacity; i++, kvp++) hmap->fns.free_fn(kvp); /* Indicate the hmap is not usable anymore, possible race condition */ hmap->max_capacity = hmap->capacity = hmap->curr_capacity = 0; free(hmap->map); } hmap_list_lock(); TAILQ_REMOVE(&hmap_registry, hmap, next); hmap_list_unlock(); hmap_unlock(hmap); if (hmap_mutex_destroy(&hmap->mutex)) HMAP_ERR_RET("Destroy of mutex failed\n"); free(hmap); return 0; } int hmap_destroy_by_name(const char *name) { hmap_t *hmap, *tvar; TAILQ_FOREACH_SAFE (hmap, &hmap_registry, next, tvar) { if (!strncmp(name, hmap->name, strlen(name))) return hmap_destroy(hmap); } return -1; } int hmap_kvp_update(hmap_t *hmap, hmap_kvp_t *kvp, hmap_val_t *val) { if (!hmap || !kvp || !val) return -1; hmap_lock(hmap); /* * Values are caller-managed (strings, arrays, pointers). * Updating an entry only overwrites the stored pointer/64-bit value. */ kvp->v = *val; /* Keep array count unchanged (set by add/update array helpers). */ if (kvp->type < HMAP_STR_ARRAY_TYPE) kvp->count = 0; hmap_unlock(hmap); return 0; } /* Open addressed insertion function */ static inline int __add_value(hmap_t *hmap, hmap_type_t type, const char *prefix, const char *key, hmap_val_t *val) { uint32_t hash = hmap_get_hash(hmap, prefix, key); for (uint32_t i = 0; i < hmap->capacity; i++) { struct hmap_kvp *kvp; kvp = &hmap->map[hash]; if (kvp->type == HMAP_EMPTY_TYPE) { /* Duplicate key and prefix strings - hmap now owns them */ kvp->key = strdup(key); if (!kvp->key) return -1; if (prefix) { kvp->prefix = strdup(prefix); if (!kvp->prefix) { free(kvp->key); kvp->key = NULL; return -1; } } kvp->type = type; if (hmap_kvp_update(hmap, kvp, val)) { hmap->fns.free_fn(kvp); return -1; } hmap->curr_capacity++; return 0; } hash = (hash + 1) % hmap->capacity; } return -1; } static int _hmap_update_capacity(hmap_t *hmap) { /* Increase the map if free space becomes too small and does not exceed max_capacity */ if ((hmap->curr_capacity >= (hmap->capacity - (HMAP_STARTING_CAPACITY / 4))) && ((hmap->capacity + HMAP_STARTING_CAPACITY) <= hmap->max_capacity)) { struct hmap_kvp *kvp, *old_map, *new_map; uint32_t old_capacity; old_capacity = hmap->capacity; old_map = hmap->map; hmap->capacity += HMAP_STARTING_CAPACITY; new_map = calloc(hmap->capacity, sizeof(struct hmap_kvp)); if (new_map == NULL) { hmap->capacity = old_capacity; return -1; } HMAP_REALLOC_INC(); HMAP_BYTES_ADD(hmap->capacity * sizeof(struct hmap_kvp)); hmap->alloc_count++; hmap->map = new_map; hmap->curr_capacity = 0; /* Add all of the old values into the new map. */ kvp = old_map; for (uint32_t i = 0; i < old_capacity; i++, kvp++) { if (kvp->type == HMAP_EMPTY_TYPE) continue; if (__add_value(hmap, kvp->type, kvp->prefix, kvp->key, &kvp->v)) { free(new_map); hmap->map = old_map; hmap->capacity = old_capacity; return -1; } } free(old_map); } return 0; } int hmap_add(hmap_t *hmap, hmap_type_t type, const char *prefix, const char *key, hmap_val_t *val) { int ret = -1; if (!hmap || !key) // prefix can be NULL return ret; /* Do not allow for duplicates in the hash map */ if (hmap_kvp_lookup(hmap, prefix, key)) return ret; hmap_lock(hmap); if ((ret = _hmap_update_capacity(hmap)) == 0) ret = __add_value(hmap, type, prefix, key, val); hmap_unlock(hmap); return ret; } int hmap_update(hmap_t *hmap, hmap_type_t type, const char *prefix, const char *key, hmap_val_t *val) { hmap_kvp_t *kvp; int ret = -1; if (!hmap || !key) // prefix can be NULL return ret; hmap_lock(hmap); // Update existing key value if it exists if ((kvp = hmap_kvp_lookup(hmap, prefix, key)) != NULL) { if (kvp->type != type) ret = -1; else ret = hmap_kvp_update(hmap, kvp, val); } else { // Otherwise add new key/value pair if ((ret = _hmap_update_capacity(hmap)) == 0) ret = __add_value(hmap, type, prefix, key, val); } hmap_unlock(hmap); return ret; } hmap_kvp_t * hmap_kvp_lookup(hmap_t *hmap, const char *prefix, const char *key) { if (hmap && hmap->map && key) { uint32_t hash = hmap_get_hash(hmap, prefix, key); hmap_lock(hmap); for (uint32_t i = 0; i < hmap->capacity; i++) { struct hmap_kvp *kvp = &hmap->map[hash]; if (kvp->key && hmap->fns.cmp_fn(kvp, prefix, key)) { hmap_unlock(hmap); return kvp; } hash = (hash + 1) % hmap->capacity; } hmap_unlock(hmap); } return NULL; } int hmap_lookup(hmap_t *hmap, const char *prefix, const char *key, hmap_val_t *val) { if (hmap && key) { uint32_t hash = hmap_get_hash(hmap, prefix, key); hmap_lock(hmap); for (uint32_t i = 0; i < hmap->capacity; i++) { struct hmap_kvp *kvp = &hmap->map[hash]; if (kvp->key && hmap->fns.cmp_fn(kvp, prefix, key)) { if (val) val->u64 = kvp->v.u64; hmap_unlock(hmap); return 0; } hash = (hash + 1) % hmap->capacity; } hmap_unlock(hmap); } return -1; } int hmap_del(hmap_t *hmap, const char *prefix, const char *key) { if (hmap && key) { hmap_kvp_t *kvp = hmap_kvp_lookup(hmap, prefix, key); hmap->fns.free_fn(kvp); hmap->curr_capacity--; return 0; } return -1; } int hmap_iterate(hmap_t *hmap, struct hmap_kvp **_kvp, uint32_t *next) { if (hmap && hmap->map && next) { hmap_lock(hmap); for (uint32_t i = *next; i < hmap->capacity; i++) { struct hmap_kvp *kvp = &hmap->map[i]; if (!kvp->key) continue; *next = ++i; if (_kvp) *_kvp = kvp; hmap_unlock(hmap); return 1; } hmap_unlock(hmap); } return 0; } static int kvp_cmp(const void *p1, const void *p2) { struct hmap_kvp *k1 = *(struct hmap_kvp *const *)p1; struct hmap_kvp *k2 = *(struct hmap_kvp *const *)p2; // First compare prefixes (primary sort key) if (k1->prefix && k2->prefix) { // Both have prefixes, compare them first int prefix_cmp = strcmp(k1->prefix, k2->prefix); if (prefix_cmp != 0) return prefix_cmp; // Prefixes are equal, compare keys as secondary sort return strcmp(k1->key, k2->key); } else if (!k1->prefix && !k2->prefix) { // Neither has prefix, just compare keys return strcmp(k1->key, k2->key); } else { // One has prefix, one doesn't - items with prefixes come first return (k1->prefix && !k2->prefix) ? -1 : 1; } } static void _print_kvp(FILE *f, hmap_kvp_t *kvp) { char buff[128]; if (!kvp) return; if (kvp->prefix) snprintf(buff, sizeof(buff), "%s:%s", kvp->prefix, kvp->key); else snprintf(buff, sizeof(buff), "%s", kvp->key); fprintf(f, "%-42s ", buff); switch (kvp->type) { case HMAP_EMPTY_TYPE: fprintf(f, "%-8s:\n", "Empty"); break; case HMAP_U64_TYPE: fprintf(f, "%-8s: %lu\n", "u64", kvp->v.u64); break; case HMAP_U32_TYPE: fprintf(f, "%-8s: %u\n", "u32", (uint32_t)kvp->v.u64); break; case HMAP_U16_TYPE: fprintf(f, "%-8s: %u\n", "u16", (uint16_t)kvp->v.u64); break; case HMAP_U8_TYPE: { uint8_t val = (uint8_t)kvp->v.u64; fprintf(f, "%-8s: %c(%u)\n", "u8", (val >= ' ' && val <= '~') ? val : '.', val); } break; case HMAP_I64_TYPE: fprintf(f, "%-8s: %ld\n", "i64", (int64_t)kvp->v.u64); break; case HMAP_I32_TYPE: fprintf(f, "%-8s: %d\n", "i32", (int32_t)kvp->v.u64); break; case HMAP_I16_TYPE: fprintf(f, "%-8s: %d\n", "i16", (int16_t)kvp->v.u64); break; case HMAP_I8_TYPE: fprintf(f, "%-8s: %d\n", "i8", (int8_t)kvp->v.u64); break; case HMAP_DOUBLE_TYPE: fprintf(f, "%-8s: %g\n", "double", kvp->v.dval); break; case HMAP_STR_TYPE: fprintf(f, "%-8s: '%s'\n", "String", (char *)kvp->v.ptr); break; case HMAP_POINTER_TYPE: fprintf(f, "%-8s: %p\n", "Pointer", kvp->v.ptr); break; case HMAP_STR_ARRAY_TYPE: { char **strs = (char **)kvp->v.ptr; fprintf(f, "%-8s: (%u)\n", "Strings", kvp->count); for (uint32_t i = 0; i < kvp->count; i++) fprintf(f, " %3u: '%s'\n", i, strs[i]); } break; case HMAP_U64_ARRAY_TYPE: { uint64_t *vals = (uint64_t *)kvp->v.ptr; fprintf(f, "%-8s: (%u)\n", "u64s", kvp->count); for (uint32_t i = 0; i < kvp->count; i++) fprintf(f, " %3u: %lu\n", i, vals[i]); } break; case HMAP_U32_ARRAY_TYPE: { uint32_t *vals = (uint32_t *)kvp->v.ptr; fprintf(f, "%-8s: (%u)\n", "u32s", kvp->count); for (uint32_t i = 0; i < kvp->count; i++) fprintf(f, " %3u: %u\n", i, vals[i]); } break; case HMAP_U16_ARRAY_TYPE: { uint16_t *vals = (uint16_t *)kvp->v.ptr; fprintf(f, "%-8s: (%u)\n", "u16s", kvp->count); for (uint32_t i = 0; i < kvp->count; i++) fprintf(f, " %3u: %u\n", i, vals[i]); } break; case HMAP_U8_ARRAY_TYPE: { uint8_t *vals = (uint8_t *)kvp->v.ptr; fprintf(f, "%-8s: (%u)\n", "u8s", kvp->count); for (uint32_t i = 0; i < kvp->count; i++) fprintf(f, " %3u: %c(%u)\n", i, (vals[i] >= ' ' && vals[i] <= '~') ? vals[i] : '.', vals[i]); } break; case HMAP_I64_ARRAY_TYPE: { int64_t *vals = (int64_t *)kvp->v.ptr; fprintf(f, "%-8s: (%u)\n", "I64s", kvp->count); for (uint32_t i = 0; i < kvp->count; i++) fprintf(f, " %3u: %ld\n", i, vals[i]); } break; case HMAP_I32_ARRAY_TYPE: { int32_t *vals = (int32_t *)kvp->v.ptr; fprintf(f, "%-8s: (%u)\n", "I32s", kvp->count); for (uint32_t i = 0; i < kvp->count; i++) fprintf(f, " %3u: %d\n", i, vals[i]); } break; case HMAP_I16_ARRAY_TYPE: { int16_t *vals = (int16_t *)kvp->v.ptr; fprintf(f, "%-8s: (%u)\n", "I16s", kvp->count); for (uint32_t i = 0; i < kvp->count; i++) fprintf(f, " %3u: %d\n", i, vals[i]); } break; case HMAP_I8_ARRAY_TYPE: { int8_t *vals = (int8_t *)kvp->v.ptr; fprintf(f, "%-8s: (%u)\n", "I8s", kvp->count); for (uint32_t i = 0; i < kvp->count; i++) fprintf(f, " %3u: %d\n", i, vals[i]); } break; case HMAP_DOUBLE_ARRAY_TYPE: { double *vals = (double *)kvp->v.ptr; fprintf(f, "%-8s: (%u)\n", "Doubles", kvp->count); for (uint32_t i = 0; i < kvp->count; i++) fprintf(f, " %3u: %g\n", i, vals[i]); } break; case HMAP_PTR_ARRAY_TYPE: { void **vals = (void **)kvp->v.ptr; fprintf(f, "%-8s: (%u)\n", "Pointers", kvp->count); for (uint32_t i = 0; i < kvp->count; i++) fprintf(f, " %3u: %p\n", i, vals[i]); } break; default: HMAP_ERR("*** Unknown type %d\n", kvp->type); break; } } void hmap_list(FILE *f, hmap_t *hmap, bool sort) { uint32_t next = 0; hmap_kvp_t *kvp; if (!hmap) return; if (!f) f = stdout; fprintf(f, "\n**** Hashmap dump (%s) %s ****\n", hmap->name, sort ? "Sorted" : "Not Sorted"); fprintf(f, " %-5s: %-42s %s\n", "Index", "Prefix:Key", "Type:Value"); if (sort) { // Sorted output using qsort struct hmap_kvp **kvp_list; int cnt = hmap_count(hmap); kvp_list = (struct hmap_kvp **)malloc(cnt * sizeof(struct hmap_kvp *)); if (!kvp_list) return; for (int i = 0; hmap_iterate(hmap, &kvp, &next); i++) kvp_list[i] = kvp; qsort(kvp_list, cnt, sizeof(struct hmap_kvp *), kvp_cmp); for (int i = 0; i < cnt; i++) { fprintf(f, " %5d: ", i); _print_kvp(f, kvp_list[i]); } free(kvp_list); } else { // Unsorted output using simple iteration for (int i = 0; hmap_iterate(hmap, &kvp, &next); i++) { fprintf(f, " %5d: ", i); _print_kvp(f, kvp); } } fprintf(f, "Total count: %u\n", hmap_count(hmap)); } void hmap_list_names(FILE *f) { hmap_t *hmap, *tvar; if (!f) f = stdout; printf("\n**** Hashmap List ****\n"); TAILQ_FOREACH_SAFE (hmap, &hmap_registry, next, tvar) printf(" %s\n", hmap->name); } void hmap_list_by_name(FILE *f, char *name, bool sort) { hmap_t *hmap, *tvar; if (!f) f = stdout; printf("\n**** Hashmap List ****\n"); TAILQ_FOREACH_SAFE (hmap, &hmap_registry, next, tvar) if (!strncmp(name, hmap->name, strlen(name))) hmap_list(f, hmap, sort); } void hmap_list_all(FILE *f, bool sort) { hmap_t *hmap, *tvar; if (!f) f = stdout; TAILQ_FOREACH_SAFE (hmap, &hmap_registry, next, tvar) hmap_list(f, hmap, sort); } RTE_INIT_PRIO(hmap_constructor, LOG) { TAILQ_INIT(&hmap_registry); if (hmap_mutex_create(&hmap_list_mutex, PTHREAD_MUTEX_RECURSIVE_NP) < 0) HMAP_RET("mutex init(hmap_list_mutex) failed\n"); } /* Allocation tracking functions - always available */ void hmap_get_global_alloc_stats(uint64_t *alloc_count, uint64_t *realloc_count, uint64_t *total_bytes) { #ifdef HMAP_TRACK_ALLOCS if (alloc_count) *alloc_count = __atomic_load_n(&hmap_alloc_count, __ATOMIC_SEQ_CST); if (realloc_count) *realloc_count = __atomic_load_n(&hmap_realloc_count, __ATOMIC_SEQ_CST); if (total_bytes) *total_bytes = __atomic_load_n(&hmap_total_bytes, __ATOMIC_SEQ_CST); #else if (alloc_count) *alloc_count = 0; if (realloc_count) *realloc_count = 0; if (total_bytes) *total_bytes = 0; #endif } void hmap_reset_global_alloc_stats(void) { #ifdef HMAP_TRACK_ALLOCS __atomic_store_n(&hmap_alloc_count, 0, __ATOMIC_SEQ_CST); __atomic_store_n(&hmap_realloc_count, 0, __ATOMIC_SEQ_CST); __atomic_store_n(&hmap_total_bytes, 0, __ATOMIC_SEQ_CST); #endif } ================================================ FILE: lib/hmap/hmap.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright (c) 2019-2026 Intel Corporation */ #pragma once /** * @file * * This library provides an API for the hashmap data structure. * * PUBLIC API: * The hmap library exposes a minimal, type-safe public API using C11 _Generic macros: * - hmap_add_value() - Add scalar values (automatically dispatches by value type) * - hmap_add_array() - Add array values (automatically dispatches by array type) * - hmap_update_value() - Update-or-add scalar values (automatically dispatches by value type) * - hmap_update_array() - Update-or-add array values (automatically dispatches by array type) * - hmap_get_value() - Retrieve scalar values (automatically dispatches by pointer type) * - hmap_get_array() - Retrieve array values (automatically dispatches by pointer type) * * INTERNAL FUNCTIONS: * Type-specific functions (e.g., _hmap_add_string, _hmap_get_u32) are prefixed with * underscore and are not part of the public API. They should not be called directly. */ // IWYU pragma: no_include #include // for bool #include // for NULL #include // for strcasecmp #include // for uint32_t, int64_t, uint16_t, uint64_t, uint8_t #include // for FILE #include // for TAILQ_ENTRY #include // for pthread_mutex_t #include "rte_common.h" #include "rte_log.h" #include "hmap_log.h" #ifdef __cplusplus extern "C" { #endif #define HMAP_MAX_NAME_SIZE 32 /**< MAX size of HMAP name */ #define HMAP_MAX_KEY_SIZE 256 /**< Key size */ #define HMAP_STARTING_CAPACITY 32 /**< Starting capacity not exceeding max_capacity */ #define HMAP_DEFAULT_CAPACITY 1024 /**< Default starting capacity not exceeding max_capacity */ #define HMAP_USE_DEFAULT_CAPACITY 0 /**< Use the default capacity value */ struct hmap; // clang-format off /** Value type stored in an hmap key-value pair. */ typedef enum { HMAP_EMPTY_TYPE, /**< Slot is empty / unoccupied */ HMAP_STR_TYPE, /**< NUL-terminated string (char *) */ HMAP_U64_TYPE, /**< Unsigned 64-bit integer */ HMAP_U32_TYPE, /**< Unsigned 32-bit integer */ HMAP_U16_TYPE, /**< Unsigned 16-bit integer */ HMAP_U8_TYPE, /**< Unsigned 8-bit integer */ HMAP_I64_TYPE, /**< Signed 64-bit integer */ HMAP_I32_TYPE, /**< Signed 32-bit integer */ HMAP_I16_TYPE, /**< Signed 16-bit integer */ HMAP_I8_TYPE, /**< Signed 8-bit integer */ HMAP_DOUBLE_TYPE, /**< IEEE 754 double-precision float */ HMAP_POINTER_TYPE, /**< Generic void pointer */ HMAP_STR_ARRAY_TYPE, /**< Array of NUL-terminated strings (char **) */ HMAP_U64_ARRAY_TYPE, /**< Array of unsigned 64-bit integers */ HMAP_U32_ARRAY_TYPE, /**< Array of unsigned 32-bit integers */ HMAP_U16_ARRAY_TYPE, /**< Array of unsigned 16-bit integers */ HMAP_U8_ARRAY_TYPE, /**< Array of unsigned 8-bit integers */ HMAP_I64_ARRAY_TYPE, /**< Array of signed 64-bit integers */ HMAP_I32_ARRAY_TYPE, /**< Array of signed 32-bit integers */ HMAP_I16_ARRAY_TYPE, /**< Array of signed 16-bit integers */ HMAP_I8_ARRAY_TYPE, /**< Array of signed 8-bit integers */ HMAP_DOUBLE_ARRAY_TYPE, /**< Array of double-precision floats */ HMAP_PTR_ARRAY_TYPE, /**< Array of generic pointers */ HMAP_NUM_TYPES /**< Sentinel: number of defined types */ } hmap_type_t; // clang-format on /** * @brief Unified value storage using single 64-bit value * * All scalar types stored directly, strings/pointers as uintptr_t, arrays via pointer */ typedef union { uint64_t u64; /**< Universal 64-bit storage for all types */ double dval; /**< Double value (64-bit IEEE 754) */ void *ptr; /**< Generic pointer (for strings, pointers, arrays) */ } hmap_val_t; /** * @brief Key/value pair stored in the hashmap table. */ typedef struct hmap_kvp { /**< Key/value pair (implementation-defined size) */ hmap_type_t type; /**< Type of the value stored in kvp */ uint32_t count; /**< Array element count (0 for non-arrays, uses padding slot) */ char *prefix; /**< Prefix string value (hmap-managed, duplicated internally) */ char *key; /**< String key pointer (hmap-managed, duplicated internally) */ hmap_val_t v; /**< Values stored in kvp */ } hmap_kvp_t; typedef uint32_t (*hash_fn_t)(const char *prefix, const char *key); typedef int (*cmp_fn_t)(const hmap_kvp_t *kvp, const char *prefix, const char *key); typedef void (*free_fn_t)(hmap_kvp_t *kvp); typedef struct hmap_funcs { hash_fn_t hash_fn; /**< Hash function pointer */ cmp_fn_t cmp_fn; /**< Compare function pointer */ free_fn_t free_fn; /**< User kvp free routine pointer */ } hmap_funcs_t; /** * @brief A structure used to retrieve information of a hmap object */ typedef struct hmap { TAILQ_ENTRY(hmap) next; /**< List of next hmap entries */ char name[HMAP_MAX_NAME_SIZE]; /**< Name of hmap */ uint32_t capacity; /**< Total capacity */ uint32_t max_capacity; /**< Max capacity should not be exceeded */ uint32_t curr_capacity; /**< Current capacity */ pthread_mutex_t mutex; /**< Mutex for hmap */ hmap_funcs_t fns; /**< Function pointers */ hmap_kvp_t *map; /**< Pointer to the key/value table */ uint64_t alloc_count; /**< Allocation counter for this hmap */ } hmap_t; /** * @brief Create a hashmap. * * @param name * Optional name for this hashmap (used for debugging/registry). If NULL, a default name is used. * @param capacity * Maximum capacity in entries. If 0, a default is used. The internal table may start smaller and * grow up to this limit. * @param funcs * Optional pointer to hmap_funcs_t to override hash/compare/free callbacks. If NULL, defaults * are used. * @return * Pointer to a new hmap_t on success, NULL on error. */ hmap_t *hmap_create(const char *name, uint32_t capacity, hmap_funcs_t *funcs); /** * @brief Get allocation statistics for a specific hmap * * @param hmap * Pointer to the hmap structure * @return * Number of allocations performed by this hmap */ static inline uint64_t hmap_get_alloc_count(hmap_t *hmap) { return hmap ? hmap->alloc_count : 0; } /** * @brief Destroy a hashmap. * * @param hmap * Pointer to the hmap structure * @return * 0 - successful or -1 on error */ int hmap_destroy(hmap_t *hmap); /** * @brief Destroy a hashmap by name. * * @param name * The name of the hmap * @return * 0 on success or -1 on error. */ int hmap_destroy_by_name(const char *name); /** * @brief Lookup an entry by prefix/key. * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string (can be NULL for global keys) * @param key * The key to search for in the hashmap * @return * Pointer to the entry if found, NULL if not found or invalid arguments. * * @note The returned pointer is owned by the hmap and is only valid while the entry exists. */ hmap_kvp_t *hmap_kvp_lookup(hmap_t *hmap, const char *prefix, const char *key); /** * @brief Lookup an entry by prefix/key and optionally copy out its raw value. * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string (can be NULL for global keys) * @param key * The key to search for in the hashmap * @param val * Pointer to hmap_val_t to return value can be NULL for no return value * @return * 0 if found, -1 if not found or invalid arguments. */ int hmap_lookup(hmap_t *hmap, const char *prefix, const char *key, hmap_val_t *val); /** * @brief Add a key/value pair to the hashmap. * * @param hmap * Pointer to the hmap structure * @param type * The hmap_type_t type for the value pointer * @param prefix * The prefix string to locate in the hashmap table (can be NULL) * @param key * The key value string to add * @param val * The value pointer to store with the key/value entry. * @return * 0 on success or -1 on error (including duplicate prefix/key). */ int hmap_add(hmap_t *hmap, hmap_type_t type, const char *prefix, const char *key, hmap_val_t *val); /** * @brief Update a key/value pair if it exists, otherwise add it. * * @param hmap * Pointer to the hmap structure * @param type * The hmap_type_t type for the value pointer * @param prefix * The prefix string to locate in the hashmap table (can be NULL) * @param key * The key value string to update * @param val * The value pointer to store with the key/value entry. * @return * 0 on success or -1 on error. */ int hmap_update(hmap_t *hmap, hmap_type_t type, const char *prefix, const char *key, hmap_val_t *val); /** * @brief Update the value for an existing key/value pair returned by hmap_kvp_lookup(). * * @param hmap * Pointer to hmap structure * @param kvp * The key/value pair structure to update * @param val * The pointer to the new value. * @return * 0 on success, -1 on error. */ int hmap_kvp_update(hmap_t *hmap, hmap_kvp_t *kvp, hmap_val_t *val); /** * @brief Internal type-specific add helpers. * * These inline functions are used by the public `hmap_add_value()` and `hmap_add_array()` macros. * They are not intended to be called directly. */ /** * @brief Add a string value to the hmap. (internal) * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string to locate in the hashmap table * @param key * The key value string to add * @param val * The value to be stored in the hmap table. * @return * 0 on success or -1 on error. */ static inline int _hmap_add_string(hmap_t *hmap, const char *prefix, const char *key, const char *val) { hmap_val_t v = {.ptr = (void *)(uintptr_t)val}; return hmap_add(hmap, HMAP_STR_TYPE, prefix, key, &v); } /** * @brief Internal type-specific update helpers. * * These inline functions are used by the public `hmap_update_value()` and * `hmap_update_array()` macros. They are not intended to be called directly. */ /** @brief Update-or-add a string value in the hmap. (internal) */ static inline int _hmap_update_string(hmap_t *hmap, const char *prefix, const char *key, const char *val) { hmap_val_t v = {.ptr = (void *)(uintptr_t)val}; return hmap_update(hmap, HMAP_STR_TYPE, prefix, key, &v); } /** @brief Update-or-add a uint64_t value in the hmap. (internal) */ static inline int _hmap_update_u64(hmap_t *hmap, const char *prefix, const char *key, uint64_t val) { hmap_val_t v = {.u64 = val}; return hmap_update(hmap, HMAP_U64_TYPE, prefix, key, &v); } /** @brief Update-or-add a uint32_t value in the hmap. (internal) */ static inline int _hmap_update_u32(hmap_t *hmap, const char *prefix, const char *key, uint32_t val) { hmap_val_t v = {.u64 = (uint64_t)val}; return hmap_update(hmap, HMAP_U32_TYPE, prefix, key, &v); } /** @brief Update-or-add a uint16_t value in the hmap. (internal) */ static inline int _hmap_update_u16(hmap_t *hmap, const char *prefix, const char *key, uint16_t val) { hmap_val_t v = {.u64 = (uint64_t)val}; return hmap_update(hmap, HMAP_U16_TYPE, prefix, key, &v); } /** @brief Update-or-add a uint8_t value in the hmap (also used for booleans). (internal) */ static inline int _hmap_update_u8(hmap_t *hmap, const char *prefix, const char *key, uint8_t val) { hmap_val_t v = {.u64 = (uint64_t)val}; return hmap_update(hmap, HMAP_U8_TYPE, prefix, key, &v); } /** @brief Update-or-add an int64_t value in the hmap. (internal) */ static inline int _hmap_update_i64(hmap_t *hmap, const char *prefix, const char *key, int64_t val) { hmap_val_t v = {.u64 = (uint64_t)val}; return hmap_update(hmap, HMAP_I64_TYPE, prefix, key, &v); } /** @brief Update-or-add an int32_t value in the hmap. (internal) */ static inline int _hmap_update_i32(hmap_t *hmap, const char *prefix, const char *key, int32_t val) { hmap_val_t v = {.u64 = (uint64_t)(uint32_t)val}; return hmap_update(hmap, HMAP_I32_TYPE, prefix, key, &v); } /** @brief Update-or-add an int16_t value in the hmap. (internal) */ static inline int _hmap_update_i16(hmap_t *hmap, const char *prefix, const char *key, int16_t val) { hmap_val_t v = {.u64 = (uint64_t)(uint16_t)val}; return hmap_update(hmap, HMAP_I16_TYPE, prefix, key, &v); } /** @brief Update-or-add an int8_t value in the hmap. (internal) */ static inline int _hmap_update_i8(hmap_t *hmap, const char *prefix, const char *key, int8_t val) { hmap_val_t v = {.u64 = (uint64_t)(uint8_t)val}; return hmap_update(hmap, HMAP_I8_TYPE, prefix, key, &v); } /** @brief Update-or-add a double value in the hmap. (internal) */ static inline int _hmap_update_double(hmap_t *hmap, const char *prefix, const char *key, double val) { hmap_val_t v = {.dval = val}; return hmap_update(hmap, HMAP_DOUBLE_TYPE, prefix, key, &v); } /** @brief Update-or-add a pointer value in the hmap. (internal) */ static inline int _hmap_update_pointer(hmap_t *hmap, const char *prefix, const char *key, void *val) { hmap_val_t v = {.ptr = val}; return hmap_update(hmap, HMAP_POINTER_TYPE, prefix, key, &v); } /** @brief Update-or-add a string array in the hmap. (internal) */ static inline int _hmap_update_string_array(hmap_t *hmap, const char *prefix, const char *key, char **val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_update(hmap, HMAP_STR_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Update-or-add a uint64_t array in the hmap. (internal) */ static inline int _hmap_update_u64_array(hmap_t *hmap, const char *prefix, const char *key, uint64_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_update(hmap, HMAP_U64_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Update-or-add a uint32_t array in the hmap. (internal) */ static inline int _hmap_update_u32_array(hmap_t *hmap, const char *prefix, const char *key, uint32_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_update(hmap, HMAP_U32_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Update-or-add a uint16_t array in the hmap. (internal) */ static inline int _hmap_update_u16_array(hmap_t *hmap, const char *prefix, const char *key, uint16_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_update(hmap, HMAP_U16_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Update-or-add a uint8_t array in the hmap. (internal) */ static inline int _hmap_update_u8_array(hmap_t *hmap, const char *prefix, const char *key, uint8_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_update(hmap, HMAP_U8_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Update-or-add an int64_t array in the hmap. (internal) */ static inline int _hmap_update_i64_array(hmap_t *hmap, const char *prefix, const char *key, int64_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_update(hmap, HMAP_I64_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Update-or-add an int32_t array in the hmap. (internal) */ static inline int _hmap_update_i32_array(hmap_t *hmap, const char *prefix, const char *key, int32_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_update(hmap, HMAP_I32_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Update-or-add an int16_t array in the hmap. (internal) */ static inline int _hmap_update_i16_array(hmap_t *hmap, const char *prefix, const char *key, int16_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_update(hmap, HMAP_I16_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Update-or-add an int8_t array in the hmap. (internal) */ static inline int _hmap_update_i8_array(hmap_t *hmap, const char *prefix, const char *key, int8_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_update(hmap, HMAP_I8_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Update-or-add a double array in the hmap. (internal) */ static inline int _hmap_update_double_array(hmap_t *hmap, const char *prefix, const char *key, double *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_update(hmap, HMAP_DOUBLE_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Update-or-add a pointer array in the hmap. (internal) */ static inline int _hmap_update_pointer_array(hmap_t *hmap, const char *prefix, const char *key, void **val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_update(hmap, HMAP_PTR_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Add a uint64_t value to the hmap. (internal) */ static inline int _hmap_add_u64(hmap_t *hmap, const char *prefix, const char *key, uint64_t val) { hmap_val_t v = {.u64 = val}; return hmap_add(hmap, HMAP_U64_TYPE, prefix, key, &v); } /** @brief Add a uint32_t value to the hmap. (internal) */ static inline int _hmap_add_u32(hmap_t *hmap, const char *prefix, const char *key, uint32_t val) { hmap_val_t v = {.u64 = (uint64_t)val}; return hmap_add(hmap, HMAP_U32_TYPE, prefix, key, &v); } /** @brief Add a uint16_t value to the hmap. (internal) */ static inline int _hmap_add_u16(hmap_t *hmap, const char *prefix, const char *key, uint16_t val) { hmap_val_t v = {.u64 = (uint64_t)val}; return hmap_add(hmap, HMAP_U16_TYPE, prefix, key, &v); } /** @brief Add a uint8_t value to the hmap (also used for booleans). (internal) */ static inline int _hmap_add_u8(hmap_t *hmap, const char *prefix, const char *key, uint8_t val) { hmap_val_t v = {.u64 = (uint64_t)val}; return hmap_add(hmap, HMAP_U8_TYPE, prefix, key, &v); } /** @brief Add an int64_t value to the hmap. (internal) */ static inline int _hmap_add_i64(hmap_t *hmap, const char *prefix, const char *key, int64_t val) { hmap_val_t v = {.u64 = (uint64_t)val}; return hmap_add(hmap, HMAP_I64_TYPE, prefix, key, &v); } /** @brief Add an int32_t value to the hmap. (internal) */ static inline int _hmap_add_i32(hmap_t *hmap, const char *prefix, const char *key, int32_t val) { hmap_val_t v = {.u64 = (uint64_t)(uint32_t)val}; return hmap_add(hmap, HMAP_I32_TYPE, prefix, key, &v); } /** @brief Add an int16_t value to the hmap. (internal) */ static inline int _hmap_add_i16(hmap_t *hmap, const char *prefix, const char *key, int16_t val) { hmap_val_t v = {.u64 = (uint64_t)(uint16_t)val}; return hmap_add(hmap, HMAP_I16_TYPE, prefix, key, &v); } /** @brief Add an int8_t value to the hmap. (internal) */ static inline int _hmap_add_i8(hmap_t *hmap, const char *prefix, const char *key, int8_t val) { hmap_val_t v = {.u64 = (uint64_t)(uint8_t)val}; return hmap_add(hmap, HMAP_I8_TYPE, prefix, key, &v); } /** @brief Add a double value to the hmap. (internal) */ static inline int _hmap_add_double(hmap_t *hmap, const char *prefix, const char *key, double val) { hmap_val_t v = {.dval = val}; return hmap_add(hmap, HMAP_DOUBLE_TYPE, prefix, key, &v); } /** @brief Add a pointer value to the hmap. (internal) */ static inline int _hmap_add_pointer(hmap_t *hmap, const char *prefix, const char *key, void *val) { hmap_val_t v = {.ptr = val}; return hmap_add(hmap, HMAP_POINTER_TYPE, prefix, key, &v); } /** @brief Add a string array to the hmap. (internal) */ static inline int _hmap_add_string_array(hmap_t *hmap, const char *prefix, const char *key, char **val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_add(hmap, HMAP_STR_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Add a uint64_t array to the hmap. (internal) */ static inline int _hmap_add_u64_array(hmap_t *hmap, const char *prefix, const char *key, uint64_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_add(hmap, HMAP_U64_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Add a uint32_t array to the hmap. (internal) */ static inline int _hmap_add_u32_array(hmap_t *hmap, const char *prefix, const char *key, uint32_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_add(hmap, HMAP_U32_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Add a uint16_t array to the hmap. (internal) */ static inline int _hmap_add_u16_array(hmap_t *hmap, const char *prefix, const char *key, uint16_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_add(hmap, HMAP_U16_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** * @brief Add a uint8_t array to the hmap. (internal) * * Note: Boolean arrays are stored as uint8_t arrays (0=false, 1=true). * Use this function to store boolean arrays. * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key value string * @param val * Pointer to the uint8_t array * @param count * Number of elements in the array * @return * 0 - successful or -1 - failed */ static inline int _hmap_add_u8_array(hmap_t *hmap, const char *prefix, const char *key, uint8_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_add(hmap, HMAP_U8_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Add an int64_t array to the hmap. (internal) */ static inline int _hmap_add_i64_array(hmap_t *hmap, const char *prefix, const char *key, int64_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_add(hmap, HMAP_I64_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Add an int32_t array to the hmap. (internal) */ static inline int _hmap_add_i32_array(hmap_t *hmap, const char *prefix, const char *key, int32_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_add(hmap, HMAP_I32_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Add an int16_t array to the hmap. (internal) */ static inline int _hmap_add_i16_array(hmap_t *hmap, const char *prefix, const char *key, int16_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_add(hmap, HMAP_I16_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Add an int8_t array to the hmap. (internal) */ static inline int _hmap_add_i8_array(hmap_t *hmap, const char *prefix, const char *key, int8_t *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_add(hmap, HMAP_I8_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Add a double array to the hmap. (internal) */ static inline int _hmap_add_double_array(hmap_t *hmap, const char *prefix, const char *key, double *val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_add(hmap, HMAP_DOUBLE_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** @brief Add a pointer array to the hmap. (internal) */ static inline int _hmap_add_pointer_array(hmap_t *hmap, const char *prefix, const char *key, void **val, uint32_t count) { hmap_val_t v = {.ptr = val}; hmap_kvp_t *kvp; int ret = hmap_add(hmap, HMAP_PTR_ARRAY_TYPE, prefix, key, &v); if (ret == 0) { kvp = hmap_kvp_lookup(hmap, prefix, key); if (kvp) kvp->count = count; } return ret; } /** * @brief Delete a hashmap entry * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string for the hashmap * @param key * The Key to search for in the table * @return * -1 on error or 0 on success */ int hmap_del(hmap_t *hmap, const char *prefix, const char *key); /** * @brief Iterate over entries in the hashmap. * * @param hmap * Pointer to the hmap structure * @param _kvp * The address to place the key/value pair structure pointer * @param next * The next entry to iterate as an index value. Initialize to 0 before the first call. * @return * 1 if an entry is returned, 0 when there are no more entries (or on invalid arguments). * * Example: * uint32_t next = 0; * hmap_kvp_t *kvp; * while (hmap_iterate(hmap, &kvp, &next)) { * // use kvp * } */ int hmap_iterate(hmap_t *hmap, hmap_kvp_t **_kvp, uint32_t *next); /** * @brief List (dump) out all of the entries in a hashmap. * Renamed from hmap_dump() to hmap_list() to better reflect intent. * * @param f * The file descriptor to use for output of the text * @param hmap * Pointer to the hmap structure to list * @param sort * If true sort the output by prefix/key before listing */ void hmap_list(FILE *f, hmap_t *hmap, bool sort); /** * @brief Get the current table capacity (number of slots). * * @param hmap * Pointer to the hmap structure. * @return * Current table capacity. */ static inline uint32_t hmap_capacity(hmap_t *hmap) { return hmap->capacity; } /** * @brief Get the number of populated entries currently stored. * * @param hmap * Pointer to the hmap structure. * @return * Number of active entries. */ static inline uint32_t hmap_count(hmap_t *hmap) { return hmap->curr_capacity; } /** * @brief Get the set of hmap function pointers. * * @param hmap * The hmap structure pointer * @return * NULL on error or pointer to the hmap function structure. */ hmap_funcs_t *hmap_get_funcs(hmap_t *hmap); /** * @brief Set hmap callback functions. * * @param hmap * Pointer to the hmap structure * @param funcs * Pointer to a set of function pointers. Any NULL members fall back to defaults. * @return * 0 on success, -1 on error. */ int hmap_set_funcs(hmap_t *hmap, hmap_funcs_t *funcs); /** * @brief Get a key/value pair from the hmap if it exists. (internal) * * @param hmap * Pointer to the hmap structure. * @param prefix * The prefix string pointer (can be NULL for global keys). * @param key * The key string. * @param type * Expected type of the entry. * @return * Pointer to matching key/value pair or NULL if not found or type mismatch. */ static inline hmap_kvp_t * __get_kvp(hmap_t *hmap, const char *prefix, const char *key, hmap_type_t type) { hmap_kvp_t *kvp; if (!hmap) HMAP_NULL_RET("get failed - hmap not defined %s(%s)\n", prefix ? prefix : "", key); kvp = hmap_kvp_lookup(hmap, prefix, key); if (!kvp) return kvp; else if (kvp->type != type) HMAP_NULL_RET("wrong type for %s(%s)\n", prefix ? prefix : "", key); return kvp; } /** * @brief Get a uint64_t value from the hmap. (internal) * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key value string to get the uint64_t value. * @param val * The location to store the value. * @return * 0 - successful or -1 - failed */ static inline int _hmap_get_u64(hmap_t *hmap, const char *prefix, const char *key, uint64_t *val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_U64_TYPE); if (!kvp) return -1; if (val) *val = kvp->v.u64; return 0; } /** * @brief Get a uint32_t value from the hmap. (internal) * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key value string to get the uint32_t value. * @param val * The location to store the value. * @return * 0 - successful or -1 - failed */ static inline int _hmap_get_u32(hmap_t *hmap, const char *prefix, const char *key, uint32_t *val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_U32_TYPE); if (!kvp) return -1; if (val) *val = (uint32_t)kvp->v.u64; return 0; } /** * @brief Get a uint16_t value from the hmap. (internal) * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key value string to get the uint16_t value. * @param val * The location to store the value. * @return * 0 - successful or -1 - failed */ static inline int _hmap_get_u16(hmap_t *hmap, const char *prefix, const char *key, uint16_t *val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_U16_TYPE); if (!kvp) return -1; if (val) *val = (uint16_t)kvp->v.u64; return 0; } /** * @brief Get a uint8_t value from the hmap. (internal) * * Note: Boolean values are stored as uint8_t (0=false, 1=true). * Use this function to retrieve boolean values. * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key value string to get the uint8_t value. * @param val * The location to store the value. * @return * 0 - successful or -1 - failed */ static inline int _hmap_get_u8(hmap_t *hmap, const char *prefix, const char *key, uint8_t *val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_U8_TYPE); if (!kvp) return -1; if (val) *val = (uint8_t)kvp->v.u64; return 0; } /** * @brief Get a number value from the hmap as a int64_t. (internal) * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key value string to get the number value. * @param val * The location to store the value. * @return * 0 - successful or -1 - failed */ static inline int _hmap_get_i64(hmap_t *hmap, const char *prefix, const char *key, int64_t *val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_I64_TYPE); if (!kvp) return -1; if (val) *val = (int64_t)kvp->v.u64; return 0; } /** * @brief Get a number value from the hmap as a int32_t. (internal) * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key value string to get the number value. * @param val * The location to store the value. * @return * 0 - successful or -1 - failed */ static inline int _hmap_get_i32(hmap_t *hmap, const char *prefix, const char *key, int32_t *val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_I32_TYPE); if (!kvp) return -1; if (val) *val = (int32_t)kvp->v.u64; return 0; } /** * @brief Get a number value from the hmap as a int16_t. (internal) * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key value string to get the number value. * @param val * The location to store the value. * @return * 0 - successful or -1 - failed */ static inline int _hmap_get_i16(hmap_t *hmap, const char *prefix, const char *key, int16_t *val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_I16_TYPE); if (!kvp) return -1; if (val) *val = (int16_t)kvp->v.u64; return 0; } /** * @brief Get a number value from the hmap as a int8_t. (internal) * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key value string to get the number value. * @param val * The location to store the value. * @return * 0 - successful or -1 - failed */ static inline int _hmap_get_i8(hmap_t *hmap, const char *prefix, const char *key, int8_t *val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_I8_TYPE); if (!kvp) return -1; if (val) *val = (int8_t)kvp->v.u64; return 0; } /** @brief Get a double value from the hmap. (internal) */ static inline int _hmap_get_double(hmap_t *hmap, const char *prefix, const char *key, double *val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_DOUBLE_TYPE); if (!kvp) return -1; if (val) *val = kvp->v.dval; return 0; } /** * @brief Get a string value from the hmap. (internal) * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key value string to get the string value. * @param val * A pointer to the return value or NULL if no return value is needed. * @return * 0 on success, -1 on error. */ static inline int _hmap_get_string(hmap_t *hmap, const char *prefix, const char *key, char **val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_STR_TYPE); if (!kvp) return -1; if (val) *val = (char *)kvp->v.ptr; return 0; } /** * @brief Get a pointer value from the hmap. (internal) * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key string. * @param val * A pointer to the return value or NULL if no return value is needed. * @return * 0 - successful or -1 on error */ static inline int _hmap_get_pointer(hmap_t *hmap, const char *prefix, const char *key, void **val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_POINTER_TYPE); if (!kvp) return -1; if (val) *val = kvp->v.ptr; return 0; } /** * @brief Get a string array from the hmap. (internal) * * @param hmap * Pointer to the hmap structure. * @param prefix * The prefix string pointer (can be NULL for global keys). * @param key * The key string. * @param val * Pointer to store the array pointer. * @return * Number of elements on success, -1 on failure. */ static inline int _hmap_get_string_array(hmap_t *hmap, const char *prefix, const char *key, char ***val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_STR_ARRAY_TYPE); if (!kvp) return -1; if (val) *val = (char **)kvp->v.ptr; return kvp->count; } /** @brief Get a uint64_t array from the hmap. (internal) */ static inline int _hmap_get_u64_array(hmap_t *hmap, const char *prefix, const char *key, uint64_t **val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_U64_ARRAY_TYPE); if (!kvp) return -1; if (val) *val = (uint64_t *)kvp->v.ptr; return kvp->count; } /** @brief Get a uint32_t array from the hmap. (internal) */ static inline int _hmap_get_u32_array(hmap_t *hmap, const char *prefix, const char *key, uint32_t **val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_U32_ARRAY_TYPE); if (!kvp) return -1; if (val) *val = (uint32_t *)kvp->v.ptr; return kvp->count; } /** @brief Get a uint16_t array from the hmap. (internal) */ static inline int _hmap_get_u16_array(hmap_t *hmap, const char *prefix, const char *key, uint16_t **val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_U16_ARRAY_TYPE); if (!kvp) return -1; if (val) *val = (uint16_t *)kvp->v.ptr; return kvp->count; } /** * @brief Get a uint8_t array from the hmap. (internal) * * Note: Boolean arrays are stored as uint8_t arrays (0=false, 1=true). * Use this function to retrieve boolean arrays. * * @param hmap * Pointer to the hmap structure * @param prefix * The prefix string pointer, can be NULL if a global value * @param key * The key value string * @param val * Pointer to store the array pointer * @return * Array count on success, -1 on failure */ static inline int _hmap_get_u8_array(hmap_t *hmap, const char *prefix, const char *key, uint8_t **val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_U8_ARRAY_TYPE); if (!kvp) return -1; if (val) *val = (uint8_t *)kvp->v.ptr; return kvp->count; } /** @brief Get an int64_t array from the hmap. (internal) */ static inline int _hmap_get_i64_array(hmap_t *hmap, const char *prefix, const char *key, int64_t **val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_I64_ARRAY_TYPE); if (!kvp) return -1; if (val) *val = (int64_t *)kvp->v.ptr; return kvp->count; } /** @brief Get an int32_t array from the hmap. (internal) */ static inline int _hmap_get_i32_array(hmap_t *hmap, const char *prefix, const char *key, int32_t **val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_I32_ARRAY_TYPE); if (!kvp) return -1; if (val) *val = (int32_t *)kvp->v.ptr; return kvp->count; } /** @brief Get an int16_t array from the hmap. (internal) */ static inline int _hmap_get_i16_array(hmap_t *hmap, const char *prefix, const char *key, int16_t **val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_I16_ARRAY_TYPE); if (!kvp) return -1; if (val) *val = (int16_t *)kvp->v.ptr; return kvp->count; } /** @brief Get an int8_t array from the hmap. (internal) */ static inline int _hmap_get_i8_array(hmap_t *hmap, const char *prefix, const char *key, int8_t **val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_I8_ARRAY_TYPE); if (!kvp) return -1; if (val) *val = (int8_t *)kvp->v.ptr; return kvp->count; } /** @brief Get a double array from the hmap. (internal) */ static inline int _hmap_get_double_array(hmap_t *hmap, const char *prefix, const char *key, double **val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_DOUBLE_ARRAY_TYPE); if (!kvp) return -1; if (val) *val = (double *)kvp->v.ptr; return kvp->count; } /** @brief Get a pointer array from the hmap. (internal) */ static inline int _hmap_get_pointer_array(hmap_t *hmap, const char *prefix, const char *key, void ***val) { hmap_kvp_t *kvp = __get_kvp(hmap, prefix, key, HMAP_PTR_ARRAY_TYPE); if (!kvp) return -1; if (val) *val = (void **)kvp->v.ptr; return kvp->count; } /** * @brief List all of the hashmaps * * @param f * The file descriptor to dump the text data. */ void hmap_list_names(FILE *f); /** * @brief Dump a hashmap by name * * @param f * The file descriptor to dump the text data. * @param name * The name of the hashmap to dump * @param sort * if true then sort the output */ void hmap_list_by_name(FILE *f, char *name, bool sort); /** * @brief Dump all of the hmap lists * * @param f * The file descriptor to dump the text data. * @param sort * if true then sort the output */ void hmap_list_all(FILE *f, bool sort); /** * @brief Type-safe generic getter macro for scalar values. * * This macro uses C11 _Generic to automatically dispatch to the correct * internal _hmap_get_*() function based on the type of the value pointer. * * @param hmap hmap handle * @param prefix Prefix string (can be NULL) * @param key Key string * @param value Pointer to store the value (type determines which function is called) * @return 0 on success, -1 on failure * * Example: * uint32_t count; * hmap_get_value(hmap, "config", "count", &count); // Calls _hmap_get_u32 * * uint8_t enabled; // boolean stored as uint8_t (0=false, 1=true) * hmap_get_value(hmap, NULL, "enabled", &enabled); // Calls _hmap_get_u8 */ #define hmap_get_value(hmap, prefix, key, value) \ _Generic((value), \ char **: _hmap_get_string, \ uint64_t *: _hmap_get_u64, \ uint32_t *: _hmap_get_u32, \ uint16_t *: _hmap_get_u16, \ uint8_t *: _hmap_get_u8, \ int64_t *: _hmap_get_i64, \ int32_t *: _hmap_get_i32, \ int16_t *: _hmap_get_i16, \ int8_t *: _hmap_get_i8, \ double *: _hmap_get_double, \ void **: _hmap_get_pointer)((hmap), (prefix), (key), (value)) /** * @brief Type-safe generic getter macro for array values. * * This macro uses C11 _Generic to automatically dispatch to the correct * internal _hmap_get_*_array() function based on the type of the value pointer. * Returns the array count and populates the pointer. * * @param hmap hmap handle * @param prefix Prefix string (can be NULL) * @param key Key string * @param value Pointer to store array pointer (type determines which function is called) * @return Array count on success, -1 on failure * * Example: * uint32_t *ports; * int count = hmap_get_array(hmap, "config", "ports", &ports); // Calls _hmap_get_u32_array */ #define hmap_get_array(hmap, prefix, key, value) \ _Generic((value), \ char ***: _hmap_get_string_array, \ uint64_t **: _hmap_get_u64_array, \ uint32_t **: _hmap_get_u32_array, \ uint16_t **: _hmap_get_u16_array, \ uint8_t **: _hmap_get_u8_array, \ int64_t **: _hmap_get_i64_array, \ int32_t **: _hmap_get_i32_array, \ int16_t **: _hmap_get_i16_array, \ int8_t **: _hmap_get_i8_array, \ double **: _hmap_get_double_array, \ void ***: _hmap_get_pointer_array)((hmap), (prefix), (key), (value)) /** * @brief Type-safe generic setter macro for scalar values. * * This macro uses C11 _Generic to automatically dispatch to the correct * internal _hmap_add_*() function based on the type of the value. * * @param hmap hmap handle * @param prefix Prefix string (can be NULL) * @param key Key string * @param value Value to store (type determines which function is called) * @return 0 on success, -1 on failure * * Example: * hmap_add_value(hmap, "config", "port", 8080); // Calls _hmap_add_u32 * hmap_add_value(hmap, "app", "name", "MyApp"); // Calls _hmap_add_string * hmap_add_value(hmap, "flags", "enabled", 1); // Calls _hmap_add_i32 * hmap_add_value(hmap, "stats", "ratio", 3.14); // Calls _hmap_add_double */ #define hmap_add_value(hmap, prefix, key, value) \ _Generic((value), \ char *: _hmap_add_string, \ const char *: _hmap_add_string, \ uint64_t: _hmap_add_u64, \ uint32_t: _hmap_add_u32, \ uint16_t: _hmap_add_u16, \ uint8_t: _hmap_add_u8, \ int64_t: _hmap_add_i64, \ int32_t: _hmap_add_i32, \ int16_t: _hmap_add_i16, \ int8_t: _hmap_add_i8, \ double: _hmap_add_double, \ float: _hmap_add_double, \ void *: _hmap_add_pointer, \ default: _hmap_add_i32)((hmap), (prefix), (key), (value)) /** * @brief Type-safe generic update macro for scalar values. * * This macro uses C11 _Generic to automatically dispatch to the correct * internal _hmap_update_*() function based on the type of the value. * * Semantics match hmap_update(): update if key exists, otherwise add. * * @param hmap hmap handle * @param prefix Prefix string (can be NULL) * @param key Key string * @param value Value to store (type determines which function is called) * @return 0 on success, -1 on failure */ #define hmap_update_value(hmap, prefix, key, value) \ _Generic((value), \ char *: _hmap_update_string, \ const char *: _hmap_update_string, \ uint64_t: _hmap_update_u64, \ uint32_t: _hmap_update_u32, \ uint16_t: _hmap_update_u16, \ uint8_t: _hmap_update_u8, \ int64_t: _hmap_update_i64, \ int32_t: _hmap_update_i32, \ int16_t: _hmap_update_i16, \ int8_t: _hmap_update_i8, \ double: _hmap_update_double, \ float: _hmap_update_double, \ void *: _hmap_update_pointer, \ default: _hmap_update_i32)((hmap), (prefix), (key), (value)) /** * @brief Type-safe generic setter macro for array values. * * This macro uses C11 _Generic to automatically dispatch to the correct * internal _hmap_add_*_array() function based on the type of the array pointer. * * @param hmap hmap handle * @param prefix Prefix string (can be NULL) * @param key Key string * @param array Pointer to array data (type determines which function is called) * @param count Number of elements in the array * @return 0 on success, -1 on failure * * Example: * uint32_t ports[] = {8080, 8081, 8082}; * hmap_add_array(hmap, "config", "ports", ports, 3); // Calls _hmap_add_u32_array * * char *names[] = {"alice", "bob"}; * hmap_add_array(hmap, "users", "names", names, 2); // Calls _hmap_add_string_array */ #define hmap_add_array(hmap, prefix, key, array, count) \ _Generic((array), \ char **: _hmap_add_string_array, \ uint64_t *: _hmap_add_u64_array, \ uint32_t *: _hmap_add_u32_array, \ uint16_t *: _hmap_add_u16_array, \ uint8_t *: _hmap_add_u8_array, \ int64_t *: _hmap_add_i64_array, \ int32_t *: _hmap_add_i32_array, \ int16_t *: _hmap_add_i16_array, \ int8_t *: _hmap_add_i8_array, \ double *: _hmap_add_double_array, \ void **: _hmap_add_pointer_array)((hmap), (prefix), (key), (array), (count)) /** * @brief Type-safe generic update macro for array values. * * This macro uses C11 _Generic to automatically dispatch to the correct * internal _hmap_update_*_array() function based on the type of the array pointer. * * Semantics match hmap_update(): update if key exists, otherwise add. * * @param hmap hmap handle * @param prefix Prefix string (can be NULL) * @param key Key string * @param array Pointer to array data (type determines which function is called) * @param count Number of elements in the array * @return 0 on success, -1 on failure */ #define hmap_update_array(hmap, prefix, key, array, count) \ _Generic((array), \ char **: _hmap_update_string_array, \ uint64_t *: _hmap_update_u64_array, \ uint32_t *: _hmap_update_u32_array, \ uint16_t *: _hmap_update_u16_array, \ uint8_t *: _hmap_update_u8_array, \ int64_t *: _hmap_update_i64_array, \ int32_t *: _hmap_update_i32_array, \ int16_t *: _hmap_update_i16_array, \ int8_t *: _hmap_update_i8_array, \ double *: _hmap_update_double_array, \ void **: _hmap_update_pointer_array)((hmap), (prefix), (key), (array), (count)) /** * @brief Get global allocation statistics * * Returns zeros if HMAP_TRACK_ALLOCS is not defined at compile time * * @param alloc_count * Pointer to store total allocation count (can be NULL) * @param realloc_count * Pointer to store reallocation count (can be NULL) * @param total_bytes * Pointer to store total bytes allocated (can be NULL) */ void hmap_get_global_alloc_stats(uint64_t *alloc_count, uint64_t *realloc_count, uint64_t *total_bytes); /** * @brief Reset global allocation statistics * * No effect if HMAP_TRACK_ALLOCS is not defined at compile time */ void hmap_reset_global_alloc_stats(void); #ifdef __cplusplus } #endif ================================================ FILE: lib/hmap/hmap_helper.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright (c) 2022-2026 Intel Corporation */ #pragma once /** * @file * * HMAP internal helper routines: mutex creation/destruction, the global * hmap registry, per-hmap locking, hash computation, key comparison, and * KVP memory management. */ #include #include #include #include "hmap_log.h" #ifdef __cplusplus extern "C" { #endif /** Global TAILQ registry of all created hmap instances. */ static TAILQ_HEAD(hmap_hmap_list, hmap) hmap_registry; /** Mutex protecting @ref hmap_registry. */ static pthread_mutex_t hmap_list_mutex; /** * Helper routine to create a mutex with a specific type. * * @param mutex * The pointer to the mutex to create. * @param flags * The attribute flags used to create the mutex i.e. recursive attribute * @return * 0 on success or -1 on failure errno is set */ static inline int hmap_mutex_create(pthread_mutex_t *mutex, int flags) { pthread_mutexattr_t attr; int inited = 0, ret = EFAULT; if (!mutex) goto err; #define __do(_exp) \ do { \ ret = _exp; \ if (ret) \ goto err; \ } while (0 /* CONSTCOND */) __do(pthread_mutexattr_init(&attr)); inited = 1; __do(pthread_mutexattr_settype(&attr, flags)); __do(pthread_mutex_init(mutex, &attr)); __do(pthread_mutexattr_destroy(&attr)); #undef __do return 0; err: if (inited) { /* Do not lose the previous error value */ if (pthread_mutexattr_destroy(&attr)) HMAP_DEBUG("Failed to destroy mutex attribute, but is not the root cause\n"); } errno = ret; return -1; } /** * Destroy a mutex * * @param mutex * Pointer to mutex to destroy. * @return * 0 on success and -1 on error with errno set. */ static inline int hmap_mutex_destroy(pthread_mutex_t *mutex) { int ret = 0; if (mutex) ret = pthread_mutex_destroy(mutex); errno = ret; return (ret != 0) ? -1 : 0; } /** * Acquire the global hmap registry mutex. * * Logs an error if the lock cannot be obtained. */ static inline void hmap_list_lock(void) { int ret = pthread_mutex_lock(&hmap_list_mutex); if (ret) HMAP_LOG("failed: %s\n", strerror(ret)); } /** * Release the global hmap registry mutex. * * Logs a warning if the unlock fails. */ static inline void hmap_list_unlock(void) { int ret = pthread_mutex_unlock(&hmap_list_mutex); if (ret) HMAP_WARN("failed: %s\n", strerror(ret)); } /** * Acquire the per-hmap mutex. * * @param hmap * Pointer to the hmap whose mutex should be locked. */ static inline void hmap_lock(hmap_t *hmap) { int ret = pthread_mutex_lock(&hmap->mutex); if (ret) HMAP_WARN("failed: %s\n", strerror(ret)); } /** * Release the per-hmap mutex. * * @param hmap * Pointer to the hmap whose mutex should be unlocked. */ static inline void hmap_unlock(hmap_t *hmap) { int ret = pthread_mutex_unlock(&hmap->mutex); if (ret) HMAP_WARN("failed: %s\n", strerror(ret)); } /** * Default hash function used when no custom hash is provided. * * Computes a Jenkins-style one-at-a-time hash over the concatenation of * @p prefix (if non-NULL) and @p key. * * @param prefix * Optional namespace prefix string, or NULL. * @param key * Key string to hash. * @return * 32-bit hash value. */ static uint32_t _hmap_hash(const char *prefix, const char *key) { const char *k; uint32_t hash; hash = 0; if (prefix) { k = prefix; for (int i = 0; k[i]; i++) { hash += k[i]; hash += (hash << 10); hash ^= (hash >> 6); } } k = key; for (int i = 0; i < k[i]; i++) { hash += k[i]; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } /** * Default key comparison function used when no custom comparator is provided. * * Returns non-zero if @p kvp matches the given prefix and key pair, zero * otherwise. Both prefix and kvp->prefix must be NULL or non-NULL together * for a match to occur. * * @param kvp * Key-value pair to compare against. * @param prefix * Optional namespace prefix, or NULL. * @param key * Key string to match. * @return * Non-zero on match, 0 on mismatch. */ static inline int _hmap_cmp(const hmap_kvp_t *kvp, const char *prefix, const char *key) { if (prefix && kvp->prefix) return strcmp(prefix, kvp->prefix) ? 0 : strcmp(key, kvp->key) ? 0 : 1; else if (!prefix && !kvp->prefix) return strcmp(key, kvp->key) ? 0 : 1; else return 0; } /** * Free hmap-managed storage within a KVP and reset its fields. * * Releases the key and prefix strings allocated by hmap. Caller-managed * value storage (arrays, pointers) is NOT freed here. The hmap lock must * be held by the caller before invoking this function. * * @param kvp * Pointer to the key-value pair to free. If NULL, the call is a no-op. */ static void _hmap_free(hmap_kvp_t *kvp) { if (!kvp) return; /* All values (strings, arrays, pointers) are caller-managed (not freed by hmap) */ /* Free hmap-managed key and prefix strings */ if (kvp->key) { free(kvp->key); kvp->key = NULL; } if (kvp->prefix) { free(kvp->prefix); kvp->prefix = NULL; } kvp->v.u64 = 0; kvp->count = 0; kvp->type = HMAP_EMPTY_TYPE; } /** * Compute a bucket index for the given prefix/key pair using the hmap's hash function. * * @param hmap * Pointer to the hmap. * @param prefix * Optional namespace prefix, or NULL. * @param key * Key string. * @return * Bucket index in the range [0, hmap->capacity). */ static inline uint32_t hmap_get_hash(hmap_t *hmap, const char *prefix, const char *key) { return hmap->fns.hash_fn(prefix, key) % hmap->capacity; } /** * Compare a KVP against the given prefix/key pair using the hmap's comparator. * * @param hmap * Pointer to the hmap. * @param kvp * Key-value pair to compare. * @param prefix * Optional namespace prefix, or NULL. * @param key * Key string. * @return * Non-zero on match, 0 on mismatch. */ static inline int hmap_compare(hmap_t *hmap, const hmap_kvp_t *kvp, const char *prefix, const char *key) { return hmap->fns.cmp_fn(kvp, prefix, key); } /** * Free a KVP using the hmap's registered free function. * * @param hmap * Pointer to the hmap. * @param kvp * Pointer to the key-value pair to free. */ static inline void hmap_free_kvp(hmap_t *hmap, hmap_kvp_t *kvp) { hmap->fns.free_fn(kvp); } #ifdef __cplusplus } #endif ================================================ FILE: lib/hmap/hmap_log.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright (c) 2025-2026 Intel Corporation */ #pragma once /** * @file * * HMAP Logs API * * This file provides a log API to HMAP applications. */ #include // for NULL #include // for va_list #include // for uint32_t #include #include #include #ifdef __cplusplus extern "C" { #endif /** Log an INFO-level message to the USER2 facility. */ #define HMAP_LOG(args...) RTE_LOG(INFO, USER2, ##args) /** Log an ERR-level message to the USER2 facility. */ #define HMAP_ERR(args...) RTE_LOG(ERR, USER2, ##args) /** Log a WARNING-level message to the USER2 facility. */ #define HMAP_WARN(args...) RTE_LOG(WARNING, USER2, ##args) /** Log a DEBUG-level message to the USER2 facility. */ #define HMAP_DEBUG(args...) RTE_LOG(DEBUG, USER2, ##args) /** * Log a message at a caller-specified level to the USER2 facility. * * @param l * DPDK log level (e.g. INFO, ERR, DEBUG) passed to RTE_LOG. * @param args * Format string followed by variable arguments, as in printf(3). */ #define HMAP_PRINT(l, args...) RTE_LOG(l, USER2, ##args) /** * Generate an Error log message and return value * * Same as HMAP_LOG(ERR,...) define, but returns -1 to enable this style of coding. * if (val == error) { * HMAP_ERR("Error: Failed\n"); * return -1; * } * Returning _val to the calling function. */ #define HMAP_ERR_RET_VAL(_val, ...) \ do { \ RTE_LOG(ERR, USER2, __VA_ARGS__); \ return _val; \ } while ((0)) /** * Generate an Error log message and return * * Same as HMAP_LOG(ERR,...) define, but returns to enable this style of coding. * if (val == error) { * HMAP_ERR("Error: Failed\n"); * return; * } * Returning to the calling function. */ #define HMAP_RET(...) HMAP_ERR_RET_VAL(, __VA_ARGS__) /** * Generate an Error log message and return -1 * * Same as HMAP_LOG(ERR,...) define, but returns -1 to enable this style of coding. * if (val == error) { * HMAP_ERR("Error: Failed\n"); * return -1; * } * Returning a -1 to the calling function. */ #define HMAP_ERR_RET(...) HMAP_ERR_RET_VAL(-1, __VA_ARGS__) /** * Generate an Error log message and return NULL * * Same as HMAP_LOG(ERR,...) define, but returns NULL to enable this style of coding. * if (val == error) { * HMAP_ERR("Error: Failed\n"); * return NULL; * } * Returning a NULL to the calling function. */ #define HMAP_NULL_RET(...) HMAP_ERR_RET_VAL(NULL, __VA_ARGS__) /** * Generate a Error log message and goto label * * Same as HMAP_LOG(ERR,...) define, but goes to a label to enable this style of coding. * if (error condition) { * HMAP_ERR("Error: Failed\n"); * goto lbl; * } */ #define HMAP_ERR_GOTO(lbl, ...) \ do { \ RTE_LOG(ERR, USER2, __VA_ARGS__); \ goto lbl; \ } while ((0)) #ifdef __cplusplus } #endif ================================================ FILE: lib/hmap/meson.build ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2024-2026 Intel Corporation sources = files('hmap.c') headers = files('hmap.h') libhmap = library('hmap', sources, dependencies: dpdk) hmap = declare_dependency(link_with: libhmap, include_directories: include_directories('.')) ================================================ FILE: lib/lua/lua_config.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Create from lua.c 2018 by Keith Wiles @ intel.com */ #ifdef LUA_ENABLED #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lua_config.h" #include "lua_stdio.h" #include "lua_utils.h" #else #include "lua_config.h" #endif #ifdef LUA_ENABLED TAILQ_HEAD(rte_luaData_list, rte_tailq_entry); static struct rte_tailq_elem rte_luaData_tailq = { .name = "RTE_LUADATA", }; EAL_REGISTER_TAILQ(rte_luaData_tailq); static const char *progname = LUA_PROGNAME; static struct newlib_info { newlib_t lib_func; int order; } newlibs[MAX_NEW_LIBS]; static int newlibs_idx = 0; const char * lua_get_progname(void) { return progname; } void lua_set_progname(const char *name) { progname = name; } int lua_newlib_add(newlib_t n, int order) { if (newlibs_idx >= MAX_NEW_LIBS) return -1; newlibs[newlibs_idx].order = order; newlibs[newlibs_idx++].lib_func = n; return 0; } static int cmp_libs(const void *p1, const void *p2) { const struct newlib_info *ni1 = p1; const struct newlib_info *ni2 = p2; return (ni1->order - ni2->order); } void lua_newlibs_init(luaData_t *ld) { struct newlib_info _libs[MAX_NEW_LIBS]; int i; memcpy(_libs, newlibs, sizeof(newlibs)); qsort(_libs, newlibs_idx, sizeof(struct newlib_info), cmp_libs); for (i = 0; i < newlibs_idx; i++) _libs[i].lib_func(ld->L); } static int handle_luainit(luaData_t *ld) { const char *name; const char *init; name = "=" LUA_INITVERSION; init = getenv(&name[1]); if (!init) { name = "=" LUA_INIT; init = getenv(&name[1]); /* try alternative name */ } if (!init) return LUA_OK; if (init[0] == '@') return lua_dofile(ld, init + 1); else return lua_dostring(ld, init, name); } luaData_t * lua_create_instance(void) { luaData_t *ld; struct rte_luaData_list *luaData_list = NULL; struct rte_tailq_entry *te; ld = (luaData_t *)calloc(1, sizeof(luaData_t)); if (!ld) return NULL; ld->client_socket = -1; ld->server_socket = -1; ld->buffer = calloc(1, LUA_BUFFER_SIZE); if (!ld->buffer) { free(ld); return NULL; } ld->L = luaL_newstate(); if (!ld->L) { free(ld); return NULL; } ld->in = stdin; ld->out = stdout; ld->err = stderr; if (handle_luainit(ld)) { free(ld); DBG("handle_luainit() failed\n"); return NULL; } luaL_openlibs(ld->L); lua_newlibs_init(ld); luaData_list = RTE_TAILQ_CAST(rte_luaData_tailq.head, rte_luaData_list); /* try to allocate tailq entry */ te = calloc(1, sizeof(*te)); if (te == NULL) { DBG("Cannot allocate tailq entry!\n"); lua_close(ld->L); free(ld); return NULL; } te->data = ld; rte_mcfg_tailq_read_lock(); TAILQ_INSERT_TAIL(luaData_list, te, next); rte_mcfg_tailq_read_unlock(); /* Make sure we display the copyright string for Lua. */ printf("%s\n", LUA_COPYRIGHT); return ld; } void lua_destroy_instance(luaData_t *ld) { struct rte_luaData_list *luaData_list = NULL; struct rte_tailq_entry *te; if (!ld) return; luaData_list = RTE_TAILQ_CAST(rte_luaData_tailq.head, rte_luaData_list); rte_mcfg_tailq_write_lock(); TAILQ_FOREACH (te, luaData_list, next) { if (te->data == (void *)ld) break; } if (te) { TAILQ_REMOVE(luaData_list, te, next); free(te); } rte_mcfg_tailq_write_unlock(); free(ld); } luaData_t * lua_find_luaData(lua_State *L) { struct rte_luaData_list *luaData_list = NULL; struct rte_tailq_entry *te; luaData_t *ret_ld = NULL; if (!L) return NULL; luaData_list = RTE_TAILQ_CAST(rte_luaData_tailq.head, rte_luaData_list); rte_mcfg_tailq_write_lock(); TAILQ_FOREACH (te, luaData_list, next) { luaData_t *ld = (luaData_t *)te->data; if (ld->L == L) { ret_ld = ld; break; } } rte_mcfg_tailq_write_unlock(); return ret_ld; } /* ** Message handler used to run all chunks */ static int msghandler(lua_State *L) { const char *msg = lua_tostring(L, 1); if (msg == NULL) { /* is error object not a string? */ if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */ lua_type(L, -1) == LUA_TSTRING) { /* that produces a string? */ DBG("No metadata to produce a string on object\n"); return 1; /* that is the message */ } else { msg = lua_pushfstring(L, "(error object is a %s value)", luaL_typename(L, 1)); } } luaL_traceback(L, L, msg, 1); /* append a standard traceback */ return 1; /* return the traceback */ } static int _k(lua_State *L, int status, lua_KContext ctx) { (void)L; (void)ctx; return status; } int lua_docall(lua_State *L, int narg, int nres) { int status; int base = 0; base = lua_gettop(L); lua_pushcfunction(L, msghandler); lua_insert(L, base); status = _k(L, lua_pcallk(L, narg, nres, base, 0, _k), 0); return status; } int lua_dofile(luaData_t *ld, const char *name) { int status; status = luaL_loadfile(ld->L, name); if (status == LUA_OK) status = lua_docall(ld->L, 0, LUA_MULTRET); else printf("lua_docall(%s) failed\n", name); return report(ld->L, status); } int lua_dostring(luaData_t *ld, const char *s, const char *name) { int status; status = luaL_loadbuffer(ld->L, s, strlen(s), name); if (status == LUA_OK) status = lua_docall(ld->L, 0, 0); return report(ld->L, status); } int lua_dolibrary(lua_State *L, const char *name) { int status; lua_getglobal(L, "require"); lua_pushstring(L, name); status = lua_docall(L, 1, 1); /* call 'require(name)' */ if (status == LUA_OK) lua_setglobal(L, name); /* global[name] = require return */ return report(L, status); } int lua_execute_string(luaData_t *ld, char *buffer) { lua_State *L = ld->L; if (!L) return -1; buffer = lua_strtrim(buffer); if (!buffer) return -1; if (luaL_dostring(L, buffer) != 0) { DBG("%s\n", lua_tostring(L, -1)); return -1; } return 0; } void lua_execute_close(luaData_t *ld) { if (ld->L) lua_close(ld->L); } #else void lua_execute_close(void *ld) { (void)ld; } #endif ================================================ FILE: lib/lua/lua_config.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #ifndef _LUA_CONFIG_H_ #define _LUA_CONFIG_H_ /** * @file * * Core Lua instance management for Pktgen. * * Provides functions to create, configure, and destroy Lua interpreter * instances (luaData_t), load and execute Lua scripts/strings, register * new native libraries, and manage Lua's standard I/O streams. All * declarations are conditionally compiled under LUA_ENABLED. */ #ifdef LUA_ENABLED #include #include #include #include #include #include #include #include #include #include #include #define lua_c #include #define LUA_COMPAT_APIINTCASTS #include #include #endif #ifdef __cplusplus extern "C" { #endif #ifdef LUA_ENABLED #if !defined(LUA_PROGNAME) #define LUA_PROGNAME "lua-shell" #endif #if !defined(LUA_INIT) #define LUA_INIT "LUA_INIT" #endif #define LUA_INITVERSION LUA_INIT "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR #define DBG(...) \ do { \ fprintf(stderr, "%s: ", __func__); \ fprintf(stderr, __VA_ARGS__); \ } while (0) #define MAX_NEW_LIBS 16 #define LUA_BUFFER_SIZE 8192 #define MAX_NEW_LIBS 16 #define LUA_EOF -1 #define IO_PREFIX "_IO_" #define IOPREF_LEN (sizeof(IO_PREFIX) / sizeof(char) - 1) #define IO_INPUT (IO_PREFIX "input") #define IO_OUTPUT (IO_PREFIX "output") typedef struct luaData { TAILQ_ENTRY(luaData) node; lua_State *L; /**< Lua State pointer */ int32_t server_socket; /**< Server socket descriptor */ int32_t client_socket; /**< Client socket descriptor */ int32_t socket_port; /**< Port address for socket */ char *buffer; /**< Buffer for reading Lua code */ void *out, *in, *err; /**< stdout, stdin, stderr */ char *hostname; /**< Name of host for socket */ } luaData_t; /** Callback type for registering a new native Lua library. */ typedef void (*newlib_t)(lua_State *L); /** * Allocate and initialise a new Lua interpreter instance. * * @return Pointer to the new luaData_t, or NULL on failure. */ luaData_t *lua_create_instance(void); /** * Register a native library constructor to be called during Lua initialisation. * * Libraries are called in ascending @p order when lua_newlibs_init() runs. * * @param n Constructor function to register. * @param order Relative initialisation order (lower values run first). * @return 0 on success, -1 if the registry is full. */ int lua_newlib_add(newlib_t n, int order); /** * Call all registered library constructors for instance @p ld. * * Should be called once after lua_create_instance() to make all * registered native libraries available within the Lua state. * * @param ld Lua instance to initialise libraries into. */ void lua_newlibs_init(luaData_t *ld); /** * Call a Lua function that is already on the stack in protected mode. * * @param L Lua state. * @param narg Number of arguments on the stack. * @param nres Expected number of return values. * @return LUA_OK on success, or a Lua error code. */ int lua_docall(lua_State *L, int narg, int nres); /** * Load and execute a Lua file. * * @param ld Lua instance to use. * @param name Path to the Lua source file. * @return LUA_OK on success, or a Lua error code. */ int lua_dofile(luaData_t *ld, const char *name); /** * Execute a Lua string within instance @p ld. * * @param ld Lua instance to use. * @param s Lua source code string. * @param name Chunk name used in error messages (e.g. the script name). * @return LUA_OK on success, or a Lua error code. */ int lua_dostring(luaData_t *ld, const char *s, const char *name); /** * Dynamically load a Lua C library by name and open it. * * @param L Lua state. * @param name Name of the C library to load (passed to package.loadlib). * @return LUA_OK on success, or a Lua error code. */ int lua_dolibrary(lua_State *L, const char *name); /** * Execute a NUL-terminated Lua string and handle errors. * * Convenience wrapper around lua_dostring() for interactive use. * * @param ld Lua instance to use. * @param str Lua source code string. * @return 0 on success, non-zero on error. */ int lua_execute_string(luaData_t *ld, char *str); /** * Close the client socket associated with instance @p ld. * * @param ld Lua instance whose socket should be closed. */ void lua_execute_close(luaData_t *ld); /** * Create and register a Lua stdio file object backed by FILE @p f. * * @param ld Lua instance. * @param f Underlying C FILE to wrap. * @param k Lua global key name (e.g. "stdout"). * @param fname Lua filename string for the file object. */ void lua_create_stdfile(luaData_t *ld, FILE *f, const char *k, const char *fname); /** * Redirect Lua's standard streams to the socket streams in @p ld. * * @param ld Lua instance to configure. */ void lua_set_stdfiles(luaData_t *ld); /** * Restore Lua's standard streams to the process standard streams. * * @param ld Lua instance to reset. */ void lua_reset_stdfiles(luaData_t *ld); /** * Return the current Lua programme name string. * * @return Programme name as a NUL-terminated string. */ const char *lua_get_progname(void); /** * Set the Lua programme name shown in error messages. * * @param name New programme name string (must remain valid for the lifetime * of the Lua instance). */ void lua_set_progname(const char *name); /** * Destroy a Lua instance and free all associated resources. * * @param ld Lua instance to destroy. */ void lua_destroy_instance(luaData_t *ld); /** * Find the luaData_t that owns Lua state @p L. * * Searches the global registry of all created Lua instances. * * @param L Lua state to look up. * @return Pointer to the owning luaData_t, or NULL if not found. */ luaData_t *lua_find_luaData(lua_State *L); #else /** Close the Lua instance socket (stub when LUA_ENABLED is not defined). */ void lua_execute_close(void *ld); #endif #ifdef __cplusplus } #endif #endif /* _LUA_CONFIG_H_ */ ================================================ FILE: lib/lua/lua_dapi.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #define lua_dpdk_c #define LUA_LIB #define lua_c #ifdef RTE_LIBRTE_API #include #include #include #include #include #include #include #include #include "lua_config.h" #include "lua_stdio.h" #include "lua_dpdk.h" #include "lua_dapi.h" #include "lua_utils.h" #ifndef __INTEL_COMPILER #pragma GCC diagnostic ignored "-Wcast-qual" #endif static const char *Dapi = "Dapi"; static int _create(lua_State *L) { dapi_t **dapi; struct dapi *d; const char *name; validate_arg_count(L, 1); name = luaL_checkstring(L, 1); if (!name) luaL_error(L, "Name is empty"); dapi = (struct dapi **)lua_newuserdata(L, sizeof(void *)); d = dapi_create((char *)(uintptr_t)name, 0, 0); if (!d) return luaL_error(L, "create: dapi_create() failed"); *dapi = d; luaL_getmetatable(L, Dapi); lua_setmetatable(L, -2); return 1; } static int _destroy(lua_State *L) { dapi_t **dapi; validate_arg_count(L, 1); dapi = (dapi_t **)luaL_checkudata(L, 1, Dapi); dapi_destroy(*dapi); return 0; } static int _get(lua_State *L) { validate_arg_count(L, 2); return 1; } static int _put(lua_State *L) { validate_arg_count(L, 2); return 0; } static int _tostring(lua_State *L) { dapi_t **dapi; struct dapi *d; char buff[64]; dapi = (dapi_t **)luaL_checkudata(L, 1, Dapi); if (!dapi || !*dapi) return luaL_error(L, "tostring, dapi is nil"); d = *dapi; lua_getmetatable(L, 1); lua_getfield(L, -1, "__name"); snprintf(buff, sizeof(buff), "%s<%s>", lua_tostring(L, -1), d->name); lua_pop(L, 3); lua_pushstring(L, buff); return 1; } static int _gc(lua_State *L __rte_unused) { return 0; } static const struct luaL_Reg _methods[] = {{"get", _get}, {"put", _put}, {NULL, NULL}}; static const struct luaL_Reg _functions[] = { {"create", _create}, {"destroy", _destroy}, {NULL, NULL}}; int luaopen_dapi(lua_State *L) { luaL_newmetatable(L, Dapi); // create and push new table called Vec lua_pushvalue(L, -1); // dup the table on the stack lua_setfield(L, -2, "__index"); // luaL_setfuncs(L, _methods, 0); lua_pushstring(L, "__tostring"); lua_pushcfunction(L, _tostring); lua_settable(L, -3); lua_pushstring(L, "__gc"); lua_pushcfunction(L, _gc); lua_settable(L, -3); lua_pop(L, 1); lua_getglobal(L, LUA_DPDK_LIBNAME); lua_newtable(L); luaL_setfuncs(L, _functions, 0); lua_setfield(L, -2, LUA_DAPI_LIBNAME); return 1; } #endif ================================================ FILE: lib/lua/lua_dapi.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #ifndef _RTE_LUA_DAPI_H_ #define _RTE_LUA_DAPI_H_ /** * @file * * Lua bindings for the DAPI (Data-plane API) library. * * Exposes DAPI functionality to Lua scripts under the "dapi" library name. * Only available when RTE_LIBRTE_DAPI is enabled at build time. */ #include #define lua_c #include #include #ifdef RTE_LIBRTE_DAPI #include #endif #ifdef __cplusplus extern "C" { #endif typedef struct dapi dapi_t; #define LUA_DAPI_LIBNAME "dapi" /** * Open the DAPI Lua library and register its functions. * * Called automatically by the Lua runtime when the library is required. * * @param L * Lua state to register the library into. * @return * Number of values pushed onto the Lua stack (1 — the library table). */ int luaopen_dapi(lua_State *L); #ifdef __cplusplus } #endif #endif /* _RTE_LUA_DAPI_H_ */ ================================================ FILE: lib/lua/lua_dpdk.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #define lua_dpdk_c #define LUA_LIB #define lua_c #include #include #include #include #include #include #include #include #include #include #include "lua_config.h" #include "lua_stdio.h" #include "lua_dpdk.h" #include "lua_pktmbuf.h" #ifdef RTE_LIBRTE_DAPI #include #else int luaopen_dapi(lua_State *L); #endif #include "lua_vec.h" #include "lua_utils.h" #ifndef __INTEL_COMPILER #pragma GCC diagnostic ignored "-Wcast-qual" #endif static int lua_logtype; static __inline__ void __delay(int32_t t) { int32_t n; while (t > 0) { n = (t > 10) ? 10 : t; rte_delay_us_sleep(n * 1000); t -= n; } } static int dpdk_delay(lua_State *L) { validate_arg_count(L, 1); __delay(luaL_checkinteger(L, 1)); return 0; } static int dpdk_pause(lua_State *L) { char *str; int v; validate_arg_count(L, 2); str = (char *)luaL_checkstring(L, 1); if (strlen(str) > 0) lua_putstring(str); v = luaL_checkinteger(L, 2); __delay(v); return 0; } static int dpdk_continue(lua_State *L) { char buf[4], *str; int n; validate_arg_count(L, 1); str = (char *)luaL_checkstring(L, 1); if (strlen(str) > 0) lua_putstring(str); buf[0] = '\0'; n = fread(buf, 1, 1, (FILE *)lua_get_stdin(lua_find_luaData(L))); if (n > 0) buf[n] = '\0'; lua_pushstring(L, buf); return 1; } static int dpdk_input(lua_State *L) { char buf[256], c, *str; uint32_t n, idx; validate_arg_count(L, 1); str = (char *)luaL_checkstring(L, 1); if (strlen(str) > 0) lua_putstring(str); idx = 0; buf[idx] = '\0'; while (idx < (sizeof(buf) - 2)) { n = fread(&c, 1, 1, (FILE *)lua_get_stdin(lua_find_luaData(L))); if ((n <= 0) || (c == '\r') || (c == '\n')) break; buf[idx++] = c; } buf[idx] = '\0'; lua_pushstring(L, buf); return 1; } static int dpdk_sleep(lua_State *L) { validate_arg_count(L, 1); rte_delay_us_sleep((luaL_checkinteger(L, 1) * 1000) * 1000); return 0; } static void link_state(lua_State *L, uint16_t pid) { struct rte_eth_link link; char buff[32]; lua_pushinteger(L, pid); /* Push the table index */ if (rte_eth_link_get_nowait(pid, &link) < 0) { fprintf(stderr, "Port %u: Failed to get link status\n", pid); return; } if (link.link_status) snprintf(buff, sizeof(buff), "", (uint32_t)link.link_speed, (link.link_duplex == RTE_ETH_LINK_FULL_DUPLEX) ? ("FD") : ("HD")); else snprintf(buff, sizeof(buff), "<--Down-->"); lua_pushstring(L, buff); /* Now set the table as an array with pid as the index. */ lua_rawset(L, -3); } static int dpdk_linkState(lua_State *L) { portlist_t portlist; uint32_t n; validate_arg_count(L, 1); portlist = 0; portlist_parse(luaL_checkstring(L, 1), RTE_MAX_ETHPORTS, &portlist); lua_newtable(L); n = 0; foreach_port(portlist, _do(link_state(L, pid); n++)); setf_integer(L, "n", n); return 1; } static void port_stats(lua_State *L, uint16_t pid) { struct rte_eth_stats stats; rte_eth_stats_get(pid, &stats); lua_pushinteger(L, pid); /* Push the table index */ lua_newtable(L); /* Create the structure table for a packet */ setf_integer(L, "ipackets", stats.ipackets); setf_integer(L, "opackets", stats.opackets); setf_integer(L, "ibytes", stats.ibytes); setf_integer(L, "obytes", stats.obytes); setf_integer(L, "ierrors", stats.ierrors); setf_integer(L, "oerrors", stats.oerrors); setf_integer(L, "rx_nombuf", stats.rx_nombuf); /* Now set the table as an array with pid as the index. */ lua_rawset(L, -3); } static int dpdk_portStats(lua_State *L) { portlist_t portlist; uint32_t n; validate_arg_count(L, 1); portlist_parse(luaL_checkstring(L, 1), RTE_MAX_ETHPORTS, &portlist); lua_newtable(L); n = 0; foreach_port(portlist, _do(port_stats(L, pid); n++)); setf_integer(L, "n", n); return 1; } static int dpdk_compile(lua_State *L) { validate_arg_count(L, 3); return 1; } static void decompile_pkt(lua_State *L, uint16_t pid) { char buff[256]; struct pkt_data *p = NULL; lua_pushinteger(L, pid); /* Push the table index */ lua_newtable(L); /* Create the structure table for a packet */ /* Add each member to the packet table indexed with port id. */ setf_string(L, "eth_dst_addr", inet_mtoa(buff, sizeof(buff), &p->eth_dst_addr)); setf_string(L, "eth_src_addr", inet_mtoa(buff, sizeof(buff), &p->eth_src_addr)); if (p->ethType == RTE_ETHER_TYPE_IPV4) { setf_string(L, "ip_dst_addr", inet_ntop4(buff, sizeof(buff), htonl(p->ip_dst_addr), 0xFFFFFFFF)); setf_string(L, "ip_src_addr", inet_ntop4(buff, sizeof(buff), htonl(p->ip_src_addr), p->ip_mask)); } setf_integer(L, "dport", p->dport); setf_integer(L, "sport", p->sport); setf_integer(L, "vlanid", p->vlanid); setf_string(L, "ethType", (char *)((p->ethType == RTE_ETHER_TYPE_IPV4) ? "ipv4" : (p->ethType == RTE_ETHER_TYPE_IPV6) ? "ipv6" : (p->ethType == RTE_ETHER_TYPE_VLAN) ? "vlan" : "unknown")); setf_string(L, "ipProto", (char *)((p->ipProto == IPPROTO_TCP) ? "tcp" : (p->ipProto == IPPROTO_ICMP) ? "icmp" : "udp")); setf_integer(L, "pktSize", p->pktSize + RTE_ETHER_CRC_LEN); /* Now set the table as an array with pid as the index. */ lua_rawset(L, -3); } static int dpdk_decompile(lua_State *L) { portlist_t portlist; uint32_t n; validate_arg_count(L, 2); portlist_parse(luaL_checkstring(L, 2), RTE_MAX_ETHPORTS, &portlist); lua_newtable(L); n = 0; foreach_port(portlist, _do(decompile_pkt(L, pid); n++)); setf_integer(L, "n", n); return 1; } static int dpdk_tx_burst(lua_State *L) { uint16_t pid, qid; uint32_t nb_pkts; int rc; struct rte_mbuf **mbs; validate_arg_count(L, 4); pid = luaL_checkinteger(L, 1); qid = luaL_checkinteger(L, 1); mbs = (struct rte_mbuf **)luaL_checkudata(L, 3, "mbufs"); nb_pkts = luaL_checkinteger(L, 4); rc = rte_eth_tx_burst(pid, qid, mbs, nb_pkts); lua_pushinteger(L, rc); return 1; } static int dpdk_portCount(lua_State *L) { validate_arg_count(L, 0); lua_pushinteger(L, rte_eth_dev_count_avail()); return 1; } static int dpdk_totalPorts(lua_State *L) { validate_arg_count(L, 0); lua_pushinteger(L, rte_eth_dev_count_total()); return 1; } static int dpdk_rx_burst(lua_State *L) { uint16_t pid, qid; struct rte_mbuf **mbs; int rc, nb_pkts; validate_arg_count(L, 4); pid = luaL_checkinteger(L, 1); qid = luaL_checkinteger(L, 1); mbs = (struct rte_mbuf **)luaL_checkudata(L, 3, "mbufs"); nb_pkts = luaL_checkinteger(L, 4); rc = rte_eth_tx_burst(pid, qid, mbs, nb_pkts); lua_pushinteger(L, rc); return 1; } static int dpdk_version(lua_State *L) { validate_arg_count(L, 0); lua_pushstring(L, rte_version()); return 1; } static const luaL_Reg dpdklib[] = { {"delay", dpdk_delay}, /* Delay a given number of milliseconds */ {"pause", dpdk_pause}, /* Delay for a given number of milliseconds and display message */ {"continue", dpdk_continue}, {"sleep", dpdk_sleep}, /* Delay a given number of seconds */ {"portCount", dpdk_portCount}, /* Used port count value */ {"totalPorts", dpdk_totalPorts}, /* Total ports seen by DPDK */ {"port_stats", dpdk_portStats}, {"input", dpdk_input}, {"linkState", dpdk_linkState}, /* Return the current link state of a port */ {"compile", dpdk_compile}, /* Convert a structure into a frame to be sent */ {"decompile", dpdk_decompile}, /* decompile a frame into Ethernet, IP, TCP, UDP or other protocols */ {"tx_burst", dpdk_tx_burst}, {"rx_bust", dpdk_rx_burst}, {"version", dpdk_version}, {NULL, NULL}}; static const char *rte_copyright = "Copyright(c) <2010-2026>, Intel Corporation"; static int luaopen_dpdk(lua_State *L) { luaL_newlib(L, dpdklib); lua_pushstring(L, "dinfo"); /* Push the table index name */ lua_newtable(L); /* Create the structure table for information */ setf_string(L, "Lua_Version", (char *)LUA_VERSION); setf_string(L, "Lua_Release", (char *)LUA_RELEASE); setf_string(L, "Lua_Copyright", (char *)LUA_COPYRIGHT); setf_string(L, "Lua_Authors", (char *)LUA_AUTHORS); setf_string(L, "DAPI_Authors", (char *)"Keith Wiles @ Intel Corp"); setf_string(L, "DPDK_Version", (char *)rte_version()); setf_string(L, "DPDK_Copyright", (char *)rte_copyright); /* Now set the table for the info values. */ lua_rawset(L, -3); return 1; } __attribute__((__weak__)) int luaopen_dapi(lua_State *L __rte_unused) { return 0; } static void dpdk_lua_openlib(lua_State *L) { lua_gc(L, LUA_GCSTOP, 0); luaL_requiref(L, LUA_DPDK_LIBNAME, luaopen_dpdk, 1); lua_pop(L, 1); if (luaopen_pktmbuf(L)) lua_pop(L, 1); if (luaopen_vec(L)) lua_pop(L, 1); if (luaopen_dapi(L)) lua_pop(L, 1); lua_gc(L, LUA_GCRESTART, 0); } RTE_INIT(dpdk_lua_init) { lua_logtype = rte_log_register("librte.lua"); if (lua_logtype >= 0) rte_log_set_level(lua_logtype, RTE_LOG_INFO); lua_newlib_add(dpdk_lua_openlib, 10); } ================================================ FILE: lib/lua/lua_dpdk.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #ifndef _RTE_LUA_DPDK_H_ #define _RTE_LUA_DPDK_H_ /** * @file * * DPDK Lua helper types, macros, and inline field accessor functions. * * Defines common structures (pkt_data), iteration macros (foreach_port), * argument validation helpers (validate_arg_count), and a set of * setf_* and getf_* inline functions for pushing and pulling values between * C and Lua table fields. */ #include #include #include #define lua_c #include #include #ifdef __cplusplus extern "C" { #endif #define LUA_DPDK_LIBNAME "dpdk" /** Emit a log message at @p level using the Lua log type. */ #define LUA_LOG(level, fmt, args...) \ rte_log(RTE_LOG_##level, lua_logtype, "%s(): " fmt "\n", __func__, ##args) /** Execute @p _exp inside a do/while(0) block (suppresses compiler warnings). */ #define _do(_exp) \ do { \ _exp; \ } while ((0)) /** * Iterate over all ports set in @p _portlist and execute @p _action for each. * * @param _portlist 64-bit portlist bitmap. * @param _action Statement or block executed with @c pid set to each active port id. */ #define foreach_port(_portlist, _action) \ do { \ uint64_t *_pl = (uint64_t *)&_portlist; \ uint16_t pid, idx, bit; \ \ RTE_ETH_FOREACH_DEV(pid) \ { \ idx = (pid / (sizeof(uint64_t) * 8)); \ bit = (pid - (idx * (sizeof(uint64_t) * 8))); \ if ((_pl[idx] & (1LL << bit)) == 0) \ continue; \ _action; \ } \ } while ((0)) /** * Validate that exactly @p _n arguments are present on the Lua stack. * * Returns a Lua error to the caller if the count does not match. * * @param _l Lua state. * @param _n Expected argument count. */ #define validate_arg_count(_l, _n) \ do { \ switch (lua_gettop(_l)) { \ default: \ return luaL_error(_l, "%s, Invalid arg count should be %d", __func__, _n); \ case _n: \ break; \ } \ } while ((0)) struct pkt_data { /* Packet type and information */ struct rte_ether_addr eth_dst_addr; /**< Destination Ethernet address */ struct rte_ether_addr eth_src_addr; /**< Source Ethernet address */ uint32_t ip_src_addr; /**< Source IPv4 address also used for IPv6 */ uint32_t ip_dst_addr; /**< Destination IPv4 address */ uint32_t ip_mask; /**< IPv4 Netmask value */ uint16_t sport; /**< Source port value */ uint16_t dport; /**< Destination port value */ uint16_t ethType; /**< IPv4 or IPv6 */ uint16_t ipProto; /**< TCP or UDP or ICMP */ uint16_t vlanid; /**< VLAN ID value if used */ uint16_t ether_hdr_size; /**< Size of Ethernet header in packet for VLAN ID */ uint16_t pktSize; /**< Size of packet in bytes not counting FCS */ uint16_t pad0; }; typedef struct rte_mempool pktmbuf_t; typedef struct rte_mempool mempool_t; typedef struct vec vec_t; /** * Push an integer value and set it as field @p name in the table at the top of the stack. * * @param L Lua state. * @param name Field name in the table. * @param value Integer value to set. */ static __inline__ void setf_integer(lua_State *L, const char *name, lua_Integer value) { lua_pushinteger(L, value); lua_setfield(L, -2, name); } /** * Push a C closure (with no upvalues) and set it as field @p name in the table at stack top. * * @param L Lua state. * @param name Field name in the table. * @param fn C function to push as a closure. */ static __inline__ void setf_function(lua_State *L, const char *name, lua_CFunction fn) { lua_pushcclosure(L, fn, 0); lua_setfield(L, -2, name); } /** * Push a NUL-terminated string and set it as field @p name in the table at stack top. * * @param L Lua state. * @param name Field name in the table. * @param value String value to push. */ static __inline__ void setf_string(lua_State *L, const char *name, const char *value) { lua_pushstring(L, value); lua_setfield(L, -2, name); } /** * Push a length-delimited string and set it as field @p name in the table at stack top. * * @param L Lua state. * @param name Field name in the table. * @param value String data to push (need not be NUL-terminated). * @param len Number of bytes in @p value. */ static __inline__ void setf_stringLen(lua_State *L, const char *name, char *value, int len) { lua_pushlstring(L, value, len); lua_setfield(L, -2, name); } /** * Push a light userdata pointer and set it as field @p name in the table at stack top. * * @param L Lua state. * @param name Field name in the table. * @param value Pointer to push as light userdata. */ static __inline__ void setf_udata(lua_State *L, const char *name, void *value) { lua_pushlightuserdata(L, value); lua_setfield(L, -2, name); } /** * Read an integer field from the Lua table at stack index 3. * * @param L Lua state (field is read from the table at index 3). * @param field Name of the integer field to retrieve. * @return Field value as uint32_t, or 0 if the field is absent or not an integer. */ static __inline__ uint32_t getf_integer(lua_State *L, const char *field) { uint32_t value = 0; lua_getfield(L, 3, field); if (lua_isinteger(L, -1)) value = luaL_checkinteger(L, -1); lua_pop(L, 1); return value; } /** * Read a string field from the Lua table at stack index 3. * * @param L Lua state (field is read from the table at index 3). * @param field Name of the string field to retrieve. * @return Pointer to the Lua-managed string, or NULL if absent or not a string. */ static __inline__ const char * getf_string(lua_State *L, const char *field) { const char *value = NULL; lua_getfield(L, 3, field); if (lua_isstring(L, -1)) value = luaL_checkstring(L, -1); lua_pop(L, 1); return value; } #ifdef __cplusplus } #endif #endif /* _RTE_LUA_DPDK_H_ */ ================================================ FILE: lib/lua/lua_pktmbuf.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #define lua_dpdk_c #define LUA_LIB #define lua_c #include #include #include #include #include #include #include #include "lua_config.h" #include "lua_stdio.h" #include "lua_dpdk.h" #include "lua_pktmbuf.h" #include "lua_utils.h" #ifndef __INTEL_COMPILER #pragma GCC diagnostic ignored "-Wcast-qual" #endif static int lua_inst; static const char *Pktmbuf = "Pktmbuf"; static int _new(lua_State *L) { pktmbuf_t **mbp; struct rte_mempool *mp; const char *name; uint32_t n, size, csize; char poolname[RTE_MEMPOOL_NAMESIZE]; validate_arg_count(L, 4); name = luaL_checkstring(L, 1); if (!name) luaL_error(L, "Name is empty"); n = luaL_checkint(L, 2); if (n == 0) luaL_error(L, "Number of entries is zero"); size = luaL_checkint(L, 3); if (size == 0) luaL_error(L, "Size of entries is zero"); csize = luaL_checkint(L, 4); mbp = (pktmbuf_t **)lua_newuserdata(L, sizeof(void *)); snprintf(poolname, sizeof(poolname), "%s-%d", name, lua_inst++); mp = rte_pktmbuf_pool_create(poolname, n, csize, 0, size, pg_socket_id()); if (mp == NULL) luaL_error(L, "Failed to create MBUF Pool"); *mbp = mp; luaL_getmetatable(L, Pktmbuf); lua_setmetatable(L, -2); return 1; } static int _destroy(lua_State *L) { pktmbuf_t **mbp; validate_arg_count(L, 1); mbp = (pktmbuf_t **)luaL_checkudata(L, 1, Pktmbuf); rte_mempool_free(*mbp); return 0; } static int _get(lua_State *L) { pktmbuf_t **mbp; struct rte_mbuf *m; validate_arg_count(L, 1); mbp = luaL_checkudata(L, 1, Pktmbuf); if (rte_mempool_get(*mbp, (void **)&m) == 0) lua_pushlightuserdata(L, m); return 1; } static int _put(lua_State *L) { struct rte_mbuf *m; validate_arg_count(L, 2); m = lua_touserdata(L, 2); rte_pktmbuf_free(m); return 0; } static int _tostring(lua_State *L) { pktmbuf_t **mbp; struct rte_mempool *mp; char buff[64]; mbp = (pktmbuf_t **)luaL_checkudata(L, 1, Pktmbuf); if (!mbp || !*mbp) return luaL_error(L, "tostring, pktmbuf is nil"); mp = *mbp; lua_getmetatable(L, 1); lua_getfield(L, -1, "__name"); snprintf(buff, sizeof(buff), "%s<%s,%d,%d,%d,%d>", lua_tostring(L, -1), mp->name, mp->size, mp->elt_size, mp->cache_size, mp->socket_id); lua_pop(L, 3); lua_pushstring(L, buff); return 1; } static int _gc(lua_State *L __rte_unused) { return 0; } static const struct luaL_Reg _methods[] = {{"get", _get}, {"put", _put}, {NULL, NULL}}; static const struct luaL_Reg _functions[] = {{"new", _new}, {"destroy", _destroy}, {NULL, NULL}}; int luaopen_pktmbuf(lua_State *L) { luaL_newmetatable(L, Pktmbuf); // create and push new table called Vec lua_pushvalue(L, -1); // dup the table on the stack lua_setfield(L, -2, "__index"); // luaL_setfuncs(L, _methods, 0); lua_pushstring(L, "__tostring"); lua_pushcfunction(L, _tostring); lua_settable(L, -3); lua_pushstring(L, "__gc"); lua_pushcfunction(L, _gc); lua_settable(L, -3); lua_pop(L, 1); lua_getglobal(L, LUA_DPDK_LIBNAME); lua_newtable(L); luaL_setfuncs(L, _functions, 0); lua_setfield(L, -2, LUA_PKTMBUF_LIBNAME); return 1; } ================================================ FILE: lib/lua/lua_pktmbuf.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #ifndef _RTE_LUA_PKTMBUF_H_ #define _RTE_LUA_PKTMBUF_H_ /** * @file * * Lua bindings for DPDK pktmbuf mempools. * * Exposes pktmbuf mempool operations to Lua scripts under the "pktmbuf" * library name, allowing scripts to allocate and manage packet buffers. */ #include #define lua_c #include #include #ifdef __cplusplus extern "C" { #endif #define LUA_PKTMBUF_LIBNAME "pktmbuf" typedef struct rte_mempool pktmbuf_t; /** * Open the pktmbuf Lua library and register its functions. * * Called automatically by the Lua runtime when the library is required. * * @param L * Lua state to register the library into. * @return * Number of values pushed onto the Lua stack (1 — the library table). */ int luaopen_pktmbuf(lua_State *L); #ifdef __cplusplus } #endif #endif /* _RTE_LUA_PKTMBUF_H_ */ ================================================ FILE: lib/lua/lua_socket.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lua_config.h" #include "lua_stdio.h" #include "lua_utils.h" #include "lua_socket.h" static int server_startup(luaData_t *ld) { const char *err_msg = NULL; struct sockaddr_in ipaddr; struct hostent *pHost; int linger = 1; pthread_detach(pthread_self()); err_msg = "gethostbyname failed"; if ((pHost = gethostbyname(ld->hostname)) == NULL) goto error_exit; err_msg = "Socket create failed"; if ((ld->server_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) goto error_exit; memset(&ipaddr, 0, sizeof(ipaddr)); ipaddr.sin_family = AF_INET; ipaddr.sin_port = htons(ld->socket_port); ipaddr.sin_addr.s_addr = htonl(INADDR_ANY); err_msg = "Setsockopt failed"; if (setsockopt(ld->server_socket, SOL_SOCKET, SO_REUSEADDR, &linger, sizeof(linger)) == -1) goto error_exit; err_msg = "Bind failed"; if (bind(ld->server_socket, (struct sockaddr *)&ipaddr, sizeof(ipaddr)) < 0) goto error_exit; err_msg = "Listen failed"; if (listen(ld->server_socket, 5) < 0) goto error_exit; return 0; error_exit: if (ld->server_socket != -1) close(ld->server_socket); ld->server_socket = -1; if (err_msg) { perror(err_msg); fflush(stdout); } return -1; } static void _socket_open(luaData_t *ld) { if (ld) { ld->in = fdopen(ld->client_socket, "r"); ld->out = fdopen(ld->client_socket, "w"); ld->err = fdopen(ld->client_socket, "w"); } } static void _socket_close(luaData_t *ld) { if (ld) { fclose(ld->in); fclose(ld->out); fclose(ld->err); } } /* mark in error messages for incomplete statements */ #define EOFMARK "" #define marklen (sizeof(EOFMARK) / sizeof(char) - 1) /* ** Check whether 'status' signals a syntax error and the error ** message at the top of the stack ends with the above mark for ** incomplete statements. */ static int incomplete(lua_State *L, int status) { if (status == LUA_ERRSYNTAX) { size_t lmsg; const char *msg = lua_tolstring(L, -1, &lmsg); if (lmsg >= marklen && !strcmp(msg + lmsg - marklen, EOFMARK)) { lua_pop(L, 1); return 1; } } return 0; /* else... */ } /* ** Read a line, and push it into the Lua stack. */ static int pushline(luaData_t *ld, int firstline) { lua_State *L = ld->L; size_t l; char *b = lua_readline(ld); if (!b) return 0; l = strlen(b); printf("%s", b); if (l > 0 && b[l - 1] == '\n') /* line ends with newline? */ b[--l] = '\0'; /* remove it */ if (firstline && b[0] == '=') /* for compatibility with 5.2, ... */ lua_pushfstring(L, "return %s", b + 1); /* change '=' to 'return' */ else lua_pushlstring(L, b, l); return 1; } /* ** Try to compile line on the stack as 'return ;'; on return, stack ** has either compiled chunk or original line (if compilation failed). */ static int addreturn(lua_State *L) { const char *line = lua_tostring(L, -1); /* original line */ const char *retline = lua_pushfstring(L, "return %s;", line); int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin"); if (status == LUA_OK) lua_remove(L, -2); /* remove modified line */ else lua_pop(L, 2); /* pop result from 'luaL_loadbuffer' and modified line */ return status; } /* ** Read multiple lines until a complete Lua statement */ static int multiline(luaData_t *ld) { lua_State *L = ld->L; for (;;) { /* repeat until gets a complete statement */ size_t len; const char *line = lua_tolstring(L, 1, &len); /* get what it has */ int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */ if (!incomplete(L, status) || !pushline(ld, 0)) return status; /* cannot or should not try to add continuation line */ lua_pushliteral(L, "\n"); /* add newline... */ lua_insert(L, -2); /* ...between the two lines */ lua_concat(L, 3); /* join them */ } } /* ** Read a line and try to load (compile) it first as an expression (by ** adding "return " in front of it) and second as a statement. Return ** the final status of load/call with the resulting function (if any) ** in the top of the stack. */ static int loadline(luaData_t *ld) { lua_State *L = ld->L; int status; lua_settop(L, 0); if (!pushline(ld, 1)) return -1; /* no input */ status = addreturn(L); if (status != LUA_OK) /* 'return ...' did not work? */ status = multiline(ld); /* try as command, maybe with continuation lines */ lua_remove(L, 1); /* remove line from the stack */ return status; } /* ** Prints (calling the Lua 'print' function) any values on the stack */ static void l_print(lua_State *L) { int n = lua_gettop(L); if (n > 0) { /* any result to be printed? */ luaL_checkstack(L, LUA_MINSTACK, "too many results to print"); if (lua_isfunction(L, 1)) return; lua_getglobal(L, "print"); lua_insert(L, 1); if (lua_pcall(L, n, 0, 0) != LUA_OK) l_message(lua_get_progname(), lua_pushfstring(L, "error calling 'print' (%s)", lua_tostring(L, -1))); } } static void doREPL(luaData_t *ld) { lua_State *L = ld->L; int status; const char *oldprogname = lua_get_progname(); lua_set_progname(NULL); while ((status = loadline(ld)) != -1) { if (status == LUA_OK) status = lua_docall(L, 0, LUA_MULTRET); if (status == LUA_OK) l_print(L); else report(L, status); } if (lua_gettop(L)) lua_settop(L, 0); /* clear stack */ lua_set_progname(oldprogname); } static void handle_server_requests(luaData_t *ld) { struct sockaddr_in ipaddr; socklen_t len; ld->client_socket = -1; do { len = sizeof(struct sockaddr_in); if ((ld->client_socket = accept(ld->server_socket, (struct sockaddr *)&ipaddr, &len)) < 0) { perror("accept failed"); break; } if (ld->client_socket > 0) { _socket_open(ld); lua_set_stdfiles(ld); doREPL(ld); lua_reset_stdfiles(ld); _socket_close(ld); close(ld->client_socket); ld->client_socket = -1; } } while (1); if (ld->server_socket > 0) { close(ld->server_socket); ld->server_socket = -1; } } static void * lua_server(void *arg) { luaData_t *ld = arg; if (server_startup(ld)) fprintf(stderr, "server_startup() failed!\n"); handle_server_requests(ld); return NULL; } int rte_thread_set_name(pthread_t id, const char *name); int lua_start_socket(luaData_t *ld, pthread_t *pthread, char *hostname, int port) { int r; ld->client_socket = -1; ld->server_socket = -1; ld->socket_port = port; ld->hostname = strdup((hostname) ? hostname : "localhost"); /* Split assert and function because using NDEBUG define will remove function */ r = pthread_create(pthread, NULL, lua_server, ld); if (r) return -1; rte_thread_set_name(*pthread, "pktgen-socket"); return 0; } ================================================ FILE: lib/lua/lua_socket.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2010 by Keith Wiles @ intel.com */ #ifndef _RTE_LUA_SOCKET_H_ #define _RTE_LUA_SOCKET_H_ /** * @file * * Lua TCP socket server for remote script execution. * * Starts a background thread that listens on a TCP port and executes * Lua code received from connected clients, enabling external control * of a running Pktgen instance. */ #include #include #include #include #include #include #include #include #include #define lua_c #include #include #include #ifdef __cplusplus extern "C" { #endif /** * Start the Lua TCP socket server in a background pthread. * * The server accepts connections on @p port and executes received Lua * strings within the @p ld Lua instance. * * @param ld * Lua instance to use for executing received scripts. * @param pthread * Output: handle of the created pthread. * @param hostname * Hostname or IP address string to bind to. * @param port * TCP port number to listen on. * @return * 0 on success, -1 on error. */ int lua_start_socket(luaData_t *ld, pthread_t *pthread, char *hostname, int port); #ifdef __cplusplus } #endif #endif /* _RTE_LUA_SOCKET_H_ */ ================================================ FILE: lib/lua/lua_stdio.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lua_config.h" #include "lua_stdio.h" #define tolstream(L) ((LStream *)luaL_checkudata(L, 1, LUA_FILEHANDLE)) typedef luaL_Stream LStream; void * lua_get_stdout(luaData_t *ld) { if (!ld || !ld->out) return stdout; return ld->out; } void * lua_get_stdin(luaData_t *ld) { if (!ld || !ld->in) return stdin; return ld->in; } void * lua_get_stderr(luaData_t *ld) { if (!ld || !ld->err) return stderr; return ld->err; } void lua_signal_set_stdfiles(luaData_t *ld) { lua_set_stdfiles(ld); signal(SIGPIPE, SIG_IGN); } void lua_signal_reset_stdfiles(luaData_t *ld) { signal(SIGPIPE, SIG_DFL); lua_reset_stdfiles(ld); } void lua_set_stdfiles(luaData_t *ld) { lua_State *L = ld->L; luaL_getmetatable(L, LUA_FILEHANDLE); if (lua_isnil(L, -1)) { DBG("luaL_getmetatable() returned NIL\n"); return; } /* create (and set) default files */ lua_create_stdfile(ld, ld->in, IO_INPUT, "stdin"); lua_create_stdfile(ld, ld->out, IO_OUTPUT, "stdout"); lua_create_stdfile(ld, ld->err, NULL, "stderr"); } void lua_reset_stdfiles(luaData_t *ld) { lua_State *L = ld->L; luaL_getmetatable(L, LUA_FILEHANDLE); if (lua_isnil(L, -1)) return; /* create (and set) default files */ lua_create_stdfile(ld, stdin, IO_INPUT, "stdin"); lua_create_stdfile(ld, stdout, IO_OUTPUT, "stdout"); lua_create_stdfile(ld, stderr, NULL, "stderr"); } /* ** function to (not) close the standard files stdin, stdout, and stderr */ static int io_noclose(lua_State *L) { LStream *p = tolstream(L); p->closef = &io_noclose; /* keep file opened */ lua_pushnil(L); lua_pushliteral(L, "cannot close standard file"); return 2; } static LStream * newprefile(lua_State *L) { LStream *p = (LStream *)lua_newuserdata(L, sizeof(LStream)); p->closef = NULL; /* mark file handle as 'closed' */ luaL_setmetatable(L, LUA_FILEHANDLE); return p; } void lua_create_stdfile(luaData_t *ld, FILE *f, const char *k, const char *fname) { lua_State *L = ld->L; LStream *p = newprefile(L); p->f = f; p->closef = &io_noclose; if (k != NULL) { lua_pushvalue(L, -1); lua_setfield(L, LUA_REGISTRYINDEX, k); /* add file to registry */ } lua_setfield(L, -2, fname); /* add file to module */ } ================================================ FILE: lib/lua/lua_stdio.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #ifndef _RTE_LUA_STDIO_H_ #define _RTE_LUA_STDIO_H_ /** * @file * * Lua stdio stream redirection helpers. * * These functions allow Lua's stdin/stdout/stderr to be redirected to * arbitrary FILE objects, which is needed when Lua scripts run over a * TCP socket rather than the process's standard streams. */ #include #include #include #include #include #include #include #include #include #define lua_c #include #include #include #ifdef __cplusplus extern "C" { #endif /** * Return the FILE pointer used as Lua's stdout for instance @p ld. * * @param ld Lua instance. * @return FILE pointer for stdout, or NULL if not set. */ void *lua_get_stdout(luaData_t *ld); /** * Return the FILE pointer used as Lua's stdin for instance @p ld. * * @param ld Lua instance. * @return FILE pointer for stdin, or NULL if not set. */ void *lua_get_stdin(luaData_t *ld); /** * Return the FILE pointer used as Lua's stderr for instance @p ld. * * @param ld Lua instance. * @return FILE pointer for stderr, or NULL if not set. */ void *lua_get_stderr(luaData_t *ld); /** * Create and register a Lua stdio file object backed by FILE @p f. * * @param ld Lua instance. * @param f Underlying C FILE to wrap. * @param k Lua global key name for this file (e.g. "stdout"). * @param fname Lua filename string associated with the file object. */ void lua_create_stdfile(luaData_t *ld, FILE *f, const char *k, const char *fname); /** * Redirect Lua's stdin/stdout/stderr to the socket streams in @p ld. * * @param ld Lua instance whose socket streams should become the std files. */ void lua_set_stdfiles(luaData_t *ld); /** * Restore Lua's stdin/stdout/stderr to the process standard streams. * * @param ld Lua instance to reset. */ void lua_reset_stdfiles(luaData_t *ld); /** * Set Lua's stdio streams from within a signal handler context. * * Signal-safe variant of lua_set_stdfiles(). * * @param ld Lua instance. */ void lua_signal_set_stdfiles(luaData_t *ld); /** * Reset Lua's stdio streams from within a signal handler context. * * Signal-safe variant of lua_reset_stdfiles(). * * @param ld Lua instance. */ void lua_signal_reset_stdfiles(luaData_t *ld); #ifdef __cplusplus } #endif #endif /* _RTE_LUA_STDIO_H_ */ ================================================ FILE: lib/lua/lua_utils.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2010 by Keith Wiles @ intel.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lua_config.h" #include "lua_stdio.h" #include "lua_utils.h" char * lua_strtrim(char *str) { if (!str || !*str) return str; /* trim white space characters at the front */ while (isspace(*str)) str++; /* Make sure the string is not empty */ if (*str) { char *p = &str[strlen(str) - 1]; /* trim trailing white space characters */ while ((p >= str) && isspace(*p)) p--; p[1] = '\0'; } return *str ? str : NULL; } ================================================ FILE: lib/lua/lua_utils.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #ifndef _RTE_LUA_UTILS_H_ #define _RTE_LUA_UTILS_H_ /** * @file * * Lua shell utility helpers: string trimming, console output, line reading, * error reporting, and Lua status checking. */ #include #include #include #include #include #include #include #include #include #include #define lua_c #include #include #include #ifdef __cplusplus extern "C" { #endif /** * Trim leading and trailing whitespace from @p str in place. * * @param str String to trim. Modified in place. * @return Pointer to the first non-whitespace character within @p str. */ char *lua_strtrim(char *str); /** * Write a formatted string to stdout and flush. * * @param format printf-style format string. * @param ... Variable arguments for @p format. */ static inline void lua_putstring(const char *format, ...) { va_list ap; va_start(ap, format); vfprintf(stdout, format, ap); va_end(ap); fflush(stdout); } /** * Read one line of input from the Lua instance's stdin into its buffer. * * @param ld Lua instance whose stdin and buffer are used. * @return Pointer to ld->buffer on success, or NULL on EOF/error. */ static inline char * lua_readline(luaData_t *ld) { *ld->buffer = '\0'; return fgets(ld->buffer, LUA_BUFFER_SIZE, lua_get_stdin(ld)); } /** * Print a Lua error message to stderr, optionally prefixed by @p pname. * * @param pname Programme name prefix, or NULL to omit. * @param msg Error message string to print. */ static inline void l_message(const char *pname, const char *msg) { if (pname) fprintf(stderr, "%s: ", pname); fprintf(stderr, "%s\n", msg); } /** * Check @p status and, if it indicates an error, print the top-of-stack message. * * Assumes the error object is a string (as generated by Lua or msghandler). * Pops the error string from the stack before returning. * * @param L Lua state. * @param status Return value from a Lua protected call (e.g. lua_pcall). * @return @p status unchanged. */ static inline int report(lua_State *L, int status) { if (status != LUA_OK) { const char *msg = lua_tostring(L, -1); l_message(lua_get_progname(), msg); lua_pop(L, 1); /* remove message */ } return status; } #ifdef __cplusplus } #endif #endif /* _LUA_SUPPORT_H_ */ ================================================ FILE: lib/lua/lua_vec.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #define lua_dpdk_c #define LUA_LIB #define lua_c #include #include #include #include #include #include #include #include "lua_config.h" #include "lua_stdio.h" #include "lua_dpdk.h" #include "lua_vec.h" #include "lua_utils.h" #ifndef __INTEL_COMPILER #pragma GCC diagnostic ignored "-Wcast-qual" #endif static const char *Vec = "Vec"; static int vec_new(lua_State *L) { vec_t *v; int size = 0, top = lua_gettop(L); if (top > 1) return luaL_error(L, "new, Invalid arg count should be 0 or 1"); if (top >= 1) size = luaL_checkint(L, 1); if (size == 0) size = vec_calc_size(0); v = (struct vec *)lua_newuserdata(L, (sizeof(struct vec) * (size * sizeof(void *)))); vec_init(v, size, VEC_CREATE_FLAG); luaL_getmetatable(L, Vec); lua_setmetatable(L, -2); return 1; } static int lvec_add1(lua_State *L) { struct vec *v; validate_arg_count(L, 2); v = (struct vec *)luaL_checkudata(L, 1, Vec); if (v->len >= v->tlen) return 0; v->list[v->len++] = lua_touserdata(L, 2); return 1; } static int vec_tostring(lua_State *L) { struct vec *v; char buff[64]; v = (struct vec *)luaL_checkudata(L, 1, Vec); lua_getmetatable(L, 1); lua_getfield(L, -1, "__name"); snprintf(buff, sizeof(buff), "%s<%d,%d,%s,0x%04x>", lua_tostring(L, -1), v->len, v->tlen, (v->vpool) ? "V" : "_", v->flags); lua_pop(L, 3); lua_pushstring(L, buff); return 1; } static int vec_gc(lua_State *L) { struct vec *v; struct rte_mbuf *m; int i; if (lua_gettop(L) != 1) return luaL_error(L, "vec.gc, Invalid arg count should be 1"); v = (struct vec *)lua_touserdata(L, 1); vec_foreach(i, m, v) { if (m) rte_pktmbuf_free(m); } vec_free(v); return 0; } static const struct luaL_Reg vec_methods[] = {{"add1", lvec_add1}, {NULL, NULL}}; static const struct luaL_Reg vec_functions[] = {{"new", vec_new}, {NULL, NULL}}; int luaopen_vec(lua_State *L) { luaL_newmetatable(L, Vec); // create and push new table called Vec lua_pushvalue(L, -1); // dup the table on the stack lua_setfield(L, -2, "__index"); // luaL_setfuncs(L, vec_methods, 0); lua_pushstring(L, "__tostring"); lua_pushcfunction(L, vec_tostring); lua_settable(L, -3); lua_pushstring(L, "__gc"); lua_pushcfunction(L, vec_gc); lua_settable(L, -3); lua_pop(L, 1); lua_getglobal(L, LUA_DPDK_LIBNAME); lua_newtable(L); luaL_setfuncs(L, vec_functions, 0); lua_setfield(L, -2, LUA_VEC_LIBNAME); return 1; } ================================================ FILE: lib/lua/lua_vec.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #ifndef _RTE_LUA_VEC_H_ #define _RTE_LUA_VEC_H_ /** * @file * * Lua bindings for the vec (pointer vector) library. * * Exposes vec container operations to Lua scripts under the "vec" * library name. */ #include #include #include #define lua_c #include #include #ifdef __cplusplus extern "C" { #endif #define LUA_VEC_LIBNAME "vec" /** * Open the vec Lua library and register its functions. * * Called automatically by the Lua runtime when the library is required. * * @param L * Lua state to register the library into. * @return * Number of values pushed onto the Lua stack (1 — the library table). */ int luaopen_vec(lua_State *L); #ifdef __cplusplus } #endif #endif /* _RTE_LUA_VEC_H_ */ ================================================ FILE: lib/lua/meson.build ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2017-2026> Intel Corporation if get_option('enable_lua') sources = files( 'lua_config.c', 'lua_dapi.c', 'lua_dpdk.c', 'lua_pktmbuf.c', 'lua_socket.c', 'lua_stdio.c', 'lua_utils.c', 'lua_vec.c') liblua = library('lua', sources, dependencies: [common, utils, vec, lua_dep, dpdk]) else sources = files('lua_config.c') liblua = library('lua', sources) endif lua = declare_dependency(link_with: liblua, include_directories: include_directories('.')) ================================================ FILE: lib/meson.build ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2020-2026> Intel Corporation subdir('hmap') subdir('common') subdir('utils') subdir('vec') subdir('plugin') subdir('cli') subdir('lua') ================================================ FILE: lib/plugin/meson.build ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2018-2026> Intel Corporation sources = files('plugin.c') libplugin = library('plugin', sources, dependencies: [common, dpdk]) plugin = declare_dependency(link_with: libplugin, include_directories: include_directories('.')) ================================================ FILE: lib/plugin/plugin.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2018-2026> Intel Corporation. */ #include #include #include #include #include #include #include #include #include #include #ifdef RTE_ARCH_X86 #include #define hash_func rte_hash_crc #else #include #define hash_func rte_jhash #endif #include "plugin.h" static rte_rwlock_t _plugin_rwlock = RTE_RWLOCK_INITIALIZER; rte_rwlock_t *plugin_rwlock = &_plugin_rwlock; #define RTE_PLUGIN_TAILQ_RWLOCK plugin_rwlock static TAILQ_HEAD(plugin_list, plugin) plugin_head = TAILQ_HEAD_INITIALIZER(plugin_head); static struct plugin *plugin_inst[PLUGIN_MAX_INST]; static const char *plugin_fmts[] = { "%s", "%s.so", "lib%s", "lib%s.so", "librte_%s.so", "librte_pmd_%s.so", NULL}; struct plugin * plugin_get(int inst) { if (inst < 0 || inst >= PLUGIN_MAX_INST) return NULL; return plugin_inst[inst]; /* This could be NULL as well */ } static int plugin_add_inst(struct plugin *pin) { int inst; rte_rwlock_write_lock(RTE_PLUGIN_TAILQ_RWLOCK); for (inst = 0; inst < PLUGIN_MAX_INST; inst++) { if (plugin_inst[inst] == NULL) { plugin_inst[inst] = pin; rte_rwlock_write_unlock(RTE_PLUGIN_TAILQ_RWLOCK); return inst; } } rte_rwlock_write_unlock(RTE_PLUGIN_TAILQ_RWLOCK); return -1; } static int plugin_del_inst(int inst) { struct plugin *pin = plugin_get(inst); if (!pin) { PLUGIN_LOG(DEBUG, "Plugin pointer is NULL or info is NULL\n"); return -1; } /* remove the plugin from the global list */ rte_rwlock_write_lock(RTE_PLUGIN_TAILQ_RWLOCK); plugin_inst[inst] = NULL; if (rte_atomic32_dec_and_test(&pin->refcnt)) { /* remove instance when refcnt becomes zero */ TAILQ_REMOVE(&plugin_head, pin, next); dlclose(pin->dl); free(pin->plugin); free(pin->path); free(pin); } rte_rwlock_write_unlock(RTE_PLUGIN_TAILQ_RWLOCK); return 0; } static int plugin_find_inst(struct plugin *pin) { int inst; rte_rwlock_write_lock(RTE_PLUGIN_TAILQ_RWLOCK); for (inst = 0; inst < PLUGIN_MAX_INST; inst++) { if (plugin_inst[inst] == pin) { rte_rwlock_write_unlock(RTE_PLUGIN_TAILQ_RWLOCK); return inst; } } rte_rwlock_write_unlock(RTE_PLUGIN_TAILQ_RWLOCK); return -1; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" /** * Routine to search for and open a plugin. * * @param pin * The plugin structure pointer. * @return * 0 if plugin found and loaded or -1 on not found. */ static int plugin_open(struct plugin *pin) { char file[1024]; int i; for (i = 0; plugin_fmts[i]; i++) { char fn[64]; snprintf(fn, sizeof(fn), plugin_fmts[i], pin->plugin); if (pin->path) snprintf(file, sizeof(file), "%s/%s", pin->path, fn); else snprintf(file, sizeof(file), "%s", fn); printf("Trying to load this file (%s)\n", file); pin->dl = dlopen(file, RTLD_NOW | RTLD_LOCAL); if (pin->dl) return 0; printf("dlopen: %s\n", dlerror()); } return -1; } #pragma GCC diagnostic pop int plugin_get_symbol(struct plugin *pin, const char *sym, char **ret) { char *val; void *v; if (!pin || !ret) return -1; *ret = NULL; (void)dlerror(); /* Clear errors */ PLUGIN_LOG(INFO, "dlsym %p for %s:%d\n", pin->dl, pin->plugin, pin->inst); v = dlsym(pin->dl, sym); /* could return NULL */ val = dlerror(); if (val) { PLUGIN_LOG(ERR, "failed to get symbol (%s)\n", val); return -1; } *ret = v; return 0; } int plugin_create(char *plugin, char *path) { struct plugin *pin; struct plugin_info *info; int inst, ret; char sym[64]; /* Make sure we have not already loaded this plugin */ inst = plugin_find_by_name(plugin); if (inst >= 0) { pin = plugin_get(inst); /* Did we find a new instance of the same plugin */ if (pin && !strcmp(path, pin->path)) { info = pin->info; PLUGIN_LOG(DEBUG, "Plugin: '%s' - %s, version: %d.%02d.%02d-rc%d\n", pin->plugin, info->desc, info->major, info->minor, info->patch, info->release); return inst; } /* not the same plugin */ } /* Create the plugin structure and begin loading plugin */ pin = calloc(1, sizeof(struct plugin)); if (!pin) return -1; memset(pin, 0, sizeof(struct plugin)); pin->inst = -1; inst = plugin_add_inst(pin); if (inst < 0) { PLUGIN_LOG(ERR, "plugin_add_inst() failed for %s\n", plugin); free(pin); return -1; } pin->inst = inst; /* Save these away for later comparsions */ pin->plugin = strdup(plugin); pin->path = strdup(path); if (plugin_open(pin)) { PLUGIN_LOG(ERR, "dlopen() unable to load %s\n", pin->plugin); PLUGIN_LOG(ERR, "dlerror: %s\n", dlerror()); goto err_exit; } /* Look for the plugin information structure */ snprintf(sym, sizeof(sym), "%s_plugin_info", pin->plugin); ret = plugin_get_symbol(pin, sym, (char **)&info); if (ret < 0) { PLUGIN_LOG(ERR, "Invalid plugin %s not found\n", sym); goto err_exit; } pin->info = info; PLUGIN_LOG(DEBUG, "Plugin: '%s' - %s, version: %d.%02d.%02d-rc%d\n", pin->plugin, info->desc, info->major, info->minor, info->patch, info->release); /* Create the CRC 32bit value using plugin name, path and info data */ pin->hash = hash_func(pin->plugin, strlen(pin->plugin), pin->hash); pin->hash = hash_func(pin->path, strlen(pin->path), pin->hash); pin->hash = hash_func(pin->info, sizeof(struct plugin_info), pin->hash); /* add the plugin to the global list */ rte_rwlock_write_lock(RTE_PLUGIN_TAILQ_RWLOCK); TAILQ_INSERT_TAIL(&plugin_head, pin, next); rte_rwlock_write_unlock(RTE_PLUGIN_TAILQ_RWLOCK); return inst; err_exit: PLUGIN_LOG(ERR, "Failed to create plugin %s\n", plugin); plugin_del_inst(inst); return -1; } int plugin_destroy(int inst) { if (inst < 0) return -1; return plugin_del_inst(inst); } int plugin_start(int inst, void *arg) { struct plugin *pin = plugin_get(inst); if (!pin) { PLUGIN_LOG(DEBUG, "Plugin pointer is NULL\n"); return -1; } /* if we have a entry point then call to unload plugin */ if (pin->info->start(inst, arg)) { PLUGIN_LOG(DEBUG, "Start of %s failed\n", pin->plugin); return -1; } return 0; } int plugin_stop(int inst) { struct plugin *pin = plugin_get(inst); if (!pin) { PLUGIN_LOG(DEBUG, "Plugin pointer is NULL or info is NULL\n"); return -1; } /* if we have a entry point then call to unload plugin */ if (pin->info->stop(inst)) { PLUGIN_LOG(DEBUG, "Start of %s failed\n", pin->plugin); return -1; } return 0; } int plugin_find_by_name(const char *name) { struct plugin *pin; rte_rwlock_write_lock(RTE_PLUGIN_TAILQ_RWLOCK); TAILQ_FOREACH (pin, &plugin_head, next) { if (!strcmp(name, pin->plugin)) { rte_rwlock_write_unlock(RTE_PLUGIN_TAILQ_RWLOCK); return pin->inst; } } rte_rwlock_write_unlock(RTE_PLUGIN_TAILQ_RWLOCK); return -1; } int plugin_find_by_id(uint32_t h) { struct plugin *pin; rte_rwlock_write_lock(RTE_PLUGIN_TAILQ_RWLOCK); TAILQ_FOREACH (pin, &plugin_head, next) { if (h == pin->hash) { int inst = plugin_find_inst(pin); rte_rwlock_write_unlock(RTE_PLUGIN_TAILQ_RWLOCK); return inst; } } rte_rwlock_write_unlock(RTE_PLUGIN_TAILQ_RWLOCK); return -1; } static void _plugin_dump(FILE *f, struct plugin *pin) { if (!f) f = stderr; if (!pin) { fprintf(f, "*** Plugin pointer is NULL\n"); return; } fprintf(f, "%08x %-16s %6d %s\n", pin->hash, pin->plugin, rte_atomic32_read(&pin->refcnt), (pin->info) ? pin->info->desc : "---"); } void plugin_dump(FILE *f) { struct plugin *pin = NULL; int cnt = 0; rte_rwlock_read_lock(RTE_PLUGIN_TAILQ_RWLOCK); fprintf(f, "**** Plugins ****\n"); fprintf(f, "%-8s %-16s %-6s %s\n", "ID", "Name", "refcnt", "Description"); TAILQ_FOREACH (pin, &plugin_head, next) { _plugin_dump(f, pin); cnt++; } fprintf(f, "Total plugins found: %d\n", cnt); rte_rwlock_read_unlock(RTE_PLUGIN_TAILQ_RWLOCK); } int libplugin_logtype; RTE_INIT(libplugin_init_log) { libplugin_logtype = rte_log_register("librte.plugin"); if (libplugin_logtype >= 0) rte_log_set_level(libplugin_logtype, RTE_LOG_INFO); } ================================================ FILE: lib/plugin/plugin.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2018-2026> Intel Corporation. */ #ifndef _PLUGIN_H_ #define _PLUGIN_H_ #include #include #include #include #include #include #include #include /** * @file * PLUGIN * * The plugin library provides the ability to add and remove modules written * in C or other languages in a .so format. */ #ifdef __cplusplus extern "C" { #endif #define PLUGIN_MAX_INST 32 extern int libplugin_logtype; #define PLUGIN_LOG(level, fmt, args...) \ rte_log(RTE_LOG_##level, libplugin_logtype, "%s(%d): " fmt, __func__, getpid(), ##args) #define PLUGIN_MKVER(_M, _m, _p, _r) .major = _M, .minor = _m, .patch = _p, .release = _r #define PLUGIN_INFO(_n, _d, _f, _v) \ struct plugin_info _n##_plugin_info = { \ .desc = _d, .start = _n##_start, .stop = _n##_stop, .pfuncs = (void *)_f, _v} struct plugin_info { const char *desc; /**< Short plugin description */ int (*start)(int inst, void *arg); /**< start function optional */ int (*stop)(int inst); /**< stop function optional */ void *pfuncs; /**< plugin defined functions/info */ union { uint32_t version; /* 18.04.00-rc1 == 18040001 */ struct { uint8_t major; /**< Version of Plugin */ uint8_t minor; uint8_t patch; uint8_t release; }; }; }; struct plugin { TAILQ_ENTRY(plugin) next; /**< Next plugin pointer */ char *plugin; /**< short name of plugin .do */ char *path; /**< path to plugin */ void *dl; /**< dlopen handle pointer */ rte_atomic32_t refcnt; /**< reference count */ uint32_t hash; /**< Plugin ID value */ int inst; /**< Plugin instance ID value */ struct plugin_info *info; /**< Pointer to plugin info struct */ }; /** * Create a plugin instance by defining the name and path if needed. * * @param name * The short name of the plugin minus the .so or .do, no path included. * @param path * The path to the plugin if not in LD_LIBRARY_PATH environment variable. * @return * the instance number or -1 if error */ int plugin_create(char *name, char *path); /** * Destroy a given instance of a plugin * * @param inst * The inst number for the plugin. * @return * < 0 is error and 0 is success. */ int plugin_destroy(int inst); /** * start the plugin * * @param inst * The instance number * @param arg * User defined value to the plugin on start up. * @return * -1 on error or 0 if OK. */ int plugin_start(int inst, void *arg); /** * stop the plugin * * @param inst * The instance number * @return * -1 on error or 0 if OK. */ int plugin_stop(int inst); /** * Return the struct plugin pointer by instance index * * @param inst * The instance index value * @return * The struct rte+plugin pointer for the instance or NULL */ struct plugin *plugin_get(int inst); /** * Locate the plugin information by name. * * @param name * The short name of the plugin to be found. * @return * instance number or -1 on error */ int plugin_find_by_name(const char *name); /** * Locate the plugin information by ID value. * * @param id * ID of the plugin. * @return * The instance number or -1 on error */ int plugin_find_by_id(uint32_t id); /** * Dump out the list of plugins in the system. * * @param f * The file pointer used to output the information, if NULL use stderr. */ void plugin_dump(FILE *f); /** * return the description for the given instance id value * * @param pin * The plugin_info pointer * @return * The description string or NULL if not found. */ static inline const char * plugin_desc(struct plugin *pin) { if (pin && pin->info) return pin->info->desc; return NULL; } /** * return the plugin hash ID for the given instance id value * * @param pin * The plugin data structure pointer * @param id * Varable to put the id value. * @return * -1 on error or 0 on success */ static inline int plugin_get_id(struct plugin *pin, uint32_t *id) { if (!pin || !id) return -1; *id = pin->hash; return 0; } /** * Get the version word from the plugin information * * @param inst * The instance index value * @return * The 32bit unsigned version number of the plugin * @return * -1 is error and 0 on success */ static inline uint32_t plugin_get_version(int inst) { struct plugin *pin = plugin_get(inst); if (!pin || !pin->info) return -1; return pin->info->version; } /** * Get the struct plugin_info structure suppled by the plugin. * * @param inst * The instance index value * @return * NULL on error or pointer to info structure. */ static inline struct plugin_info * plugin_get_info(int inst) { struct plugin *pin = plugin_get(inst); if (!pin) return NULL; return pin->info; } /** * Return a pointer to a global symbol in the plugin code. * * @param ping * The instance of plugin structure * @param symbol_name * The symbol string to search for in the plugin. * @param ret * Place to put the symbol address if found. * @return * -1 on error or 0 if success and ret contains the address. */ int plugin_get_symbol(struct plugin *pin, const char *symbol_name, char **ret); #ifdef __cplusplus } #endif #endif /* _PLUGIN_H_ */ ================================================ FILE: lib/utils/_atoip.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ #include "pg_strings.h" #include "_atoip.h" /* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our own. */ static int isblank2(char c) { if (c == ' ' || c == '\t') return 1; return 0; } static int isendofline(char c) { if (c == '\n' || c == '\r') return 1; return 0; } static int iscomment(char c) { if (c == '#') return 1; return 0; } static int rte_isendoftoken(char c) { if (!c || iscomment(c) || isblank2(c) || isendofline(c)) return 1; return 0; } /* * Like inet_aton() but without all the hexadecimal and shorthand. * return: * 1 if `src' is a valid dotted quad, else 0. * notice: * does not touch `dst' unless it's returning 1. * author: * Paul Vixie, 1996. */ static int inet_ipton4(const char *src, unsigned char *dst) { static const char digits[] = "0123456789"; int saw_digit, octets, ch; unsigned char tmp[RTE_INADDRSZ], *tp; saw_digit = 0; octets = 0; *(tp = tmp) = 0; while ((ch = *src++) != '\0') { const char *pch; if ((pch = strchr(digits, ch)) != NULL) { unsigned int new = *tp * 10 + (pch - digits); if (new > 255) return 0; if (!saw_digit) { if (++octets > 4) return 0; saw_digit = 1; } *tp = (unsigned char)new; } else if (ch == '.' && saw_digit) { if (octets == 4) return 0; *++tp = 0; saw_digit = 0; } else return 0; } if (octets < 4) return 0; memcpy(dst, tmp, RTE_INADDRSZ); return 1; } /* * Convert presentation level address to network order binary form. * return: * 1 if `src' is a valid [RFC1884 2.2] address, else 0. * notice: * (1) does not touch `dst' unless it's returning 1. * (2) :: in a full address is silently ignored. * credit: * inspired by Mark Andrews. * author: * Paul Vixie, 1996. */ static int inet_ipton6(const char *src, unsigned char *dst) { static const char xdigits_l[] = "0123456789abcdef", xdigits_u[] = "0123456789ABCDEF"; unsigned char tmp[RTE_IN6ADDRSZ], *tp = 0, *endp = 0, *colonp = 0; const char *xdigits = 0, *curtok = 0; int ch = 0, saw_xdigit = 0, count_xdigit = 0; unsigned int val = 0; unsigned dbloct_count = 0; memset((tp = tmp), '\0', RTE_IN6ADDRSZ); endp = tp + RTE_IN6ADDRSZ; colonp = NULL; /* Leading :: requires some special handling. */ if (*src == ':') if (*++src != ':') return 0; curtok = src; saw_xdigit = count_xdigit = 0; val = 0; while ((ch = *src++) != '\0') { const char *pch; if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) pch = strchr((xdigits = xdigits_u), ch); if (pch != NULL) { if (count_xdigit >= 4) return 0; val <<= 4; val |= (pch - xdigits); if (val > 0xffff) return 0; saw_xdigit = 1; count_xdigit++; continue; } if (ch == ':') { curtok = src; if (!saw_xdigit) { if (colonp) return 0; colonp = tp; continue; } else if (*src == '\0') return 0; if (tp + sizeof(int16_t) > endp) return 0; *tp++ = (unsigned char)((val >> 8) & 0xff); *tp++ = (unsigned char)(val & 0xff); saw_xdigit = 0; count_xdigit = 0; val = 0; dbloct_count++; continue; } if (ch == '.' && ((tp + RTE_INADDRSZ) <= endp) && inet_ipton4(curtok, tp) > 0) { tp += RTE_INADDRSZ; saw_xdigit = 0; dbloct_count += 2; break; /* '\0' was seen by inet_pton4(). */ } return 0; } if (saw_xdigit) { if (tp + sizeof(int16_t) > endp) return 0; *tp++ = (unsigned char)((val >> 8) & 0xff); *tp++ = (unsigned char)(val & 0xff); dbloct_count++; } if (colonp != NULL) { /* if we already have 8 double octets, having a colon means error */ if (dbloct_count == 8) return 0; /* Use memmove()'s to handle overlapping regions. */ const int n = tp - colonp; // Move n bytes from colonp to the end of tmp memmove(endp - n, colonp, n); // Clear original memory memset(colonp, 0, n); tp = endp; } if (tp != endp) return 0; memcpy(dst, tmp, RTE_IN6ADDRSZ); return 1; } /* * Convert from presentation format (which usually means ASCII printable) * to network format (which is usually some kind of binary format). * @return: * 1 if the address was valid for the specified address family * 0 if the address wasn't valid (`dst' is untouched in this case) * -1 if some other error occurred (`dst' is untouched in this case, too) * author: * Paul Vixie, 1996. */ static int inet_ipton(int af, const char *src, void *dst) { switch (af) { case AF_INET: return inet_ipton4(src, dst); case AF_INET6: return inet_ipton6(src, dst); default: errno = EAFNOSUPPORT; return -1; } /* NOTREACHED */ } int _atoip(const char *buf, int flags, void *res, unsigned ressize) { unsigned int token_len = 0; char ip_str[INET6_ADDRSTRLEN + 4 + 1]; /* '+4' is for prefixlen (if any) */ struct rte_ipaddr ipaddr; char *prefix, *prefix_end; long prefixlen = 0; int default_max_prefix = 0; if (res && ressize < sizeof(struct rte_ipaddr)) return -1; if (!buf || !*buf) return -1; while (!rte_isendoftoken(buf[token_len])) token_len++; /* if token is too big... */ if (token_len >= INET6_ADDRSTRLEN + 4) return -1; snprintf(ip_str, token_len + 1, "%s", buf); /* convert the network prefix */ if (flags & RTE_IPADDR_NETWORK) { prefix = strrchr(ip_str, '/'); if (prefix == NULL) { default_max_prefix = 1; } else { *prefix = '\0'; prefix++; errno = 0; prefixlen = strtol(prefix, &prefix_end, 10); if (errno || (*prefix_end != '\0') || prefixlen < 0 || prefixlen > RTE_PREFIXMAX) return -1; ipaddr.prefixlen = prefixlen; } } else ipaddr.prefixlen = 0; /* convert the IP addr */ if (inet_ipton(AF_INET, ip_str, &ipaddr.ipv4) == 1 && prefixlen <= RTE_V4PREFIXMAX) { if (default_max_prefix) ipaddr.prefixlen = RTE_V4PREFIXMAX; ipaddr.family = AF_INET; if (res) memcpy(res, &ipaddr, sizeof(ipaddr)); return 4; } else if (inet_ipton(AF_INET6, ip_str, &ipaddr.ipv6) == 1) { ipaddr.family = AF_INET6; if (default_max_prefix) ipaddr.prefixlen = RTE_PREFIXMAX; if (res) memcpy(res, &ipaddr, sizeof(ipaddr)); return 6; } return -1; } ================================================ FILE: lib/utils/_atoip.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /** * @file * * String-related utility functions for IP addresses */ #ifndef __ATOIP_H_ #define __ATOIP_H_ #include #include #ifdef __cplusplus extern "C" { #endif #define RTE_IPADDR_V4 0x01 /**< Flag: address is IPv4 */ #define RTE_IPADDR_V6 0x02 /**< Flag: address is IPv6 */ #define RTE_IPADDR_NETWORK 0x04 /**< Flag: parse as network/prefix notation */ #define RTE_INADDRSZ 4 /**< Size of an IPv4 address in bytes */ #define RTE_IN6ADDRSZ 16 /**< Size of an IPv6 address in bytes */ #define RTE_PREFIXMAX 128 /**< Maximum IPv6 prefix length in bits */ #define RTE_V4PREFIXMAX 32 /**< Maximum IPv4 prefix length in bits */ /** Holds a parsed IPv4 or IPv6 address, optionally with a prefix length. */ struct rte_ipaddr { uint8_t family; /**< Address family: AF_INET or AF_INET6 */ union { struct in_addr ipv4; /**< IPv4 address (when family == AF_INET) */ struct rte_ipv6_addr ipv6; /**< IPv6 address (when family == AF_INET6) */ }; unsigned int prefixlen; /**< Network prefix length in bits (0 if not a network address) */ }; /** * Convert an IPv4/v6 address into a binary value. * * @param buf * Location of string to convert * @param flags * Set of flags for converting IPv4/v6 addresses and netmask. * @param res * Location to put the results * @param ressize * Length of res in bytes. * @return * 4 or 6 on OK, indicating an IPv4/v6 address, respectively, and -1 on error */ int _atoip(const char *buf, int flags, void *res, unsigned ressize); #ifdef __cplusplus } #endif #endif /* __ATOIP_H_ */ ================================================ FILE: lib/utils/heap.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ #include #include #include #include "heap.h" heap_t * heap_create(void *addr, size_t size) { heap_t *heap = NULL; heap_entry_t *entry; /* Make sure the size is greater or equal to sizeof(rte_heap_entry_t) */ if (size < sizeof(heap_entry_t)) return NULL; heap = calloc(1, sizeof(heap_t)); if (!heap) return NULL; pthread_spin_init(&heap->sl, PTHREAD_PROCESS_PRIVATE); heap->addr = addr; heap->total_space = size; STAILQ_INIT(&heap->list); entry = addr; entry->next.stqe_next = NULL; entry->size = size; STAILQ_INSERT_TAIL(&heap->list, entry, next); return heap; } /****************************************************************************** * simpleMemDestroy - Destroy a heap. * * heap - is the free heap structure pointer. */ int heap_destroy(heap_t *heap) { /* Free the rte_heap_t structure */ if (heap) free(heap); return 0; } int heap_free(heap_t *heap, void *addr, size_t size) { heap_entry_t *p; heap_entry_t *q; /* the size can not be zero */ if (!heap || !addr || size == 0) return -1; pthread_spin_lock(&heap->sl); p = addr; p->next.stqe_next = NULL; p->size = size; STAILQ_FOREACH (q, &heap->list, next) { /* insert into ascending order */ if (p < q->next.stqe_next) break; } if (q) { if (RTE_PTR_ADD(p, size) == q->next.stqe_next) { p->size += q->size; p->next.stqe_next = q->next.stqe_next->next.stqe_next; } else /* non contiguous regions insert p into list */ p->next.stqe_next = q->next.stqe_next; /* join to lower element */ if (RTE_PTR_ADD(q, q->size) == p) { /* add size of prev region */ q->size += p->size; /* point to p->next region */ q->next.stqe_next = p->next.stqe_next; } else /* not contiguous regions insert p into list */ q->next.stqe_next = p; } else STAILQ_INSERT_TAIL(&heap->list, p, next); pthread_spin_unlock(&heap->sl); return 0; } /****************************************************************************** * simpleMemAlloc: allocate space for a tlv structure and data. * * This routine will allocate a contiguous amount of memory * of size (size) from the free list. * * This routine will search a linked list for the first * fit free space. * * This routine will return a null pointer if the contiguous * free space is not found. * * heap - is the pointer to the heap structure. * size - is the size of the requested memory. */ void * heap_alloc(heap_t *heap, size_t size) { heap_entry_t *hd; /* pointer to entry free space */ heap_entry_t *phd; /* prev head pointer to free list */ heap_entry_t *nxt_hd; /* temp space for pointer to entry */ heap_entry_t *ret_ptr; /* return pointer to free space */ if (!heap || size == 0) return NULL; pthread_spin_lock(&heap->sl); size = RTE_ALIGN_CEIL(size, sizeof(heap_entry_t)); /* * If the requested size is less then sizeof(rte_heap_entry_t) then set * the requested size to sizeof(rte_heap_entry_t) */ if (size < sizeof(heap_entry_t)) size = sizeof(heap_entry_t); ret_ptr = NULL; hd = STAILQ_FIRST(&heap->list); phd = (heap_entry_t *)&heap->list; if (hd) { if (size <= heap->total_space) { do { /* * if current free section size is equal to size then * point prev head. */ if (size == hd->size) { /* take size out of total free space */ heap->total_space -= size; /* to entry free section in list. */ phd->next.stqe_next = hd->next.stqe_next; ret_ptr = hd; break; } /* * section is larger than size build a new rte_heap_entry_t structure * in the left over space in this section. * nxt_hd is the new pointer to the structure after size. */ if (size <= (hd->size - sizeof(heap_entry_t))) { /* take size out of total free space */ heap->total_space -= size; /* current entry is now moved */ nxt_hd = RTE_PTR_ADD(hd, size); nxt_hd->next.stqe_next = hd->next.stqe_next; nxt_hd->size = hd->size - size; phd->next.stqe_next = nxt_hd; ret_ptr = hd; break; } phd = hd; /* Prev head = current head */ hd = hd->next.stqe_next; /* entry free space */ } while (hd); } } pthread_spin_unlock(&heap->sl); return ret_ptr; } /****************************************************************************** * simpleMemMalloc - Wrapper routine for simpleMemAlloc insert size into buffer. */ void * heap_malloc(heap_t *heap, size_t size) { uint64_t *addr; if (!heap || size == 0) return NULL; if (size < sizeof(heap_entry_t)) size = sizeof(heap_entry_t); addr = (uint64_t *)heap_alloc(heap, size + sizeof(uint64_t)); if (addr == NULL) return NULL; addr[0] = size + sizeof(uint64_t); return &addr[1]; } /****************************************************************************** * simpleMemMFree - Wrapper routine for simpleMemFree extract size from buffer. */ int heap_mfree(heap_t *heap, void *addr) { uint64_t *p; uint64_t size; if (heap == NULL) return -1; if (addr == NULL) return 0; if (((uint64_t)addr & 3) != 0) return -1; p = (uint64_t *)addr; p--; size = *p; if (size == 0) return -1; return heap_free(heap, p, size); } /****************************************************************************** * debug function */ void heap_dump(FILE *f, heap_t *heap) { heap_entry_t *p; uint64_t total; uint64_t largest; uint64_t segCount; if (!f) f = stdout; if (!heap) { fprintf(f, "Pointer to rte_heap_entry_t structure is NULL\n"); return; } fprintf(f, " Free Header = %p\n", (void *)heap); fprintf(f, " Address of Heap = %p\n", heap->addr); fprintf(f, " Total free space = %lu\n", heap->total_space); total = 0; largest = 0; segCount = 0; STAILQ_FOREACH (p, &heap->list, next) { fprintf(f, " Size = %ld\n", p->size); total += p->size; if (p->size > largest) largest = p->size; segCount++; } fprintf(f, " Total Free = %ld\n", total); fprintf(f, " Largest Free area = %ld\n", largest); fprintf(f, " Segment Count = %ld\n", segCount); } ================================================ FILE: lib/utils/heap.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ #ifndef __HEAP_H #define __HEAP_H /** * @file * * Fixed-size heap allocator backed by a caller-supplied memory region. * * The heap manages a contiguous block of memory supplied at creation time. * Allocations and frees are protected by a spinlock. The implementation * is intended for use in low-latency DPDK data-plane paths where dynamic * system allocators are undesirable. */ #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif typedef struct heap_entry { STAILQ_ENTRY(heap_entry) next; /**< pointer to next entry */ size_t size; /**< size of free entry */ } heap_entry_t; typedef struct heap { STAILQ_HEAD(, heap_entry) list; /**< Heap entry list */ void *addr; /**< Base Heap address pointer */ size_t total_space; /**< total space in heap */ pthread_spinlock_t sl; /**< Spinlock for this heap */ } heap_t; /** * Create a heap over a caller-supplied memory region. * * @param addr * Base address of the memory region to manage. * @param size * Total size of the region in bytes. * @return * Pointer to the new heap_t descriptor, or NULL on failure. */ heap_t *heap_create(void *addr, size_t size); /** * Destroy a heap and release the heap_t descriptor. * * The underlying memory region is NOT freed; the caller is responsible for * managing the lifetime of the memory passed to heap_create(). * * @param si * Heap to destroy. * @return * 0 on success, -1 on error. */ int heap_destroy(heap_t *si); /** * Allocate @p size bytes from heap @p si. * * The caller must track @p size to pass to heap_free(). * * @param si * Heap to allocate from. * @param size * Number of bytes to allocate. * @return * Pointer to the allocated region, or NULL if the heap has insufficient space. */ void *heap_alloc(heap_t *si, size_t size); /** * Return a previously allocated block back to heap @p si. * * @param si * Heap that owns the block. * @param addr * Pointer returned by heap_alloc(). * @param size * Original allocation size passed to heap_alloc(). * @return * 0 on success, -1 on error. */ int heap_free(heap_t *si, void *addr, size_t size); /** * Allocate @p size bytes from @p si, storing the size internally for later use by heap_mfree(). * * @param si * Heap to allocate from. * @param size * Number of bytes to allocate. * @return * Pointer to the allocated region, or NULL on failure. */ void *heap_malloc(heap_t *si, size_t size); /** * Free a block allocated with heap_malloc(). * * The allocation size is read from internal metadata; the caller does not * need to track it separately (unlike heap_free()). * * @param si * Heap that owns the block. * @param addr * Pointer returned by heap_malloc(). * @return * 0 on success, -1 on error. */ int heap_mfree(heap_t *si, void *addr); /** * Dump a human-readable description of the heap's free-list to @p f. * * @param f Output file (e.g. stdout or stderr). * @param si Heap to dump. */ void heap_dump(FILE *f, heap_t *si); #ifdef __cplusplus } #endif #endif /* __HEAP_H */ ================================================ FILE: lib/utils/inet_pton.c ================================================ /* * Copyright(c) 2004 by Internet Systems Consortium, Inc. ("ISC") * Copyright(c) 1996,1999 by Internet Software Consortium. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #if defined(LIBC_SCCS) && !defined(lint) static const char rcsid[] = "$Id: inet_pton.c,v 1.3.18.2 2005/07/28 07:38:07 marka Exp $"; #endif /* LIBC_SCCS and not lint */ #include // __FBSDID("$FreeBSD$"); #include #include // #include #include #include #include // #if __FreeBSD_version < 700000 // #define strchr index // #endif /*% * WARNING: Don't even consider trying to compile this on a system where * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. */ static int inet_pton4(const char *src, u_char *dst); static int inet_pton6(const char *src, u_char *dst); /* int * inet_pton(af, src, dst) * convert from presentation format (which usually means ASCII printable) * to network format (which is usually some kind of binary format). * return: * 1 if the address was valid for the specified address family * 0 if the address wasn't valid (`dst' is untouched in this case) * -1 if some other error occurred (`dst' is untouched in this case, too) * author: * Paul Vixie, 1996. */ int inet_pton(int af, const char *src, void *dst) { switch (af) { case AF_INET: return (inet_pton4(src, dst)); case AF_INET6: return (inet_pton6(src, dst)); default: return (-1); } /* NOTREACHED */ } /* int * inet_pton4(src, dst) * like inet_aton() but without all the hexadecimal and shorthand. * return: * 1 if `src' is a valid dotted quad, else 0. * notice: * does not touch `dst' unless it's returning 1. * author: * Paul Vixie, 1996. */ static int inet_pton4(const char *src, u_char *dst) { static const char digits[] = "0123456789"; int saw_digit, octets, ch; #define NS_INADDRSZ 4 u_char tmp[NS_INADDRSZ], *tp; saw_digit = 0; octets = 0; *(tp = tmp) = 0; while ((ch = *src++) != '\0') { const char *pch; if ((pch = strchr(digits, ch)) != NULL) { u_int new = *tp * 10 + (pch - digits); if (saw_digit && *tp == 0) return (0); if (new > 255) return (0); *tp = new; if (!saw_digit) { if (++octets > 4) return (0); saw_digit = 1; } } else if (ch == '.' && saw_digit) { if (octets == 4) return (0); *++tp = 0; saw_digit = 0; } else return (0); } if (octets < 4) return (0); memcpy(dst, tmp, NS_INADDRSZ); return (1); } /* int * inet_pton6(src, dst) * convert presentation level address to network order binary form. * return: * 1 if `src' is a valid [RFC1884 2.2] address, else 0. * notice: * (1) does not touch `dst' unless it's returning 1. * (2) :: in a full address is silently ignored. * credit: * inspired by Mark Andrews. * author: * Paul Vixie, 1996. */ static int inet_pton6(const char *src, u_char *dst) { static const char xdigits_l[] = "0123456789abcdef", xdigits_u[] = "0123456789ABCDEF"; #define NS_IN6ADDRSZ 16 #define NS_INT16SZ 2 u_char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp; const char *xdigits, *curtok; int ch, seen_xdigits; u_int val; memset((tp = tmp), '\0', NS_IN6ADDRSZ); endp = tp + NS_IN6ADDRSZ; colonp = NULL; /* Leading :: requires some special handling. */ if (*src == ':') if (*++src != ':') return (0); curtok = src; seen_xdigits = 0; val = 0; while ((ch = *src++) != '\0') { const char *pch; if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) pch = strchr((xdigits = xdigits_u), ch); if (pch != NULL) { val <<= 4; val |= (pch - xdigits); if (++seen_xdigits > 4) return (0); continue; } if (ch == ':') { curtok = src; if (!seen_xdigits) { if (colonp) return (0); colonp = tp; continue; } else if (*src == '\0') { return (0); } if (tp + NS_INT16SZ > endp) return (0); *tp++ = (u_char)(val >> 8) & 0xff; *tp++ = (u_char)val & 0xff; seen_xdigits = 0; val = 0; continue; } if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && inet_pton4(curtok, tp) > 0) { tp += NS_INADDRSZ; seen_xdigits = 0; break; /*%< '\\0' was seen by inet_pton4(). */ } return (0); } if (seen_xdigits) { if (tp + NS_INT16SZ > endp) return (0); *tp++ = (u_char)(val >> 8) & 0xff; *tp++ = (u_char)val & 0xff; } if (colonp != NULL) { /* * Since some memmove()'s erroneously fail to handle * overlapping regions, we'll do the shift by hand. */ const int n = tp - colonp; int i; if (tp == endp) return (0); for (i = 1; i <= n; i++) { endp[-i] = colonp[n - i]; colonp[n - i] = 0; } tp = endp; } if (tp != endp) return (0); memcpy(dst, tmp, NS_IN6ADDRSZ); return (1); } /*! \file */ ================================================ FILE: lib/utils/meson.build ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2020-2026> Intel Corporation sources = files( '_atoip.c', 'portlist.c', 'heap.c') libutils = library('utils', sources, dependencies: [common, dpdk]) utils = declare_dependency(link_with: libutils, include_directories: include_directories('.')) ================================================ FILE: lib/utils/parson_json.c ================================================ /* Parson ( http://kgabis.github.com/parson/ ) Copyright(c) 2012 - 2017 Krzysztof Gabis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef _MSC_VER #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif /* _CRT_SECURE_NO_WARNINGS */ #endif /* _MSC_VER */ #include "parson_json.h" #include #include #include #include #include #include #include #include /* Apparently sscanf is not implemented in some "standard" libraries, so don't use it, if you * don't have to. */ #define sscanf THINK_TWICE_ABOUT_USING_SSCANF #define STARTING_CAPACITY 16 #define MAX_NESTING 2048 #define FLOAT_FORMAT "%1.17g" /* do not increase precision without incresing NUM_BUF_SIZE */ #define UINT64_FORMAT "%lx" #define INTEGER_FORMAT "%d" #define NUM_BUF_SIZE \ 64 /* double printed with "%1.17g" shouldn't be longer than 25 bytes so let's be paranoid and \ use 64 */ #define SIZEOF_TOKEN(a) (sizeof(a) - 1) #define SKIP_CHAR(str) ((*str)++) #define SKIP_WHITESPACES(str) \ while (isspace((unsigned char)(**str))) { \ SKIP_CHAR(str); \ } #define MAX(a, b) ((a) > (b) ? (a) : (b)) #undef malloc #undef free #if defined(isnan) && defined(isinf) #define IS_NUMBER_INVALID(x) (isnan((x)) || isinf((x))) #else #define IS_NUMBER_INVALID(x) (((x) * 0.0) != 0.0) #endif static JSON_Malloc_Function parson_malloc = malloc; static JSON_Free_Function parson_free = free; #define IS_CONT(b) (((unsigned char)(b) & 0xC0) == 0x80) /* is utf-8 continuation byte */ /* Type definitions */ typedef union json_value_value { char *string; double number; uint64_t u64; JSON_Object *object; JSON_Array *array; int i32; int boolean; int null; } JSON_Value_Value; struct json_value_t { JSON_Value *parent; JSON_Value_Type type; JSON_Value_Value value; }; struct json_object_t { JSON_Value *wrapping_value; char **names; JSON_Value **values; size_t count; size_t capacity; }; struct json_array_t { JSON_Value *wrapping_value; JSON_Value **items; size_t count; size_t capacity; }; /* Various */ static char *read_file(const char *filename); static void remove_comments(char *string); static char *parson_strndup(const char *string, size_t n); static char *parson_strdup(const char *string); static int hex_char_to_int(char c); static int parse_utf16_hex(const char *string, unsigned int *result); static int num_bytes_in_utf8_sequence(unsigned char c); static int verify_utf8_sequence(const unsigned char *string, int *len); static int is_valid_utf8(const char *string, size_t string_len); static int is_decimal(const char *string, size_t length); /* JSON Object */ static JSON_Object *json_object_new(JSON_Value *wrapping_value); static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value); static JSON_Status json_object_addn(JSON_Object *object, const char *name, size_t name_len, JSON_Value *value); static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity); static JSON_Value *json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len); static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, int free_value); static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, int free_value); static void json_object_free(JSON_Object *object); /* JSON Array */ static JSON_Array *json_array_init(JSON_Value *wrapping_value); static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value); static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity); static void json_array_free(JSON_Array *array); /* JSON Value */ static JSON_Value *json_value_new_string_no_copy(char *string); /* Parser */ static JSON_Status skip_quotes(const char **string); static int parse_utf16(const char **unprocessed, char **processed); static char *process_string(const char *input, size_t len); static char *get_quoted_string(const char **string); static JSON_Value *parse_object_value(const char **string, size_t nesting); static JSON_Value *parse_array_value(const char **string, size_t nesting); static JSON_Value *parse_string_value(const char **string); static JSON_Value *parse_boolean_value(const char **string); static JSON_Value *parse_number_value(const char **string); static JSON_Value *parse_uint64_value(const char **string); static JSON_Value *parse_integer_value(const char **string); static JSON_Value *parse_null_value(const char **string); static JSON_Value *parse_value(const char **string, size_t nesting); /* Serialization */ static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf); static int json_serialize_string(const char *string, char *buf); static int append_indent(char *buf, int level); static int append_string(char *buf, const char *string); /* Various */ static char * parson_strndup(const char *string, size_t n) { char *output_string = (char *)parson_malloc(n + 1); if (!output_string) return NULL; output_string[n] = '\0'; rte_strlcpy(output_string, string, n); return output_string; } static char * parson_strdup(const char *string) { return parson_strndup(string, strlen(string)); } static int hex_char_to_int(char c) { if (c >= '0' && c <= '9') return c - '0'; else if (c >= 'a' && c <= 'f') return c - 'a' + 10; else if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -1; } static int parse_utf16_hex(const char *s, unsigned int *result) { int x1, x2, x3, x4; if (s[0] == '\0' || s[1] == '\0' || s[2] == '\0' || s[3] == '\0') return 0; x1 = hex_char_to_int(s[0]); x2 = hex_char_to_int(s[1]); x3 = hex_char_to_int(s[2]); x4 = hex_char_to_int(s[3]); if (x1 == -1 || x2 == -1 || x3 == -1 || x4 == -1) return 0; *result = (unsigned int)((x1 << 12) | (x2 << 8) | (x3 << 4) | x4); return 1; } static int num_bytes_in_utf8_sequence(unsigned char c) { if (c == 0xC0 || c == 0xC1 || c > 0xF4 || IS_CONT(c)) return 0; else if ((c & 0x80) == 0) /* 0xxxxxxx */ return 1; else if ((c & 0xE0) == 0xC0) /* 110xxxxx */ return 2; else if ((c & 0xF0) == 0xE0) /* 1110xxxx */ return 3; else if ((c & 0xF8) == 0xF0) /* 11110xxx */ return 4; return 0; /* won't happen */ } static int verify_utf8_sequence(const unsigned char *string, int *len) { unsigned int cp = 0; *len = num_bytes_in_utf8_sequence(string[0]); if (*len == 1) cp = string[0]; else if (*len == 2 && IS_CONT(string[1])) { cp = string[0] & 0x1F; cp = (cp << 6) | (string[1] & 0x3F); } else if (*len == 3 && IS_CONT(string[1]) && IS_CONT(string[2])) { cp = ((unsigned char)string[0]) & 0xF; cp = (cp << 6) | (string[1] & 0x3F); cp = (cp << 6) | (string[2] & 0x3F); } else if (*len == 4 && IS_CONT(string[1]) && IS_CONT(string[2]) && IS_CONT(string[3])) { cp = string[0] & 0x7; cp = (cp << 6) | (string[1] & 0x3F); cp = (cp << 6) | (string[2] & 0x3F); cp = (cp << 6) | (string[3] & 0x3F); } else return 0; /* overlong encodings */ if ((cp < 0x80 && *len > 1) || (cp < 0x800 && *len > 2) || (cp < 0x10000 && *len > 3)) return 0; /* invalid unicode */ if (cp > 0x10FFFF) return 0; /* surrogate halves */ if (cp >= 0xD800 && cp <= 0xDFFF) return 0; return 1; } static int is_valid_utf8(const char *string, size_t string_len) { int len = 0; const char *string_end = string + string_len; while (string < string_end) { if (!verify_utf8_sequence((const unsigned char *)string, &len)) return 0; string += len; } return 1; } static int is_decimal(const char *string, size_t length) { if (length > 1 && string[0] == '0' && string[1] != '.') return 0; if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.') return 0; while (length--) if (strchr("xX", string[length])) return 0; return 1; } static char * read_file(const char *filename) { FILE *fp = fopen(filename, "r"); size_t size_to_read = 0; size_t size_read = 0; long pos; char *file_contents; if (!fp) return NULL; fseek(fp, 0L, SEEK_END); pos = ftell(fp); if (pos < 0) { fclose(fp); return NULL; } size_to_read = pos; rewind(fp); file_contents = (char *)parson_malloc(sizeof(char) * (size_to_read + 1)); if (!file_contents) { fclose(fp); return NULL; } size_read = fread(file_contents, 1, size_to_read, fp); if (size_read == 0 || ferror(fp)) { fclose(fp); parson_free(file_contents); return NULL; } fclose(fp); file_contents[size_read] = '\0'; return file_contents; } static void remove_comments(char *string) { int in_string = 0, escaped = 0; char *ptr, current_char; while ((current_char = *string) != '\0') { if (current_char == '\\' && !escaped) { escaped = 1; string++; continue; } else if (current_char == '\"' && !escaped) in_string = !in_string; else if (!in_string) { if (strncmp(string, "/*", 2) == 0) { ptr = strstr(string + 2, "*/"); if (!ptr) return; ptr += 2; while (string != ptr) *string++ = ' '; } else if (strncmp(string, "//", 2) == 0) while (*string != '\n') *string++ = ' '; } escaped = 0; string++; } } /* JSON Object */ static JSON_Object * json_object_new(JSON_Value *wrapping_value) { JSON_Object *new_obj = (JSON_Object *)parson_malloc(sizeof(JSON_Object)); if (new_obj == NULL) return NULL; new_obj->wrapping_value = wrapping_value; new_obj->names = (char **)NULL; new_obj->values = (JSON_Value **)NULL; new_obj->capacity = 0; new_obj->count = 0; return new_obj; } static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value) { if (name == NULL) return JSONFailure; return json_object_addn(object, name, strlen(name), value); } static JSON_Status json_object_addn(JSON_Object *object, const char *name, size_t name_len, JSON_Value *value) { size_t index = 0; if (object == NULL || name == NULL || value == NULL) return JSONFailure; if (json_object_getn_value(object, name, name_len) != NULL) return JSONFailure; if (object->count >= object->capacity) { size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY); if (json_object_resize(object, new_capacity) == JSONFailure) return JSONFailure; } index = object->count; object->names[index] = parson_strndup(name, name_len); if (object->names[index] == NULL) return JSONFailure; value->parent = json_object_get_wrapping_value(object); object->values[index] = value; object->count++; return JSONSuccess; } static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity) { char **temp_names = NULL; JSON_Value **temp_values = NULL; if ((object->names == NULL && object->values != NULL) || (object->names != NULL && object->values == NULL) || new_capacity == 0) return JSONFailure; /* Shouldn't happen */ temp_names = (char **)parson_malloc(new_capacity * sizeof(char *)); if (temp_names == NULL) return JSONFailure; temp_values = (JSON_Value **)parson_malloc(new_capacity * sizeof(JSON_Value *)); if (temp_values == NULL) { parson_free(temp_names); return JSONFailure; } if (object->names != NULL && object->values != NULL && object->count > 0) { memcpy(temp_names, object->names, object->count * sizeof(char *)); memcpy(temp_values, object->values, object->count * sizeof(JSON_Value *)); } parson_free(object->names); parson_free(object->values); object->names = temp_names; object->values = temp_values; object->capacity = new_capacity; return JSONSuccess; } static JSON_Value * json_object_getn_value(const JSON_Object *object, const char *name, size_t name_len) { size_t i, name_length; for (i = 0; i < json_object_get_count(object); i++) { name_length = strlen(object->names[i]); if (name_length != name_len) continue; if (strncmp(object->names[i], name, name_len) == 0) return object->values[i]; } return NULL; } static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, int free_value) { size_t i = 0, last_item_index = 0; if (object == NULL || json_object_get_value(object, name) == NULL) return JSONFailure; last_item_index = json_object_get_count(object) - 1; for (i = 0; i < json_object_get_count(object); i++) if (strcmp(object->names[i], name) == 0) { parson_free(object->names[i]); if (free_value) json_value_free(object->values[i]); if (i != last_item_index) { /* Replace key value pair with one from the end */ object->names[i] = object->names[last_item_index]; object->values[i] = object->values[last_item_index]; } object->count -= 1; return JSONSuccess; } return JSONFailure; /* No execution path should end here */ } static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, int free_value) { JSON_Value *temp_value = NULL; JSON_Object *temp_object = NULL; const char *dot_pos = strchr(name, '.'); if (dot_pos == NULL) return json_object_remove_internal(object, name, free_value); temp_value = json_object_getn_value(object, name, dot_pos - name); if (json_value_get_type(temp_value) != JSONObject) return JSONFailure; temp_object = json_value_get_object(temp_value); return json_object_dotremove_internal(temp_object, dot_pos + 1, free_value); } static void json_object_free(JSON_Object *object) { size_t i; for (i = 0; i < object->count; i++) { parson_free(object->names[i]); json_value_free(object->values[i]); } parson_free(object->names); parson_free(object->values); parson_free(object); } /* JSON Array */ static JSON_Array * json_array_init(JSON_Value *wrapping_value) { JSON_Array *new_array = (JSON_Array *)parson_malloc(sizeof(JSON_Array)); if (new_array == NULL) return NULL; new_array->wrapping_value = wrapping_value; new_array->items = (JSON_Value **)NULL; new_array->capacity = 0; new_array->count = 0; return new_array; } static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value) { if (array->count >= array->capacity) { size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY); if (json_array_resize(array, new_capacity) == JSONFailure) return JSONFailure; } value->parent = json_array_get_wrapping_value(array); array->items[array->count] = value; array->count++; return JSONSuccess; } static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity) { JSON_Value **new_items = NULL; if (new_capacity == 0) return JSONFailure; new_items = (JSON_Value **)parson_malloc(new_capacity * sizeof(JSON_Value *)); if (new_items == NULL) return JSONFailure; if (array->items != NULL && array->count > 0) memcpy(new_items, array->items, array->count * sizeof(JSON_Value *)); parson_free(array->items); array->items = new_items; array->capacity = new_capacity; return JSONSuccess; } static void json_array_free(JSON_Array *array) { size_t i; for (i = 0; i < array->count; i++) json_value_free(array->items[i]); parson_free(array->items); parson_free(array); } /* JSON Value */ static JSON_Value * json_value_new_string_no_copy(char *string) { JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); if (!new_value) return NULL; new_value->parent = NULL; new_value->type = JSONString; new_value->value.string = string; return new_value; } /* Parser */ static JSON_Status skip_quotes(const char **string) { if (**string != '\"') return JSONFailure; SKIP_CHAR(string); while (**string != '\"') { if (**string == '\0') return JSONFailure; else if (**string == '\\') { SKIP_CHAR(string); if (**string == '\0') return JSONFailure; } SKIP_CHAR(string); } SKIP_CHAR(string); return JSONSuccess; } static int parse_utf16(const char **unprocessed, char **processed) { unsigned int cp, lead, trail; int parse_succeeded = 0; char *processed_ptr = *processed; const char *unprocessed_ptr = *unprocessed; unprocessed_ptr++; /* skips u */ parse_succeeded = parse_utf16_hex(unprocessed_ptr, &cp); if (!parse_succeeded) return JSONFailure; if (cp < 0x80) processed_ptr[0] = (char)cp; /* 0xxxxxxx */ else if (cp < 0x800) { processed_ptr[0] = ((cp >> 6) & 0x1F) | 0xC0; /* 110xxxxx */ processed_ptr[1] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */ processed_ptr += 1; } else if (cp < 0xD800 || cp > 0xDFFF) { processed_ptr[0] = ((cp >> 12) & 0x0F) | 0xE0; /* 1110xxxx */ processed_ptr[1] = ((cp >> 6) & 0x3F) | 0x80; /* 10xxxxxx */ processed_ptr[2] = ((cp) & 0x3F) | 0x80; /* 10xxxxxx */ processed_ptr += 2; } else if (cp >= 0xD800 && cp <= 0xDBFF) { /* lead surrogate (0xD800..0xDBFF) */ lead = cp; unprocessed_ptr += 4; /* should always be within the buffer, otherwise previous sscanf would fail */ if (*unprocessed_ptr++ != '\\' || *unprocessed_ptr++ != 'u') return JSONFailure; parse_succeeded = parse_utf16_hex(unprocessed_ptr, &trail); if (!parse_succeeded || trail < 0xDC00 || trail > 0xDFFF) /* valid trail surrogate? (0xDC00..0xDFFF) */ return JSONFailure; cp = ((((lead - 0xD800) & 0x3FF) << 10) | ((trail - 0xDC00) & 0x3FF)) + 0x010000; processed_ptr[0] = (((cp >> 18) & 0x07) | 0xF0); /* 11110xxx */ processed_ptr[1] = (((cp >> 12) & 0x3F) | 0x80); /* 10xxxxxx */ processed_ptr[2] = (((cp >> 6) & 0x3F) | 0x80); /* 10xxxxxx */ processed_ptr[3] = (((cp) & 0x3F) | 0x80); /* 10xxxxxx */ processed_ptr += 3; } else /* trail surrogate before lead surrogate */ return JSONFailure; unprocessed_ptr += 3; *processed = processed_ptr; *unprocessed = unprocessed_ptr; return JSONSuccess; } /* Copies and processes passed string up to supplied length. Example: "\u006Corem ipsum" -> lorem ipsum */ static char * process_string(const char *input, size_t len) { const char *input_ptr = input; size_t initial_size = (len + 1) * sizeof(char); size_t final_size = 0; char *output = NULL, *output_ptr = NULL, *resized_output = NULL; output = (char *)parson_malloc(initial_size); if (output == NULL) goto error; output_ptr = output; while ((*input_ptr != '\0') && (size_t)(input_ptr - input) < len) { if (*input_ptr == '\\') { input_ptr++; switch (*input_ptr) { case '\"': *output_ptr = '\"'; break; case '\\': *output_ptr = '\\'; break; case '/': *output_ptr = '/'; break; case 'b': *output_ptr = '\b'; break; case 'f': *output_ptr = '\f'; break; case 'n': *output_ptr = '\n'; break; case 'r': *output_ptr = '\r'; break; case 't': *output_ptr = '\t'; break; case 'u': if (parse_utf16(&input_ptr, &output_ptr) == JSONFailure) goto error; break; default: goto error; } } else if ((unsigned char)*input_ptr < 0x20) goto error; /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */ else *output_ptr = *input_ptr; output_ptr++; input_ptr++; } *output_ptr = '\0'; /* resize to new length */ final_size = (size_t)(output_ptr - output) + 1; /* todo: don't resize if final_size == initial_size */ resized_output = (char *)parson_malloc(final_size); if (resized_output == NULL) goto error; memcpy(resized_output, output, final_size); parson_free(output); return resized_output; error: parson_free(output); return NULL; } /* Return processed contents of a string between quotes and skips passed argument to a matching quote. */ static char * get_quoted_string(const char **string) { const char *string_start = *string; size_t string_len = 0; JSON_Status status = skip_quotes(string); if (status != JSONSuccess) return NULL; string_len = *string - string_start - 2; /* length without quotes */ return process_string(string_start + 1, string_len); } static JSON_Value * parse_value(const char **string, size_t nesting) { if (nesting > MAX_NESTING) return NULL; SKIP_WHITESPACES(string); switch (**string) { case '{': return parse_object_value(string, nesting + 1); case '[': return parse_array_value(string, nesting + 1); case '\"': return parse_string_value(string); case 'f': case 't': case 'F': case 'T': return parse_boolean_value(string); case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (**string == '0' && *(*string + 1) == 'x') return parse_uint64_value(string); else if (strchr(*string, '.') == NULL) return parse_integer_value(string); return parse_number_value(string); case 'n': return parse_null_value(string); default: return NULL; } } static JSON_Value * parse_object_value(const char **string, size_t nesting) { JSON_Value *output_value = NULL, *new_value = NULL; JSON_Object *output_object = NULL; char *new_key = NULL; output_value = json_value_new_object(); if (output_value == NULL) return NULL; if (**string != '{') { json_value_free(output_value); return NULL; } output_object = json_value_get_object(output_value); SKIP_CHAR(string); SKIP_WHITESPACES(string); if (**string == '}') { /* empty object */ SKIP_CHAR(string); return output_value; } while (**string != '\0') { new_key = get_quoted_string(string); if (new_key == NULL) { json_value_free(output_value); return NULL; } SKIP_WHITESPACES(string); if (**string != ':') { parson_free(new_key); json_value_free(output_value); return NULL; } SKIP_CHAR(string); new_value = parse_value(string, nesting); if (new_value == NULL) { parson_free(new_key); json_value_free(output_value); return NULL; } if (json_object_add(output_object, new_key, new_value) == JSONFailure) { parson_free(new_key); json_value_free(new_value); json_value_free(output_value); return NULL; } parson_free(new_key); SKIP_WHITESPACES(string); if (**string != ',') break; SKIP_CHAR(string); SKIP_WHITESPACES(string); } SKIP_WHITESPACES(string); if (**string != '}' || /* Trim object after parsing is over */ json_object_resize(output_object, json_object_get_count(output_object)) == JSONFailure) { json_value_free(output_value); return NULL; } SKIP_CHAR(string); return output_value; } static JSON_Value * parse_array_value(const char **string, size_t nesting) { JSON_Value *output_value = NULL, *new_array_value = NULL; JSON_Array *output_array = NULL; output_value = json_value_new_array(); if (output_value == NULL) return NULL; if (**string != '[') { json_value_free(output_value); return NULL; } output_array = json_value_get_array(output_value); SKIP_CHAR(string); SKIP_WHITESPACES(string); if (**string == ']') { /* empty array */ SKIP_CHAR(string); return output_value; } while (**string != '\0') { new_array_value = parse_value(string, nesting); if (new_array_value == NULL) { json_value_free(output_value); return NULL; } if (json_array_add(output_array, new_array_value) == JSONFailure) { json_value_free(new_array_value); json_value_free(output_value); return NULL; } SKIP_WHITESPACES(string); if (**string != ',') break; SKIP_CHAR(string); SKIP_WHITESPACES(string); } SKIP_WHITESPACES(string); if (**string != ']' || /* Trim array after parsing is over */ json_array_resize(output_array, json_array_get_count(output_array)) == JSONFailure) { json_value_free(output_value); return NULL; } SKIP_CHAR(string); return output_value; } static JSON_Value * parse_string_value(const char **string) { JSON_Value *value = NULL; char *new_string = get_quoted_string(string); if (new_string == NULL) return NULL; value = json_value_new_string_no_copy(new_string); if (value == NULL) { parson_free(new_string); return NULL; } return value; } static JSON_Value * parse_boolean_value(const char **string) { size_t true_token_size = SIZEOF_TOKEN("true"); size_t false_token_size = SIZEOF_TOKEN("false"); if (strncasecmp("true", *string, true_token_size) == 0) { *string += true_token_size; return json_value_new_boolean(1); } else if (strncasecmp("false", *string, false_token_size) == 0) { *string += false_token_size; return json_value_new_boolean(0); } return NULL; } static JSON_Value * parse_number_value(const char **string) { char *end; double number = 0; errno = 0; number = strtod(*string, &end); if (errno || !is_decimal(*string, end - *string)) return NULL; *string = end; return json_value_new_number(number); } static JSON_Value * parse_uint64_value(const char **string) { char *end; uint64_t number = 0; errno = 0; number = strtoul(*string, &end, 0); if (errno) return NULL; *string = end; return json_value_new_uint64(number); } static JSON_Value * parse_integer_value(const char **string) { char *end; int number = 0; errno = 0; number = strtol(*string, &end, 0); if (errno) return NULL; *string = end; return json_value_new_integer(number); } static JSON_Value * parse_null_value(const char **string) { size_t token_size = SIZEOF_TOKEN("null"); if (strncmp("null", *string, token_size) == 0) { *string += token_size; return json_value_new_null(); } return NULL; } /* Serialization */ #define APPEND_STRING(str) \ do { \ written = append_string(buf, (str)); \ if (written < 0) { \ return -1; \ } \ if (buf != NULL) { \ buf += written; \ } \ written_total += written; \ } while (0) #define APPEND_INDENT(level) \ do { \ written = append_indent(buf, (level)); \ if (written < 0) { \ return -1; \ } \ if (buf != NULL) { \ buf += written; \ } \ written_total += written; \ } while (0) static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf) { const char *key = NULL, *string = NULL; JSON_Value *temp_value = NULL; JSON_Array *array = NULL; JSON_Object *object = NULL; size_t i = 0, count = 0; double num = 0.0; uint64_t u64; int i32; int written = -1, written_total = 0; switch (json_value_get_type(value)) { case JSONArray: array = json_value_get_array(value); count = json_array_get_count(array); APPEND_STRING("["); if (count > 0 && is_pretty) APPEND_STRING("\n"); for (i = 0; i < count; i++) { if (is_pretty) APPEND_INDENT(level + 1); temp_value = json_array_get_value(array, i); written = json_serialize_to_buffer_r(temp_value, buf, level + 1, is_pretty, num_buf); if (written < 0) return -1; if (buf != NULL) buf += written; written_total += written; if (i < (count - 1)) APPEND_STRING(","); if (is_pretty) APPEND_STRING("\n"); } if (count > 0 && is_pretty) APPEND_INDENT(level); APPEND_STRING("]"); return written_total; case JSONObject: object = json_value_get_object(value); count = json_object_get_count(object); APPEND_STRING("{"); if (count > 0 && is_pretty) APPEND_STRING("\n"); for (i = 0; i < count; i++) { key = json_object_get_name(object, i); if (key == NULL) return -1; if (is_pretty) APPEND_INDENT(level + 1); written = json_serialize_string(key, buf); if (written < 0) return -1; if (buf != NULL) buf += written; written_total += written; APPEND_STRING(":"); if (is_pretty) APPEND_STRING(" "); temp_value = json_object_get_value(object, key); written = json_serialize_to_buffer_r(temp_value, buf, level + 1, is_pretty, num_buf); if (written < 0) return -1; if (buf != NULL) buf += written; written_total += written; if (i < (count - 1)) APPEND_STRING(","); if (is_pretty) APPEND_STRING("\n"); } if (count > 0 && is_pretty) APPEND_INDENT(level); APPEND_STRING("}"); return written_total; case JSONString: string = json_value_get_string(value); if (string == NULL) return -1; written = json_serialize_string(string, buf); if (written < 0) return -1; if (buf != NULL) buf += written; written_total += written; return written_total; case JSONBoolean: if (json_value_get_boolean(value)) APPEND_STRING("true"); else APPEND_STRING("false"); return written_total; case JSONNumber: num = json_value_get_number(value); if (buf != NULL) num_buf = buf; written = sprintf(num_buf, FLOAT_FORMAT, num); if (written < 0) return -1; if (buf != NULL) buf += written; written_total += written; return written_total; case JSONUint64: u64 = json_value_get_uint64(value); if (buf != NULL) num_buf = buf; written = sprintf(num_buf, UINT64_FORMAT, u64); if (written < 0) return -1; if (buf != NULL) buf += written; written_total += written; return written_total; case JSONInteger: i32 = json_value_get_integer(value); if (buf != NULL) num_buf = buf; written = sprintf(num_buf, INTEGER_FORMAT, i32); if (written < 0) return -1; if (buf != NULL) buf += written; written_total += written; return written_total; case JSONNull: APPEND_STRING("null"); return written_total; case JSONError: return -1; default: return -1; } } static int json_serialize_string(const char *string, char *buf) { size_t i = 0, len = strlen(string); char c = '\0'; int written = -1, written_total = 0; APPEND_STRING("\""); for (i = 0; i < len; i++) { c = string[i]; switch (c) { case '\"': APPEND_STRING("\\\""); break; case '\\': APPEND_STRING("\\\\"); break; case '/': APPEND_STRING("\\/"); break; /* to make json embeddable in xml\/html */ case '\b': APPEND_STRING("\\b"); break; case '\f': APPEND_STRING("\\f"); break; case '\n': APPEND_STRING("\\n"); break; case '\r': APPEND_STRING("\\r"); break; case '\t': APPEND_STRING("\\t"); break; case '\x00': APPEND_STRING("\\u0000"); break; case '\x01': APPEND_STRING("\\u0001"); break; case '\x02': APPEND_STRING("\\u0002"); break; case '\x03': APPEND_STRING("\\u0003"); break; case '\x04': APPEND_STRING("\\u0004"); break; case '\x05': APPEND_STRING("\\u0005"); break; case '\x06': APPEND_STRING("\\u0006"); break; case '\x07': APPEND_STRING("\\u0007"); break; /* '\x08' duplicate: '\b' */ /* '\x09' duplicate: '\t' */ /* '\x0a' duplicate: '\n' */ case '\x0b': APPEND_STRING("\\u000b"); break; /* '\x0c' duplicate: '\f' */ /* '\x0d' duplicate: '\r' */ case '\x0e': APPEND_STRING("\\u000e"); break; case '\x0f': APPEND_STRING("\\u000f"); break; case '\x10': APPEND_STRING("\\u0010"); break; case '\x11': APPEND_STRING("\\u0011"); break; case '\x12': APPEND_STRING("\\u0012"); break; case '\x13': APPEND_STRING("\\u0013"); break; case '\x14': APPEND_STRING("\\u0014"); break; case '\x15': APPEND_STRING("\\u0015"); break; case '\x16': APPEND_STRING("\\u0016"); break; case '\x17': APPEND_STRING("\\u0017"); break; case '\x18': APPEND_STRING("\\u0018"); break; case '\x19': APPEND_STRING("\\u0019"); break; case '\x1a': APPEND_STRING("\\u001a"); break; case '\x1b': APPEND_STRING("\\u001b"); break; case '\x1c': APPEND_STRING("\\u001c"); break; case '\x1d': APPEND_STRING("\\u001d"); break; case '\x1e': APPEND_STRING("\\u001e"); break; case '\x1f': APPEND_STRING("\\u001f"); break; default: if (buf != NULL) { buf[0] = c; buf += 1; } written_total += 1; break; } } APPEND_STRING("\""); return written_total; } static int append_indent(char *buf, int level) { int i; int written = -1, written_total = 0; for (i = 0; i < level; i++) APPEND_STRING(" "); return written_total; } static int append_string(char *buf, const char *string) { if (buf == NULL) return (int)strlen(string); return sprintf(buf, "%s", string); } #undef APPEND_STRING #undef APPEND_INDENT /* Parser API */ JSON_Value * json_parse_file(const char *filename) { char *file_contents = read_file(filename); JSON_Value *output_value = NULL; if (file_contents == NULL) return NULL; output_value = json_parse_string(file_contents); parson_free(file_contents); return output_value; } JSON_Value * json_parse_file_with_comments(const char *filename) { char *file_contents = read_file(filename); JSON_Value *output_value = NULL; if (file_contents == NULL) return NULL; output_value = json_parse_string_with_comments(file_contents); parson_free(file_contents); return output_value; } JSON_Value * json_parse_string(const char *string) { if (string == NULL) return NULL; if (string[0] == '\xEF' && string[1] == '\xBB' && string[2] == '\xBF') string = string + 3; /* Support for UTF-8 BOM */ return parse_value((const char **)&string, 0); } JSON_Value * json_parse_string_with_comments(const char *string) { JSON_Value *result = NULL; char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL; string_mutable_copy = parson_strdup(string); if (string_mutable_copy == NULL) return NULL; remove_comments(string_mutable_copy); string_mutable_copy_ptr = string_mutable_copy; result = parse_value((const char **)(uintptr_t)&string_mutable_copy_ptr, 0); parson_free(string_mutable_copy); return result; } /* JSON Object API */ JSON_Value * json_object_get_value(const JSON_Object *object, const char *name) { if (object == NULL || name == NULL) return NULL; return json_object_getn_value(object, name, strlen(name)); } const char * json_object_get_string(const JSON_Object *object, const char *name) { return json_value_get_string(json_object_get_value(object, name)); } double json_object_get_number(const JSON_Object *object, const char *name) { return json_value_get_number(json_object_get_value(object, name)); } uint64_t json_object_get_uint64(const JSON_Object *object, const char *name) { return json_value_get_uint64(json_object_get_value(object, name)); } int json_object_get_integer(const JSON_Object *object, const char *name) { return json_value_get_integer(json_object_get_value(object, name)); } JSON_Object * json_object_get_object(const JSON_Object *object, const char *name) { return json_value_get_object(json_object_get_value(object, name)); } JSON_Array * json_object_get_array(const JSON_Object *object, const char *name) { return json_value_get_array(json_object_get_value(object, name)); } int json_object_get_boolean(const JSON_Object *object, const char *name) { return json_value_get_boolean(json_object_get_value(object, name)); } /**************************************/ const char * json_object_at_get_string(const JSON_Object *object, size_t idx) { return json_value_get_string(json_object_get_value_at(object, idx)); } double json_object_at_get_number(const JSON_Object *object, size_t idx) { return json_value_get_number(json_object_get_value_at(object, idx)); } uint64_t json_object_at_get_uint64(const JSON_Object *object, size_t idx) { return json_value_get_uint64(json_object_get_value_at(object, idx)); } int json_object_at_get_integer(const JSON_Object *object, size_t idx) { return json_value_get_integer(json_object_get_value_at(object, idx)); } JSON_Object * json_object_at_get_object(const JSON_Object *object, size_t idx) { return json_value_get_object(json_object_get_value_at(object, idx)); } JSON_Array * json_object_at_get_array(const JSON_Object *object, size_t idx) { return json_value_get_array(json_object_get_value_at(object, idx)); } int json_object_at_get_boolean(const JSON_Object *object, size_t idx) { return json_value_get_boolean(json_object_get_value_at(object, idx)); } JSON_Value * json_object_dotget_value(const JSON_Object *object, const char *name) { const char *dot_position = strchr(name, '.'); if (!dot_position) return json_object_get_value(object, name); object = json_value_get_object(json_object_getn_value(object, name, dot_position - name)); return json_object_dotget_value(object, dot_position + 1); } const char * json_object_dotget_string(const JSON_Object *object, const char *name) { return json_value_get_string(json_object_dotget_value(object, name)); } double json_object_dotget_number(const JSON_Object *object, const char *name) { return json_value_get_number(json_object_dotget_value(object, name)); } uint64_t json_object_dotget_uint64(const JSON_Object *object, const char *name) { return json_value_get_uint64(json_object_dotget_value(object, name)); } int json_object_dotget_integer(const JSON_Object *object, const char *name) { return json_value_get_integer(json_object_dotget_value(object, name)); } JSON_Object * json_object_dotget_object(const JSON_Object *object, const char *name) { return json_value_get_object(json_object_dotget_value(object, name)); } JSON_Array * json_object_dotget_array(const JSON_Object *object, const char *name) { return json_value_get_array(json_object_dotget_value(object, name)); } int json_object_dotget_boolean(const JSON_Object *object, const char *name) { return json_value_get_boolean(json_object_dotget_value(object, name)); } int json_object_get_boolean_by_name(JSON_Object *obj, const char *name) { int ret = 0; if (obj && name) ret = json_object_dotget_boolean(obj, name); return (ret >= 0) ? ret : 0; } size_t json_object_get_count(const JSON_Object *object) { return object ? object->count : 0; } const char * json_object_get_name(const JSON_Object *object, size_t index) { if (object == NULL || index >= json_object_get_count(object)) return NULL; return object->names[index]; } JSON_Value * json_object_get_value_at(const JSON_Object *object, size_t index) { if (object == NULL || index >= json_object_get_count(object)) return NULL; return object->values[index]; } JSON_Value * json_object_get_wrapping_value(const JSON_Object *object) { return object->wrapping_value; } int json_object_has_value(const JSON_Object *object, const char *name) { return json_object_get_value(object, name) != NULL; } int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) { JSON_Value *val = json_object_get_value(object, name); return val != NULL && json_value_get_type(val) == type; } int json_object_dothas_value(const JSON_Object *object, const char *name) { return json_object_dotget_value(object, name) != NULL; } int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) { JSON_Value *val = json_object_dotget_value(object, name); return val != NULL && json_value_get_type(val) == type; } /* JSON Array API */ JSON_Value * json_array_get_value(const JSON_Array *array, size_t index) { if (array == NULL || index >= json_array_get_count(array)) return NULL; return array->items[index]; } const char * json_array_get_string(const JSON_Array *array, size_t index) { return json_value_get_string(json_array_get_value(array, index)); } double json_array_get_number(const JSON_Array *array, size_t index) { return json_value_get_number(json_array_get_value(array, index)); } uint64_t json_array_get_uint64(const JSON_Array *array, size_t index) { return json_value_get_uint64(json_array_get_value(array, index)); } int json_array_get_integer(const JSON_Array *array, size_t index) { return json_value_get_integer(json_array_get_value(array, index)); } JSON_Object * json_array_get_object(const JSON_Array *array, size_t index) { return json_value_get_object(json_array_get_value(array, index)); } JSON_Array * json_array_get_array(const JSON_Array *array, size_t index) { return json_value_get_array(json_array_get_value(array, index)); } int json_array_get_boolean(const JSON_Array *array, size_t index) { return json_value_get_boolean(json_array_get_value(array, index)); } size_t json_array_get_count(const JSON_Array *array) { return array ? array->count : 0; } JSON_Value * json_array_get_wrapping_value(const JSON_Array *array) { return array->wrapping_value; } /* JSON Value API */ JSON_Value_Type json_value_get_type(const JSON_Value *value) { return value ? value->type : JSONError; } JSON_Object * json_value_get_object(const JSON_Value *value) { return json_value_get_type(value) == JSONObject ? value->value.object : NULL; } JSON_Array * json_value_get_array(const JSON_Value *value) { return json_value_get_type(value) == JSONArray ? value->value.array : NULL; } const char * json_value_get_string(const JSON_Value *value) { return json_value_get_type(value) == JSONString ? value->value.string : NULL; } double json_value_get_number(const JSON_Value *value) { return json_value_get_type(value) == JSONNumber ? value->value.number : 0; } uint64_t json_value_get_uint64(const JSON_Value *value) { return json_value_get_type(value) == JSONUint64 ? value->value.u64 : 0; } int json_value_get_integer(const JSON_Value *value) { return json_value_get_type(value) == JSONInteger ? value->value.i32 : 0; } int json_value_get_boolean(const JSON_Value *value) { return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1; } JSON_Value * json_value_get_parent(const JSON_Value *value) { return value ? value->parent : NULL; } void json_value_free(JSON_Value *value) { if (!value) return; switch (json_value_get_type(value)) { case JSONObject: json_object_free(value->value.object); break; case JSONString: parson_free(value->value.string); break; case JSONArray: json_array_free(value->value.array); break; default: break; } parson_free(value); } JSON_Value * json_value_new_object(void) { JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); if (!new_value) return NULL; new_value->parent = NULL; new_value->type = JSONObject; new_value->value.object = json_object_new(new_value); if (!new_value->value.object) { parson_free(new_value); return NULL; } return new_value; } JSON_Value * json_value_new_array(void) { JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); if (!new_value) return NULL; new_value->parent = NULL; new_value->type = JSONArray; new_value->value.array = json_array_init(new_value); if (!new_value->value.array) { parson_free(new_value); return NULL; } return new_value; } JSON_Value * json_value_new_string(const char *string) { char *copy = NULL; JSON_Value *value; size_t string_len = 0; if (string == NULL) return NULL; string_len = strlen(string); if (!is_valid_utf8(string, string_len)) return NULL; copy = parson_strndup(string, string_len); if (copy == NULL) return NULL; value = json_value_new_string_no_copy(copy); if (value == NULL) parson_free(copy); return value; } JSON_Value * json_value_new_number(double number) { JSON_Value *new_value = NULL; if (IS_NUMBER_INVALID(number)) return NULL; new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); if (new_value == NULL) return NULL; new_value->parent = NULL; new_value->type = JSONNumber; new_value->value.number = number; return new_value; } JSON_Value * json_value_new_uint64(uint64_t number) { JSON_Value *new_value = NULL; new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); if (new_value == NULL) return NULL; new_value->parent = NULL; new_value->type = JSONUint64; new_value->value.u64 = number; return new_value; } JSON_Value * json_value_new_integer(int number) { JSON_Value *new_value = NULL; new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); if (new_value == NULL) return NULL; new_value->parent = NULL; new_value->type = JSONInteger; new_value->value.i32 = number; return new_value; } JSON_Value * json_value_new_boolean(int boolean) { JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); if (!new_value) return NULL; new_value->parent = NULL; new_value->type = JSONBoolean; new_value->value.boolean = boolean ? 1 : 0; return new_value; } JSON_Value * json_value_new_null(void) { JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); if (!new_value) return NULL; new_value->parent = NULL; new_value->type = JSONNull; return new_value; } JSON_Value * json_value_deep_copy(const JSON_Value *value) { size_t i = 0; JSON_Value *return_value = NULL, *temp_value_copy = NULL, *temp_value = NULL; const char *temp_string = NULL, *temp_key = NULL; char *temp_string_copy = NULL; JSON_Array *temp_array = NULL, *temp_array_copy = NULL; JSON_Object *temp_object = NULL, *temp_object_copy = NULL; switch (json_value_get_type(value)) { case JSONArray: temp_array = json_value_get_array(value); return_value = json_value_new_array(); if (return_value == NULL) return NULL; temp_array_copy = json_value_get_array(return_value); for (i = 0; i < json_array_get_count(temp_array); i++) { temp_value = json_array_get_value(temp_array, i); temp_value_copy = json_value_deep_copy(temp_value); if (temp_value_copy == NULL) { json_value_free(return_value); return NULL; } if (json_array_add(temp_array_copy, temp_value_copy) == JSONFailure) { json_value_free(return_value); json_value_free(temp_value_copy); return NULL; } } return return_value; case JSONObject: temp_object = json_value_get_object(value); return_value = json_value_new_object(); if (return_value == NULL) return NULL; temp_object_copy = json_value_get_object(return_value); for (i = 0; i < json_object_get_count(temp_object); i++) { temp_key = json_object_get_name(temp_object, i); temp_value = json_object_get_value(temp_object, temp_key); temp_value_copy = json_value_deep_copy(temp_value); if (temp_value_copy == NULL) { json_value_free(return_value); return NULL; } if (json_object_add(temp_object_copy, temp_key, temp_value_copy) == JSONFailure) { json_value_free(return_value); json_value_free(temp_value_copy); return NULL; } } return return_value; case JSONBoolean: return json_value_new_boolean(json_value_get_boolean(value)); case JSONNumber: return json_value_new_number(json_value_get_number(value)); case JSONUint64: return json_value_new_uint64(json_value_get_uint64(value)); case JSONInteger: return json_value_new_integer(json_value_get_integer(value)); case JSONString: temp_string = json_value_get_string(value); if (temp_string == NULL) return NULL; temp_string_copy = parson_strdup(temp_string); if (temp_string_copy == NULL) return NULL; return_value = json_value_new_string_no_copy(temp_string_copy); if (return_value == NULL) parson_free(temp_string_copy); return return_value; case JSONNull: return json_value_new_null(); case JSONError: return NULL; default: return NULL; } } size_t json_serialization_size(const JSON_Value *value) { char num_buf[NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ int res = json_serialize_to_buffer_r(value, NULL, 0, 0, num_buf); return res < 0 ? 0 : (size_t)(res + 1); } JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { int written = -1; size_t needed_size_in_bytes = json_serialization_size(value); if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) return JSONFailure; written = json_serialize_to_buffer_r(value, buf, 0, 0, NULL); if (written < 0) return JSONFailure; return JSONSuccess; } JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename) { JSON_Status return_code = JSONSuccess; FILE *fp = NULL; char *serialized_string = json_serialize_to_string(value); if (serialized_string == NULL) return JSONFailure; fp = fopen(filename, "w"); if (fp == NULL) { json_free_serialized_string(serialized_string); return JSONFailure; } if (fputs(serialized_string, fp) == EOF) return_code = JSONFailure; if (fclose(fp) == EOF) return_code = JSONFailure; json_free_serialized_string(serialized_string); return return_code; } char * json_serialize_to_string(const JSON_Value *value) { JSON_Status serialization_result = JSONFailure; size_t buf_size_bytes = json_serialization_size(value); char *buf = NULL; if (buf_size_bytes == 0) return NULL; buf = (char *)parson_malloc(buf_size_bytes); if (buf == NULL) return NULL; serialization_result = json_serialize_to_buffer(value, buf, buf_size_bytes); if (serialization_result == JSONFailure) { json_free_serialized_string(buf); return NULL; } return buf; } size_t json_serialization_size_pretty(const JSON_Value *value) { char num_buf[NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */ int res = json_serialize_to_buffer_r(value, NULL, 0, 1, num_buf); return res < 0 ? 0 : (size_t)(res + 1); } JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) { int written = -1; size_t needed_size_in_bytes = json_serialization_size_pretty(value); if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) return JSONFailure; written = json_serialize_to_buffer_r(value, buf, 0, 1, NULL); if (written < 0) return JSONFailure; return JSONSuccess; } JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename) { JSON_Status return_code = JSONSuccess; FILE *fp = NULL; char *serialized_string = json_serialize_to_string_pretty(value); if (serialized_string == NULL) return JSONFailure; fp = fopen(filename, "w"); if (fp == NULL) { json_free_serialized_string(serialized_string); return JSONFailure; } if (fputs(serialized_string, fp) == EOF) return_code = JSONFailure; if (fclose(fp) == EOF) return_code = JSONFailure; json_free_serialized_string(serialized_string); return return_code; } char * json_serialize_to_string_pretty(const JSON_Value *value) { JSON_Status serialization_result = JSONFailure; size_t buf_size_bytes = json_serialization_size_pretty(value); char *buf = NULL; if (buf_size_bytes == 0) return NULL; buf = (char *)parson_malloc(buf_size_bytes); if (buf == NULL) return NULL; serialization_result = json_serialize_to_buffer_pretty(value, buf, buf_size_bytes); if (serialization_result == JSONFailure) { json_free_serialized_string(buf); return NULL; } return buf; } void json_free_serialized_string(char *string) { parson_free(string); } JSON_Status json_array_remove(JSON_Array *array, size_t ix) { size_t to_move_bytes = 0; if (array == NULL || ix >= json_array_get_count(array)) return JSONFailure; json_value_free(json_array_get_value(array, ix)); to_move_bytes = (json_array_get_count(array) - 1 - ix) * sizeof(JSON_Value *); memmove(array->items + ix, array->items + ix + 1, to_move_bytes); array->count -= 1; return JSONSuccess; } JSON_Status json_array_replace_value(JSON_Array *array, size_t ix, JSON_Value *value) { if (array == NULL || value == NULL || value->parent != NULL || ix >= json_array_get_count(array)) return JSONFailure; json_value_free(json_array_get_value(array, ix)); value->parent = json_array_get_wrapping_value(array); array->items[ix] = value; return JSONSuccess; } JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char *string) { JSON_Value *value = json_value_new_string(string); if (value == NULL) return JSONFailure; if (json_array_replace_value(array, i, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number) { JSON_Value *value = json_value_new_number(number); if (value == NULL) return JSONFailure; if (json_array_replace_value(array, i, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_uint64(JSON_Array *array, size_t i, uint64_t number) { JSON_Value *value = json_value_new_uint64(number); if (value == NULL) return JSONFailure; if (json_array_replace_value(array, i, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_integer(JSON_Array *array, size_t i, int number) { JSON_Value *value = json_value_new_integer(number); if (value == NULL) return JSONFailure; if (json_array_replace_value(array, i, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean) { JSON_Value *value = json_value_new_boolean(boolean); if (value == NULL) return JSONFailure; if (json_array_replace_value(array, i, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_replace_null(JSON_Array *array, size_t i) { JSON_Value *value = json_value_new_null(); if (value == NULL) return JSONFailure; if (json_array_replace_value(array, i, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_clear(JSON_Array *array) { size_t i = 0; if (array == NULL) return JSONFailure; for (i = 0; i < json_array_get_count(array); i++) json_value_free(json_array_get_value(array, i)); array->count = 0; return JSONSuccess; } JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value) { if (array == NULL || value == NULL || value->parent != NULL) return JSONFailure; return json_array_add(array, value); } JSON_Status json_array_append_string(JSON_Array *array, const char *string) { JSON_Value *value = json_value_new_string(string); if (value == NULL) return JSONFailure; if (json_array_append_value(array, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_number(JSON_Array *array, double number) { JSON_Value *value = json_value_new_number(number); if (value == NULL) return JSONFailure; if (json_array_append_value(array, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_uint64(JSON_Array *array, uint64_t number) { JSON_Value *value = json_value_new_uint64(number); if (value == NULL) return JSONFailure; if (json_array_append_value(array, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_integer(JSON_Array *array, int number) { JSON_Value *value = json_value_new_integer(number); if (value == NULL) return JSONFailure; if (json_array_append_value(array, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_boolean(JSON_Array *array, int boolean) { JSON_Value *value = json_value_new_boolean(boolean); if (value == NULL) return JSONFailure; if (json_array_append_value(array, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_array_append_null(JSON_Array *array) { JSON_Value *value = json_value_new_null(); if (value == NULL) return JSONFailure; if (json_array_append_value(array, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value) { size_t i = 0; JSON_Value *old_value; if (object == NULL || name == NULL || value == NULL || value->parent != NULL) return JSONFailure; old_value = json_object_get_value(object, name); if (old_value != NULL) { /* free and overwrite old value */ json_value_free(old_value); for (i = 0; i < json_object_get_count(object); i++) if (strcmp(object->names[i], name) == 0) { value->parent = json_object_get_wrapping_value(object); object->values[i] = value; return JSONSuccess; } } /* add new key value pair */ return json_object_add(object, name, value); } JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string) { return json_object_set_value(object, name, json_value_new_string(string)); } JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number) { return json_object_set_value(object, name, json_value_new_number(number)); } JSON_Status json_object_set_uint64(JSON_Object *object, const char *name, uint64_t number) { return json_object_set_value(object, name, json_value_new_uint64(number)); } JSON_Status json_object_set_integer(JSON_Object *object, const char *name, int number) { return json_object_set_value(object, name, json_value_new_integer(number)); } JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean) { return json_object_set_value(object, name, json_value_new_boolean(boolean)); } JSON_Status json_object_set_null(JSON_Object *object, const char *name) { return json_object_set_value(object, name, json_value_new_null()); } JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value) { const char *dot_pos = NULL; JSON_Value *temp_value = NULL, *new_value = NULL; JSON_Object *temp_object = NULL, *new_object = NULL; JSON_Status status = JSONFailure; size_t name_len = 0; if (object == NULL || name == NULL || value == NULL) return JSONFailure; dot_pos = strchr(name, '.'); if (dot_pos == NULL) return json_object_set_value(object, name, value); name_len = dot_pos - name; temp_value = json_object_getn_value(object, name, name_len); if (temp_value) { /* Don't overwrite existing non-object (unlike json_object_set_value, but it shouldn't be * changed at this point) */ if (json_value_get_type(temp_value) != JSONObject) return JSONFailure; temp_object = json_value_get_object(temp_value); return json_object_dotset_value(temp_object, dot_pos + 1, value); } new_value = json_value_new_object(); if (new_value == NULL) return JSONFailure; new_object = json_value_get_object(new_value); status = json_object_dotset_value(new_object, dot_pos + 1, value); if (status != JSONSuccess) { json_value_free(new_value); return JSONFailure; } status = json_object_addn(object, name, name_len, new_value); if (status != JSONSuccess) { json_object_dotremove_internal(new_object, dot_pos + 1, 0); json_value_free(new_value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string) { JSON_Value *value = json_value_new_string(string); if (value == NULL) return JSONFailure; if (json_object_dotset_value(object, name, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number) { JSON_Value *value = json_value_new_number(number); if (value == NULL) return JSONFailure; if (json_object_dotset_value(object, name, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_uint64(JSON_Object *object, const char *name, uint64_t number) { JSON_Value *value = json_value_new_uint64(number); if (value == NULL) return JSONFailure; if (json_object_dotset_value(object, name, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_integer(JSON_Object *object, const char *name, int number) { JSON_Value *value = json_value_new_integer(number); if (value == NULL) return JSONFailure; if (json_object_dotset_value(object, name, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean) { JSON_Value *value = json_value_new_boolean(boolean); if (value == NULL) return JSONFailure; if (json_object_dotset_value(object, name, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_dotset_null(JSON_Object *object, const char *name) { JSON_Value *value = json_value_new_null(); if (value == NULL) return JSONFailure; if (json_object_dotset_value(object, name, value) == JSONFailure) { json_value_free(value); return JSONFailure; } return JSONSuccess; } JSON_Status json_object_remove(JSON_Object *object, const char *name) { return json_object_remove_internal(object, name, 1); } JSON_Status json_object_dotremove(JSON_Object *object, const char *name) { return json_object_dotremove_internal(object, name, 1); } JSON_Status json_object_clear(JSON_Object *object) { size_t i = 0; if (object == NULL) return JSONFailure; for (i = 0; i < json_object_get_count(object); i++) { parson_free(object->names[i]); json_value_free(object->values[i]); } object->count = 0; return JSONSuccess; } JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value) { JSON_Value *temp_schema_value = NULL, *temp_value = NULL; JSON_Array *schema_array = NULL, *value_array = NULL; JSON_Object *schema_object = NULL, *value_object = NULL; JSON_Value_Type schema_type = JSONError, value_type = JSONError; const char *key = NULL; size_t i = 0, count = 0; if (schema == NULL || value == NULL) return JSONFailure; schema_type = json_value_get_type(schema); value_type = json_value_get_type(value); if (schema_type != value_type && schema_type != JSONNull) /* null represents all values */ return JSONFailure; switch (schema_type) { case JSONArray: schema_array = json_value_get_array(schema); value_array = json_value_get_array(value); count = json_array_get_count(schema_array); if (count == 0) return JSONSuccess; /* Empty array allows all types */ /* Get first value from array, rest is ignored */ temp_schema_value = json_array_get_value(schema_array, 0); for (i = 0; i < json_array_get_count(value_array); i++) { temp_value = json_array_get_value(value_array, i); if (json_validate(temp_schema_value, temp_value) == JSONFailure) return JSONFailure; } return JSONSuccess; case JSONObject: schema_object = json_value_get_object(schema); value_object = json_value_get_object(value); count = json_object_get_count(schema_object); if (count == 0) return JSONSuccess; /* Empty object allows all objects */ else if (json_object_get_count(value_object) < count) return JSONFailure; /* Tested object mustn't have less name-value pairs than schema */ for (i = 0; i < count; i++) { key = json_object_get_name(schema_object, i); temp_schema_value = json_object_get_value(schema_object, key); temp_value = json_object_get_value(value_object, key); if (temp_value == NULL) return JSONFailure; if (json_validate(temp_schema_value, temp_value) == JSONFailure) return JSONFailure; } return JSONSuccess; case JSONString: case JSONNumber: case JSONBoolean: case JSONNull: return JSONSuccess; /* equality already tested before switch */ case JSONError: default: return JSONFailure; } } int json_value_equals(const JSON_Value *a, const JSON_Value *b) { JSON_Object *a_object = NULL, *b_object = NULL; JSON_Array *a_array = NULL, *b_array = NULL; const char *a_string = NULL, *b_string = NULL; const char *key = NULL; size_t a_count = 0, b_count = 0, i = 0; JSON_Value_Type a_type, b_type; a_type = json_value_get_type(a); b_type = json_value_get_type(b); if (a_type != b_type) return 0; switch (a_type) { case JSONArray: a_array = json_value_get_array(a); b_array = json_value_get_array(b); a_count = json_array_get_count(a_array); b_count = json_array_get_count(b_array); if (a_count != b_count) return 0; for (i = 0; i < a_count; i++) if (!json_value_equals(json_array_get_value(a_array, i), json_array_get_value(b_array, i))) return 0; return 1; case JSONObject: a_object = json_value_get_object(a); b_object = json_value_get_object(b); a_count = json_object_get_count(a_object); b_count = json_object_get_count(b_object); if (a_count != b_count) return 0; for (i = 0; i < a_count; i++) { key = json_object_get_name(a_object, i); if (!json_value_equals(json_object_get_value(a_object, key), json_object_get_value(b_object, key))) return 0; } return 1; case JSONString: a_string = json_value_get_string(a); b_string = json_value_get_string(b); if (a_string == NULL || b_string == NULL) return 0; /* shouldn't happen */ return strcmp(a_string, b_string) == 0; case JSONBoolean: return json_value_get_boolean(a) == json_value_get_boolean(b); case JSONNumber: return fabs(json_value_get_number(a) - json_value_get_number(b)) < 0.000001; /* EPSILON */ case JSONUint64: return json_value_get_uint64(a) - json_value_get_uint64(b); case JSONInteger: return abs(json_value_get_integer(a) - json_value_get_integer(b)); case JSONError: return 1; case JSONNull: return 1; default: return 1; } } JSON_Value_Type json_get_type(const JSON_Value *value) { return json_value_get_type(value); } JSON_Object * json_get_object(const JSON_Value *value) { return json_value_get_object(value); } JSON_Array * json_get_array(const JSON_Value *value) { return json_value_get_array(value); } const char * json_get_string(const JSON_Value *value) { return json_value_get_string(value); } double json_get_number(const JSON_Value *value) { return json_value_get_number(value); } uint64_t json_get_uint64(const JSON_Value *value) { return json_value_get_uint64(value); } int json_get_integer(const JSON_Value *value) { return json_value_get_integer(value); } int json_get_boolean(const JSON_Value *value) { return json_value_get_boolean(value); } void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun) { parson_malloc = malloc_fun; parson_free = free_fun; } ================================================ FILE: lib/utils/parson_json.h ================================================ /* Parson ( http://kgabis.github.com/parson/ ) Copyright(c) 2012 - 2017 Krzysztof Gabis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef parson_parson_h #define parson_parson_h #ifdef __cplusplus extern "C" { #endif #include /* size_t */ #include /* Types and enums */ typedef struct json_object_t JSON_Object; typedef struct json_array_t JSON_Array; typedef struct json_value_t JSON_Value; enum json_value_type { JSONError = -1, JSONNull = 1, JSONString = 2, JSONNumber = 3, JSONObject = 4, JSONArray = 5, JSONBoolean = 6, JSONUint64 = 7, JSONInteger = 8 }; typedef int JSON_Value_Type; enum json_result_t { JSONSuccess = 0, JSONFailure = -1 }; typedef int JSON_Status; typedef void *(*JSON_Malloc_Function)(size_t); typedef void (*JSON_Free_Function)(void *); /* Call only once, before calling any other function from parson API. If not called, malloc and free from stdlib will be used for all allocations */ void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun); /* Parses first JSON value in a file, returns NULL in case of error */ JSON_Value *json_parse_file(const char *filename); /* Parses first JSON value in a file and ignores comments (/ * * / and //), returns NULL in case of error */ JSON_Value *json_parse_file_with_comments(const char *filename); /* Parses first JSON value in a string, returns NULL in case of error */ JSON_Value *json_parse_string(const char *string); /* Parses first JSON value in a string and ignores comments (/ * * / and //), returns NULL in case of error */ JSON_Value *json_parse_string_with_comments(const char *string); /* Serialization */ size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */ JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename); char *json_serialize_to_string(const JSON_Value *value); /* Pretty serialization */ size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */ JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename); char *json_serialize_to_string_pretty(const JSON_Value *value); void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */ /* Comparing */ int json_value_equals(const JSON_Value *a, const JSON_Value *b); /* Validation This is *NOT* JSON Schema. It validates json by checking if object have identically named fields with matching types. For example schema {"name":"", "age":0} will validate {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"}, but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}. In case of arrays, only first value in schema is checked against all values in tested array. Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays, null validates values of every type. */ JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value); /* * JSON Object */ JSON_Value *json_object_get_value(const JSON_Object *object, const char *name); const char *json_object_get_string(const JSON_Object *object, const char *name); JSON_Object *json_object_get_object(const JSON_Object *object, const char *name); JSON_Array *json_object_get_array(const JSON_Object *object, const char *name); double json_object_get_number(const JSON_Object *object, const char *name); /* returns 0 on fail */ uint64_t json_object_get_uint64(const JSON_Object *object, const char *name); /* returns 0 on fail */ int json_object_get_integer(const JSON_Object *object, const char *name); /* returns -1 on fail */ int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ const char *json_object_at_get_string(const JSON_Object *object, size_t idx); JSON_Object *json_object_at_get_object(const JSON_Object *object, size_t idx); JSON_Array *json_object_at_get_array(const JSON_Object *object, size_t idx); double json_object_at_get_number(const JSON_Object *object, size_t idx); /* returns 0 on fail */ uint64_t json_object_at_get_uint64(const JSON_Object *object, size_t idx); /* returns 0 on fail */ int json_object_at_get_integer(const JSON_Object *object, size_t idx); int json_object_at_get_boolean(const JSON_Object *object, size_t idx); /* returns -1 on fail */ /* dotget functions enable addressing values with dot notation in nested objects, just like in structs or c++/java/c# objects (e.g. objectA.objectB.value). Because valid names in JSON can contain dots, some values may be inaccessible this way. */ JSON_Value *json_object_dotget_value(const JSON_Object *object, const char *name); const char *json_object_dotget_string(const JSON_Object *object, const char *name); JSON_Object *json_object_dotget_object(const JSON_Object *object, const char *name); JSON_Array *json_object_dotget_array(const JSON_Object *object, const char *name); double json_object_dotget_number(const JSON_Object *object, const char *name); /* returns 0 on fail */ uint64_t json_object_dotget_uint64(const JSON_Object *object, const char *name); /* returns 0 on fail */ int json_object_dotget_integer(const JSON_Object *object, const char *name); /* returns -1 on fail */ int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ int json_object_get_boolean_by_name(JSON_Object *obj, const char *name); /* Functions to get available names */ size_t json_object_get_count(const JSON_Object *object); const char *json_object_get_name(const JSON_Object *object, size_t index); JSON_Value *json_object_get_value_at(const JSON_Object *object, size_t index); JSON_Value *json_object_get_wrapping_value(const JSON_Object *object); /* Functions to check if object has a value with a specific name. Returned value is 1 if object has * a value and 0 if it doesn't. dothas functions behave exactly like dotget functions. */ int json_object_has_value(const JSON_Object *object, const char *name); int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); int json_object_dothas_value(const JSON_Object *object, const char *name); int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type); /* Creates new name-value pair or frees and replaces old value with a new one. * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value); JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string); JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number); JSON_Status json_object_set_uint64(JSON_Object *object, const char *name, uint64_t number); JSON_Status json_object_set_integer(JSON_Object *object, const char *name, int number); JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean); JSON_Status json_object_set_null(JSON_Object *object, const char *name); /* Works like dotget functions, but creates whole hierarchy if necessary. * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value); JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string); JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number); JSON_Status json_object_dotset_uint64(JSON_Object *object, const char *name, uint64_t number); JSON_Status json_object_dotset_integer(JSON_Object *object, const char *name, int number); JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean); JSON_Status json_object_dotset_null(JSON_Object *object, const char *name); /* Frees and removes name-value pair */ JSON_Status json_object_remove(JSON_Object *object, const char *name); /* Works like dotget function, but removes name-value pair only on exact match. */ JSON_Status json_object_dotremove(JSON_Object *object, const char *key); /* Removes all name-value pairs in object */ JSON_Status json_object_clear(JSON_Object *object); /* * JSON Array */ JSON_Value *json_array_get_value(const JSON_Array *array, size_t index); const char *json_array_get_string(const JSON_Array *array, size_t index); JSON_Object *json_array_get_object(const JSON_Array *array, size_t index); JSON_Array *json_array_get_array(const JSON_Array *array, size_t index); double json_array_get_number(const JSON_Array *array, size_t index); /* returns 0 on fail */ uint64_t json_array_get_uint64(const JSON_Array *array, size_t index); /* returns 0 on fail */ int json_array_get_integer(const JSON_Array *array, size_t index); /* returns -1 on fail */ int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */ size_t json_array_get_count(const JSON_Array *array); JSON_Value *json_array_get_wrapping_value(const JSON_Array *array); /* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't * exist. Order of values in array may change during execution. */ JSON_Status json_array_remove(JSON_Array *array, size_t i); /* Frees and removes from array value at given index and replaces it with given one. * Does nothing and returns JSONFailure if index doesn't exist. * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value); JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char *string); JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number); JSON_Status json_array_replace_uint64(JSON_Array *array, size_t i, uint64_t number); JSON_Status json_array_replace_integer(JSON_Array *array, size_t i, int number); JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean); JSON_Status json_array_replace_null(JSON_Array *array, size_t i); /* Frees and removes all values from array */ JSON_Status json_array_clear(JSON_Array *array); /* Appends new value at the end of array. * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */ JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value); JSON_Status json_array_append_string(JSON_Array *array, const char *string); JSON_Status json_array_append_number(JSON_Array *array, double number); JSON_Status json_array_append_uint64(JSON_Array *array, uint64_t number); JSON_Status json_array_append_integer(JSON_Array *array, int number); JSON_Status json_array_append_boolean(JSON_Array *array, int boolean); JSON_Status json_array_append_null(JSON_Array *array); /* * JSON Value */ JSON_Value *json_value_new_object(void); JSON_Value *json_value_new_array(void); JSON_Value *json_value_new_string(const char *string); /* copies passed string */ JSON_Value *json_value_new_number(double number); JSON_Value *json_value_new_uint64(uint64_t number); JSON_Value *json_value_new_integer(int number); JSON_Value *json_value_new_boolean(int boolean); JSON_Value *json_value_new_null(void); JSON_Value *json_value_deep_copy(const JSON_Value *value); void json_value_free(JSON_Value *value); JSON_Value_Type json_value_get_type(const JSON_Value *value); JSON_Object *json_value_get_object(const JSON_Value *value); JSON_Array *json_value_get_array(const JSON_Value *value); const char *json_value_get_string(const JSON_Value *value); double json_value_get_number(const JSON_Value *value); uint64_t json_value_get_uint64(const JSON_Value *value); int json_value_get_integer(const JSON_Value *value); int json_value_get_boolean(const JSON_Value *value); JSON_Value *json_value_get_parent(const JSON_Value *value); /* Same as above, but shorter */ JSON_Value_Type json_get_type(const JSON_Value *value); JSON_Object *json_get_object(const JSON_Value *value); JSON_Array *json_get_array(const JSON_Value *value); const char *json_get_string(const JSON_Value *value); double json_get_number(const JSON_Value *value); uint64_t json_get_uint64(const JSON_Value *value); int json_get_integer(const JSON_Value *value); int json_get_boolean(const JSON_Value *value); static inline const char * json_string_type(JSON_Value *val) { if (!val) return "oops"; if (json_get_type(val) == JSONString) return "String"; if (json_get_type(val) == JSONNull) return "Null"; if (json_get_type(val) == JSONNumber) return "Number"; if (json_get_type(val) == JSONUint64) return "uint64"; if (json_get_type(val) == JSONInteger) return "Integer"; if (json_get_type(val) == JSONArray) return "Array"; if (json_get_type(val) == JSONObject) return "Object"; if (json_get_type(val) == JSONBoolean) return "Boolean"; return "Unknown"; } #ifdef __cplusplus } #endif #endif ================================================ FILE: lib/utils/portlist.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ #include "pg_strings.h" #include "portlist.h" int portmask_parse(const char *str, portlist_t *portmask) { char *end = NULL; portlist_t pm; /* parse hexadecimal string */ pm = strtoull(str, &end, 16); if ((str[0] == '\0') || (end == NULL) || (*end != '\0')) return -1; if (pm == 0) return -1; if (portmask) *portmask = pm; return 0; } static inline void set_portlist_bits(size_t low, size_t high, uint64_t *map) { do { *map |= (1LL << low++); } while (low <= high); } #define MAX_SPLIT 64 /* portlist = N,N,N-M,N, ... */ int portlist_parse(const char *str, int nb_ports, portlist_t *portlist) { size_t ps, pe, n; char *split[MAX_SPLIT], *s, *p; uint64_t map = 0; if (!str || !*str || !portlist) return -1; *portlist = 0; if (!strcmp(str, "all")) { for (int i = 0; i < nb_ports; i++) *portlist |= (1LL << i); return 0; } n = strlen(str); s = alloca(n + 1); if (!s) return -1; memcpy(s, str, n); s[n] = '\0'; n = pg_strtok(s, ",", split, MAX_SPLIT); if (!n) return 0; for (size_t i = 0; i < n; i++) { p = strchr(split[i], '-'); if (!p) { ps = strtoul(split[i], NULL, 10); pe = ps; } else { *p++ = '\0'; ps = strtoul(split[i], NULL, 10); pe = strtoul(p, NULL, 10); } if ((ps > pe) || (pe >= (sizeof(map) * 8))) return -1; if (pe > RTE_MAX_ETHPORTS) pe = RTE_MAX_ETHPORTS; set_portlist_bits(ps, pe, &map); } if (portlist) *portlist = map; return 0; } char * portlist_string(uint64_t portlist, char *buf, int len) { int i, k, j, cnt = 0; k = 0; for (i = 0; i < RTE_MAX_ETHPORTS; i++) if (portlist & (1UL << i)) cnt++; memset(buf, 0, len); k = 0; j = 0; *buf = '\0'; for (i = 0; (i < RTE_MAX_ETHPORTS) && (k < len); i++) if (portlist & (1UL << i)) { k += snprintf(&buf[k], len - k, "%d%s", i, (j >= cnt) ? "" : ", "); j++; } if (k >= 2) buf[k - 2] = '\0'; return buf; } char * portlist_print(FILE *f, uint64_t portlist, char *buf, int len) { int i, k; if (!f) f = stdout; k = 0; *buf = '\0'; for (i = 0; (i < RTE_MAX_ETHPORTS) && (k < len); i++) if (portlist & (1UL << i)) k += snprintf(&buf[k], len - k, "%d, ", i); if (k >= 2) buf[k - 2] = '\0'; return buf; } ================================================ FILE: lib/utils/portlist.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /** * @file * * String-related utility function for parsing port mask. */ #ifndef __PORTLIST_H_ #define __PORTLIST_H_ #include #include #include #include #ifdef __cplusplus extern "C" { #endif typedef uint64_t portlist_t; #define INVALID_PORTLIST ((portlist_t) - 1) /** * Parse a portlist string into a mask or bitmap value. * * @param str * String to parse * @param nb_ports * Max number of ports to set in the portlist * @param portlist * Pointer to uint64_t value for returned bitmap * @return * -1 on error or 0 on success. */ int portlist_parse(const char *str, int nb_ports, portlist_t *portlist); /** * Parse a portmask string into a mask or bitmap value. * * @param str * String to parse * @param portmask * Pointer to uint64_t value for returned bitmap * @return * -1 on error or 0 on success. */ int portmask_parse(const char *str, portlist_t *portmask); /** * Convert a portlist bitmap to a human-readable string (e.g. "0,1,3-5"). * * @param portlist * 64-bit bitmap of port indices to serialise. * @param buf * Destination buffer for the result string. * @param len * Size of @p buf in bytes. * @return * Pointer to @p buf on success, or NULL on error. */ char *portlist_string(uint64_t portlist, char *buf, int len); /** * Convert a portlist bitmap to a string and print it to file @p f. * * @param f * Output file (e.g. stdout). * @param portlist * 64-bit bitmap of port indices to serialise. * @param buf * Scratch buffer used to build the string. * @param len * Size of @p buf in bytes. * @return * Pointer to @p buf on success, or NULL on error. */ char *portlist_print(FILE *f, uint64_t portlist, char *buf, int len); #ifdef __cplusplus } #endif #endif /* __PORTLIST_H_ */ ================================================ FILE: lib/vec/meson.build ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2020-2026> Intel Corporation sources = files('vec.c') libvec = library('vec', sources, dependencies: [common, utils, dpdk]) vec = declare_dependency(link_with: libvec, include_directories: include_directories('.')) ================================================ FILE: lib/vec/vec.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #include #include #include #include #include "vec.h" static void vec_obj_init(struct rte_mempool *mp, void *uarg __rte_unused, void *obj, unsigned idx __rte_unused) { struct vec *v = (struct vec *)obj; vec_reset(mp, v); } struct rte_mempool * vec_create_pool(const char *name, unsigned int n, unsigned int entires, unsigned int cache_size) { struct rte_mempool *mp = NULL; unsigned int size = vec_calc_size(entires); mp = rte_mempool_create(name, n, size, cache_size, 0, NULL, NULL, NULL, NULL, pg_socket_id(), 0); if (mp) rte_mempool_obj_iter(mp, vec_obj_init, NULL); return mp; } void vec_destroy_pool(struct rte_mempool *obj) { rte_mempool_free(obj); } struct vec * vec_create(const char *name, unsigned int n, uint16_t flags) { struct vec *vec; uint32_t vec_size; vec_size = vec_calc_size(n); vec = rte_zmalloc_socket(name, vec_size, RTE_CACHE_LINE_SIZE, pg_socket_id()); if (vec) { vec->tlen = n; vec->flags = flags; vec->flags |= VEC_CREATE_FLAG; } return vec; } void vec_destroy(struct vec *vec) { rte_free(vec); } int vec_to_data(struct vec *v, char *buf, size_t len) { size_t count = 0, cnt; uint16_t vlen; int i; vlen = vec_len(v); for (i = 0; i < vlen && len; i++) { struct rte_mbuf *m = vec_at_index(v, i); /* set cnt to number of bytes that will fit in buffer */ cnt = RTE_MIN(rte_pktmbuf_pkt_len(m), len); /* Copy the data from mbuf to buffer for cnt bytes */ rte_memcpy(buf, rte_pktmbuf_mtod(m, char *), cnt); rte_pktmbuf_adj(m, cnt); /* reduce the pkt size */ count += cnt; len -= cnt; buf += cnt; if (rte_pktmbuf_pkt_len(m) == 0) vec_free_mbuf_at_index(v, i); else break; } vec_compact(v); return count; } void vec_print(FILE *f, const char *msg, struct vec *v) { int i, k; if (!f) f = stderr; if (!msg) fprintf(f, "Vector: @ %p: ", v); else fprintf(f, "Vector: %s @ %p ", msg, v); fprintf(f, " flags %04x, len %u, tlen %u\n", v->flags, v->len, v->tlen); fprintf(f, " vpool %p\nList: ", v->vpool); for (i = 0, k = 0; i < v->len; i++) { fprintf(f, "%p ", v->list[i]); if (++k >= 8) { k = 0; fprintf(f, "\n "); } } fprintf(f, "\n"); } ================================================ FILE: lib/vec/vec.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause * Copyright(c) <2020-2026> Intel Corporation. */ /* Created 2018 by Keith Wiles @ intel.com */ #ifndef __VEC_H #define __VEC_H /** * @file * * DPDK mempool-backed pointer vector container. * * A vec is a fixed-capacity array of void pointers allocated from an * rte_mempool object. It is used throughout Pktgen for batching * rte_mbuf pointers across lcore-to-port boundaries. */ #include #include #ifdef __cplusplus extern "C" { #endif #define VEC_DEFAULT_SIZE 8 struct vec { uint16_t flags; /**< Flags for Vec structure */ uint16_t len; /**< Number of pointers in vector list */ uint16_t tlen; /**< Total number of vectors */ void *vpool; /**< pool from were the vector was allocated */ uint16_t pad0[20]; /**< force to cache line size */ void *list[0]; /**< Vector list of pointer place holder */ }; /** Flag bits stored in struct vec::flags. */ enum { VEC_FREE_FLAG = 0x0001, /**< Vec has been returned to its mempool */ VEC_COMPACT_FLAG = 0x0002, /**< Vec contains NULL gaps; compact needed */ VEC_DONT_FREE_FLAG = 0x0004, /**< Do not return vec to mempool on free */ VEC_CREATE_FLAG = 0x0008, /**< Vec was created via vec_create() */ VEC_COND_WAIT_FLAG = 0x8000, /**< Internal: conditional-wait in use */ VEC_RESET_MASK = (VEC_DONT_FREE_FLAG | VEC_CREATE_FLAG), /**< Flags preserved across reset */ VEC_CLEAR_FLAGS = 0x0000 /**< Clear all flags */ }; /** * Iterate over all entries in a vec. * * @param idx Integer index variable (declared by caller). * @param var Pointer variable set to each element on each iteration. * @param vec Pointer to the struct vec to iterate over. */ #define vec_foreach(idx, var, vec) \ for (idx = 0, var = vec_at_index((vec), idx); idx < vec_len((vec)); \ idx++, var = vec_at_index((vec), idx)) /** * Initialise an already-allocated vec in place. * * @param v * Pointer to the vec to initialise. * @param n * Maximum number of elements (total length). * @param flags * Initial flag bits (see VEC_* constants). */ static inline void vec_init(struct vec *v, unsigned int n, uint16_t flags) { v->len = 0; v->tlen = n; v->flags = flags; v->vpool = NULL; } /** * Calculate the allocation size in bytes for a vec with @p cnt entries. * * The result is cache-line aligned. If @p cnt is zero, VEC_DEFAULT_SIZE is used. * * @param cnt * Desired element capacity. * @return * Byte size of the vec object, aligned to RTE_CACHE_LINE_SIZE. */ static inline unsigned int vec_calc_size(unsigned int cnt) { unsigned int size; if (cnt == 0) cnt = VEC_DEFAULT_SIZE; size = (cnt * sizeof(void *)) + sizeof(struct vec); return RTE_ALIGN_CEIL(size, RTE_CACHE_LINE_SIZE); } /** * Calculate how many vec entries fit within one mempool element. * * @param mp * Mempool whose element size determines the entry count. * @return * Number of void-pointer entries available in a single vec element. */ static inline uint32_t vec_calc_count(struct rte_mempool *mp) { uint32_t size = mp->elt_size; size = (size - sizeof(struct vec) - sizeof(void *)) / sizeof(void *); return size; } /** Mark vec @p v as already returned to its mempool. */ static inline void vec_set_free(struct vec *v) { v->flags |= VEC_FREE_FLAG; } /** Return non-zero if vec @p v has been freed (VEC_FREE_FLAG set). */ static inline int vec_is_free(struct vec *v) { return v->flags & VEC_FREE_FLAG; } /** Return non-zero if VEC_DONT_FREE_FLAG is set on @p vec. */ static inline int vec_is_dont_free(struct vec *vec) { return vec->flags & VEC_DONT_FREE_FLAG; } /** Set VEC_DONT_FREE_FLAG on @p vec so it is not returned to its mempool on free. */ static inline void vec_set_dont_free(struct vec *vec) { vec->flags |= VEC_DONT_FREE_FLAG; } /** Clear VEC_DONT_FREE_FLAG on @p vec, re-enabling normal mempool return. */ static inline void vec_clr_dont_free(struct vec *vec) { vec->flags &= ~VEC_DONT_FREE_FLAG; } /** Return the current number of elements in vec @p v. */ static __rte_always_inline uint16_t vec_len(struct vec *v) { return v->len; } /** Return the used size of vec @p v in bytes (len * sizeof(void*)). */ static __rte_always_inline int vec_byte_len(struct vec *v) { return v->len * sizeof(void *); } /** Set the current element count of vec @p v to @p n. */ static __rte_always_inline void vec_set_len(struct vec *v, uint16_t n) { v->len = n; } /** Set the maximum element capacity of vec @p v to @p n. */ static __rte_always_inline void vec_set_max_len(struct vec *v, uint16_t n) { v->tlen = n; } /** Decrement the element count of vec @p v by one. */ static __rte_always_inline void vec_dec_len(struct vec *v) { v->len--; } /** Increment the element count of vec @p v by one. */ static __rte_always_inline void vec_inc_len(struct vec *v) { v->len++; } /** Return the maximum element capacity of vec @p v. */ static __rte_always_inline uint16_t vec_max_len(struct vec *v) { return v->tlen; } /** Return a pointer to the start of the rte_mbuf pointer array in @p v. */ static __rte_always_inline struct rte_mbuf ** vec_list(struct vec *v) { return (struct rte_mbuf **)&v->list[0]; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warray-bounds" /** * Append one pointer to @p vec. * * @param vec Vec to append to. * @param val Pointer value to append. * @return Index of the inserted element, or -1 if the vec is full. */ static __rte_always_inline int vec_add1(struct vec *vec, void *val) { if (vec->len >= vec->tlen) return -1; vec->list[vec->len++] = val; return vec->len - 1; } /** * Store @p val at a specific index @p n in @p vec and increment the length. * * @param vec Vec to write into. * @param val Pointer value to store. * @param n Target index. * @return 0 on success, -1 if the vec is already full. */ static __rte_always_inline int vec_add_at_index(struct vec *vec, void *val, uint16_t n) { if (vec->len >= vec->tlen) return -1; vec->list[n] = val; vec->len++; return 0; } /** * Return the pointer at index @p n in @p vec. * * @param vec Vec to read from. * @param n Element index. * @return Pointer value, or NULL if @p n is out of range. */ static __rte_always_inline void * vec_at_index(struct vec *vec, uint16_t n) { if (n >= vec->len) return NULL; return vec->list[n]; } /** * Overwrite the entry at index @p idx in @p vec without changing the length. * * @param vec Vec to modify. * @param idx Target index (must be within tlen). * @param val New pointer value. */ static __rte_always_inline void vec_set_at_index(struct vec *vec, uint16_t idx, void *val) { if (idx < vec->tlen) vec->list[idx] = val; } /** * Return a pointer to the rte_mbuf pointer at index @p n in @p vec. * * @param vec Vec to address. * @param n Element index. * @return Pointer to element @p n. */ static __rte_always_inline struct rte_mbuf ** vec_addr(struct vec *vec, uint16_t n) { return (struct rte_mbuf **)&vec->list[n]; } /** * Return a pointer one past the last valid element of @p vec. * * @param vec Vec to address. * @return Pointer to the slot after the last element. */ static __rte_always_inline struct rte_mbuf ** vec_end(struct vec *vec) { return (struct rte_mbuf **)&vec->list[vec->len]; } /** * Return the number of unused slots remaining in @p vec. * * @param vec Vec to query. * @return tlen - len. */ static __rte_always_inline int vec_len_remaining(struct vec *vec) { return vec->tlen - vec->len; } /** * Return non-zero if @p v has no remaining capacity (len == tlen). * * @param v Vec to test. * @return Non-zero when full. */ static __rte_always_inline int vec_is_full(struct vec *v) { return (v->len == v->tlen); } #pragma GCC diagnostic pop /** * Reset @p vec to an empty state, preserving sticky flags (DONT_FREE, CREATE). * * @param mp Mempool that owns @p vec (stored in vec->vpool). * @param vec Vec to reset. */ static inline void vec_reset(struct rte_mempool *mp, struct vec *vec) { uint16_t flags = vec->flags; vec->flags = (flags & VEC_RESET_MASK); vec->vpool = mp; vec_set_max_len(vec, vec_calc_count(mp)); vec_set_len(vec, 0); } /** * Search @p vec for pointer @p v and return its index. * * @param vec Vec to search. * @param v Pointer value to find. * @return Index of the first matching entry, or -1 if not found. */ static inline int vec_find_index(struct vec *vec, void *v) { int i; for (i = 0; i < vec_len(vec); i++) { if (vec_at_index(vec, i) == v) return i; } return -1; } /** * Allocate a single vec from @p mp. * * @param mp Mempool to allocate from. * @return Pointer to the allocated vec, or NULL on failure. */ static inline struct vec * vec_alloc(struct rte_mempool *mp) { struct vec *vec; if (rte_mempool_get(mp, (void **)&vec)) return NULL; return vec; } /** * Return @p vec to its owning mempool (unless VEC_DONT_FREE_FLAG is set). * * If @p vec is NULL, already freed (VEC_FREE_FLAG), or has VEC_RESET_MASK * flags set, the function resets it in place without returning it to the pool. * * @param vec Vec to free. May be NULL. */ static inline void vec_free(struct vec *vec) { struct rte_mempool *mp; if (!vec) return; if (vec_is_free(vec)) return; vec_set_len(vec, 0); if (vec->flags & VEC_RESET_MASK) { vec->flags = (vec->flags & VEC_RESET_MASK); return; } vec->flags = (vec->flags & VEC_RESET_MASK); vec->flags |= VEC_FREE_FLAG; mp = vec->vpool; vec_reset(mp, vec); rte_mempool_put(mp, vec); } /** * Allocate @p count vecs from @p mp in one call and reset each to its default state. * * Uses Duff's device loop unrolling for efficiency. * * @param mp * Mempool from which vecs are allocated. * @param vecs * Caller-supplied array of pointers, filled with allocated vec objects. * @param count * Number of vecs to allocate. * @return * 0 on success, non-zero if rte_mempool_get_bulk() fails. */ static inline int vec_alloc_bulk(struct rte_mempool *mp, struct vec **vecs, unsigned count) { unsigned idx = 0; int rc; rc = rte_mempool_get_bulk(mp, (void **)vecs, count); if (unlikely(rc)) return rc; /* To understand duff's device on loop unwinding optimization, see * https://en.wikipedia.org/wiki/Duff's_device. * Here while() loop is used rather than do() while{} to avoid extra * check if count is zero. */ switch (count % 4) { case 0: while (idx != count) { vec_reset(mp, vecs[idx++]); /* fall-through */ case 3: vec_reset(mp, vecs[idx++]); /* fall-through */ case 2: vec_reset(mp, vecs[idx++]); /* fall-through */ case 1: vec_reset(mp, vecs[idx++]); /* fall-through */ } } return 0; } /** * Free @p n vecs from the @p vecs array by calling vec_free() on each. * * @param vecs Array of vec pointers to free. * @param n Number of entries in @p vecs. */ static inline void vec_free_bulk(struct vec **vecs, uint32_t n) { uint32_t i; for (i = 0; i < n; i++) vec_free(vecs[i]); } /** * Free every rte_mbuf stored in @p vec and reset its length to zero. * * Uses Duff's device loop unrolling. The vec itself is NOT returned to its * mempool; call vec_free() separately if that is desired. * * @param vec Vec whose mbuf entries should be freed. May be NULL. */ static inline void vec_free_mbufs(struct vec *vec) { unsigned idx = 0, count; struct rte_mbuf **mbufs; if (!vec) return; count = vec_len(vec); mbufs = vec_list(vec); vec_set_len(vec, 0); /* To understand duff's device on loop unwinding optimization, see * https://en.wikipedia.org/wiki/Duff's_device. * Here while() loop is used rather than do() while{} to avoid extra * check if count is zero. */ switch (count % 4) { case 0: while (idx != count) { rte_pktmbuf_free(mbufs[idx++]); /* fall-through */ case 3: rte_pktmbuf_free(mbufs[idx++]); /* fall-through */ case 2: rte_pktmbuf_free(mbufs[idx++]); /* fall-through */ case 1: rte_pktmbuf_free(mbufs[idx++]); /* fall-through */ } } } /** * Set the entry at @p idx to NULL and mark @p vec for compaction. * * @param vec Vec to modify. * @param idx Index to clear (must be a valid index). */ static inline void vec_clr_at_index(struct vec *vec, uint16_t idx) { /* Assume idx and vec are valid for vec array */ vec->list[idx] = NULL; vec->flags |= VEC_COMPACT_FLAG; } /** * Remove NULL gaps from @p vec by shifting remaining entries down. * * Only runs if VEC_COMPACT_FLAG is set; clears the flag when done. * * @param vec Vec to compact in place. */ static inline void vec_compact(struct vec *vec) { void **l1, **l2; uint16_t len; int i; if ((vec->flags & VEC_COMPACT_FLAG) == 0) return; vec->flags &= ~VEC_COMPACT_FLAG; if (!vec->len) return; /* Compress out the NULL entries */ l1 = l2 = vec->list; len = vec->len; vec->len = 0; for (i = 0; i < len; i++) { if (*l1) { if (l1 == l2) l2++; else *l2++ = *l1; vec->len++; } l1++; } } /** * Find and remove the first occurrence of @p val from @p vec. * * Compacts the vec after removal. * * @param vec Vec to search and modify. * @param val Pointer value to find and remove. */ static inline void vec_find_delete(struct vec *vec, void *val) { int idx = vec_find_index(vec, val); if (idx != -1) { vec_clr_at_index(vec, idx); vec_compact(vec); } } /** * Remove and return the first element from @p vec. * * @param vec Vec to pop from. * @param val Output: set to the first element's pointer value. * @return 1 if an element was popped, 0 if @p vec was empty. */ static inline int vec_pop(struct vec *vec, void **val) { RTE_ASSERT(vec && val); if (vec->len) { *val = vec_at_index(vec, 0); vec_clr_at_index(vec, 0); vec_compact(vec); return 1; } return 0; } /** * Free the rte_mbuf at index @p idx in @p vec and NULL out its slot. * * Sets VEC_COMPACT_FLAG; out-of-range @p idx is silently ignored. * * @param vec Vec containing the mbuf. * @param idx Index of the mbuf to free. */ static inline void vec_free_mbuf_at_index(struct vec *vec, uint16_t idx) { void *val; if (idx >= vec->len) return; /* Assume idx is valid for vec array */ val = vec->list[idx]; vec->list[idx] = NULL; rte_pktmbuf_free((struct rte_mbuf *)val); vec->flags |= VEC_COMPACT_FLAG; } /** * Move the entry at @p idx from @p from to @p to. * * On success, the source slot is cleared (compaction deferred). On failure * (destination full), the source entry is left intact. * * @param to Destination vec. * @param from Source vec. * @param idx Index in @p from to move. * @return Index assigned in @p to on success, -1 if @p to is full. */ static inline int vec_move_at_index(struct vec *to, struct vec *from, uint16_t idx) { void *v; int rc; v = vec_at_index(from, idx); rc = vec_add1(to, v); if (rc >= 0) vec_clr_at_index(from, idx); return rc; } /** * Copy the entry at @p idx from @p from into @p to (source is not cleared). * * @param to Destination vec. * @param from Source vec. * @param idx Index in @p from to copy. */ static inline void vec_copy_at_index(struct vec *to, struct vec *from, uint16_t idx) { void *v; v = vec_at_index(from, idx); vec_add1(to, v); } /** * Move the mbuf at @p idx from @p from to @p to; free the mbuf if @p to is full. * * @param to Destination vec. * @param from Source vec. * @param idx Index of the mbuf in @p from to move. */ static inline void vec_move_mbuf(struct vec *to, struct vec *from, uint16_t idx) { if (vec_move_at_index(to, from, idx)) rte_pktmbuf_free(vec_at_index(from, idx)); } /** * Retrieve the mbuf at @p idx from @p vec and clear its slot. * * @param vec Vec to read from. * @param idx Element index. * @return Pointer to the rte_mbuf at @p idx (may be NULL if already cleared). */ static inline struct rte_mbuf * vec_get_and_clr(struct vec *vec, uint16_t idx) { struct rte_mbuf *m; m = vec_at_index(vec, idx); vec_clr_at_index(vec, idx); return m; } /** * Create a standalone vec (allocated with rte_zmalloc, not from a mempool). * * @param name Logical name used for allocation tracking (passed to rte_zmalloc). * @param n Element capacity of the new vec. * @param flags Initial flag bits (see VEC_* constants). * @return Pointer to the new vec, or NULL on allocation failure. */ struct vec *vec_create(const char *name, unsigned int n, uint16_t flags); /** * Destroy a vec previously created with vec_create(). * * @param vec Vec to free. If NULL, the call is a no-op. */ void vec_destroy(struct vec *vec); /** * Create a DPDK mempool sized to hold vecs with @p entries elements each. * * @param name Name for the new mempool. * @param n Number of vec objects in the pool. * @param entries Element capacity of each vec object. * @param cache_size Per-lcore cache size for the mempool. * @return Pointer to the new rte_mempool, or NULL on failure. */ struct rte_mempool *vec_create_pool(const char *name, unsigned int n, unsigned int entries, unsigned int cache_size); /** * Free a mempool previously created with vec_create_pool(). * * @param obj Mempool to free. If NULL, the call is a no-op. */ void vec_destroy_pool(struct rte_mempool *obj); /** * Serialise the pointer values in @p v into a human-readable string. * * @param v Vec to serialise. * @param buf Destination buffer. * @param len Size of @p buf in bytes. * @return 0 on success, -1 if @p buf is too small. */ int vec_to_data(struct vec *v, char *buf, size_t len); /** * Print a summary of @p vec to file @p f with optional prefix @p msg. * * @param f Output file (e.g. stdout). * @param msg Optional prefix message string, or NULL. * @param vec Vec to print. */ void vec_print(FILE *f, const char *msg, struct vec *vec); #ifdef __cplusplus } #endif #endif /* __VEC_H */ ================================================ FILE: meson.build ================================================ project('pktgen', 'C', version: run_command(find_program('cat', 'more'), files('VERSION'), check:false).stdout().strip(), license: 'BSD', default_options: [ 'buildtype=release', 'default_library=static', 'warning_level=3', 'werror=true' ], meson_version: '>= 0.58.0' ) pktgen_conf = configuration_data() # set up some global vars for compiler, platform, configuration, etc. cc = meson.get_compiler('c') pktgen_source_root = meson.current_source_dir() pktgen_build_root = meson.current_build_dir() target = target_machine.cpu_family() if (target != 'riscv64') and (target != 'aarch64') add_project_arguments('-march=native', language: 'c') endif if get_option('enable-avx') and cc.has_argument('-mavx') add_project_arguments('-mavx', language: 'c') endif if get_option('enable-avx2') and cc.has_argument('-mavx2') add_project_arguments('-mavx2', language: 'c') endif add_project_arguments('-DALLOW_EXPERIMENTAL_API', language: 'c') add_project_arguments('-D_GNU_SOURCE', language: 'c') # enable extra warnings and disable any unwanted warnings warning_flags = [ '-Wno-pedantic', '-Wno-format-truncation', ] foreach arg: warning_flags if cc.has_argument(arg) add_project_arguments(arg, language: 'c') endif endforeach fgen_dep = dependency('libfgen', required: false) lua_dep = dependency('', required: false) if get_option('enable_lua') message('>>>>>>>>>>>>> Lua enabled <<<<<<<<<<<<<<') add_project_arguments('-DLUA_ENABLED', language: 'c') lua_names = ['lua', 'lua-5.3', 'lua5.3', 'lua-5.4', 'lua5.4'] foreach n:lua_names lua_dep = dependency(n, required: false) if not lua_dep.found() lua_dep = cc.find_library(n, required: false) endif if lua_dep.found() break endif endforeach if not lua_dep.found() error('unable to find Lua') endif endif if get_option('only_docs') subdir('tools') subdir('docs') else dpdk = dependency('libdpdk', required: true, method: 'pkg-config') dpdk_prefix = dpdk.get_variable('prefix') message('prefix: ' + dpdk_prefix + ' libdir: ' + get_option('libdir')) dpdk_libs_path = join_paths(dpdk_prefix, get_option('libdir')) message('DPDK lib path: ' + dpdk_libs_path) dpdk_bond = cc.find_library('rte_net_bond', dirs: [dpdk_libs_path], required: false) subdirs =['tools', 'lib', 'examples', 'app', 'docs'] foreach d:subdirs subdir(d) endforeach endif ================================================ FILE: meson_options.txt ================================================ option('enable_lua', type: 'boolean', value: false, description: 'Enable Lua support') option('enable_docs', type: 'boolean', value: false, description: 'build documentation') option('enable-avx', type: 'boolean', value: true, description: 'Try to compile with AVX support') option('enable-avx2', type: 'boolean', value: true, description: 'Try to compile with AVX2 support') option('only_docs', type: 'boolean', value: false, description: 'Only build documentation') ================================================ FILE: scripts/latency-samples.lua ================================================ -- Latency Demo script -- -- SPDX-License-Identifier: BSD-3-Clause package.path = package.path ..";?.lua;test/?.lua;app/?.lua;../?.lua" require "Pktgen"; default_pktsize = 64 default_sleeptime = 10 default_rate = 0.1 function getLatency(a) local port_stats = {} local num_lat_pkts = 0 local min_cycles = 0 local avg_cycles = 0 local max_cycles = 0 local min_us = 0 local avg_us = 0 local max_us = 0 local mbits_rx = 0 local mbits_tx = 0 if a.pktsize == 0 then a.pktsize = default_pktsize end if a.sleeptime == 0 then a.sleeptime = default_sleeptime end if a.rate < 0 then a.rate = default_rate end pktgen.set(a.sendport, "count", 0) pktgen.set(a.sendport, "rate", a.rate) pktgen.set(a.sendport, "size", a.pktsize) -- enable latency pktgen.latency(a.sendport, "enable"); pktgen.latency(a.sendport, "rate", 1000); pktgen.latency(a.sendport, "entropy", 12); pktgen.latency(a.recvport, "enable"); pktgen.latency(a.recvport, "rate", 10000); pktgen.latency(a.recvport, "entropy", 8); pktgen.delay(100); -- Start traffic pktgen.start(a.sendport) startTime = os.time() for i = 1, a.iterations, 1 do pktgen.sleep(a.sleeptime) t1 = os.difftime(os.time(), startTime) port_stats = pktgen.pktStats(a.recvport); num_lat_pkts = port_stats[tonumber(a.recvport)].latency.num_pkts num_skipped = port_stats[tonumber(a.recvport)].latency.num_skipped min_cycles = port_stats[tonumber(a.recvport)].latency.min_cycles avg_cycles = port_stats[tonumber(a.recvport)].latency.avg_cycles max_cycles = port_stats[tonumber(a.recvport)].latency.max_cycles min_us = port_stats[tonumber(a.recvport)].latency.min_us avg_us = port_stats[tonumber(a.recvport)].latency.avg_us max_us = port_stats[tonumber(a.recvport)].latency.max_us mbits_rx = pktgen.portStats(a.recvport, "rate")[tonumber(a.recvport)].mbits_rx mbits_tx = pktgen.portStats(a.sendport, "rate")[tonumber(a.sendport)].mbits_tx printf("%4d %8d %14d %14d %14d %12.2f %12.2f %12.2f %6d %6d %8d\n", t1, num_lat_pkts, min_cycles, avg_cycles, max_cycles, min_us, avg_us, max_us, mbits_rx, mbits_tx, num_skipped) end pktgen.stop(a.sendport) pktgen.latency(a.sendport, "disable"); pktgen.latency(a.recvport, "disable"); return 0 end -- pktgen.page("latency") pktgen.screen("off") printf("Latency Samples\n") printf("%4s %8s %14s %14s %14s %12s %12s %12s %6s %6s %8s\n", "time", "nbPkts", "minCycles", "avgCycles", "maxCycles", "min_us", "avg_us", "max_us", "RxMB", "TxMB", "Skipped") pktgen.sleep(2) min_latency = getLatency{ sendport=0, recvport=0, rate=0.1, pktsize=128, sleeptime=2, iterations=12 } print("Done") ================================================ FILE: scripts/latency.lua ================================================ -- Latency Demo script -- -- SPDX-License-Identifier: BSD-3-Clause package.path = package.path ..";?.lua;test/?.lua;app/?.lua;../?.lua" require "Pktgen"; local port = 2; local sleeptime = 10 pktgen.stop(port); pktgen.latency(port, "rate", 1000) -- 1000us pktgen.latency(port, "entropy", 16) -- adjust sport (sport + (index % N)) printf("Setup port %d for latency packets\n", port); pktgen.clr(); pktgen.delay(100); pktgen.latency(port, "enable"); pktgen.start(port); printf("Sleep for %d seconds\n", sleeptime); pktgen.sleep(sleeptime); printf("Sleep is done\n"); pktgen.stop(port); pktgen.latency(port, "disable"); printf("Sendport Type: %s\n", type(port)); prints("SendPort", pktgen.pktStats(port)); local port_stats = pktgen.pktStats(port); printf("Number of latency packets on port %d : %d\n", port, port_stats[port].latency.num_pkts); ================================================ FILE: scripts/port_stats_dump.lua ================================================ -- Dump portStats() including full port_stats_t (pstats) table -- -- SPDX-License-Identifier: BSD-3-Clause -- -- Usage examples: -- pktgen -f scripts/port_stats_dump.lua -- -- Optional overrides (edit in this file before running): -- PORTLIST = "0-3" package.path = package.path .. ";?.lua;test/?.lua;app/?.lua;../?.lua" require "Pktgen" PORTLIST = PORTLIST or "0-1" local function _key_to_string(k) if type(k) == "string" then return string.format("%q", k) end return tostring(k) end local function dump_table(value, indent, visited) indent = indent or "" visited = visited or {} if type(value) ~= "table" then print(indent .. tostring(value)) return end if visited[value] then print(indent .. "") return end visited[value] = true local keys = {} for k in pairs(value) do keys[#keys + 1] = k end table.sort(keys, function(a, b) local ta, tb = type(a), type(b) if ta == tb then if ta == "number" then return a < b end return tostring(a) < tostring(b) end if ta == "number" then return true end if tb == "number" then return false end return ta < tb end) print(indent .. "{") local nextIndent = indent .. " " for _, k in ipairs(keys) do local v = value[k] io.write(nextIndent .. "[" .. _key_to_string(k) .. "] = ") if type(v) == "table" then io.write("\n") dump_table(v, nextIndent, visited) else io.write(tostring(v) .. "\n") end end print(indent .. "}") visited[value] = nil end local stats = pktgen.portStats(PORTLIST) print(string.format("pktgen.portStats(%q)", PORTLIST)) dump_table(stats) ================================================ FILE: scripts/rfc2544.lua ================================================ -- RFC2544 PktGen Throughput Test -- as defined by https://www.ietf.org/rfc/rfc2544.txt -- SPDX-License-Identifier: BSD-3-Clause -- -- Improved 2x100GBE version v1.1 - Contributed by Niclas Edin 2022 -- Set 4 CPU cores per Rx:Tx function for +200MPPS performance, using Intel Ice Lake-SP platform with C810 NICs. -- Using ranges for RSS multiq support and setting multiple IP pairs for better DUT load balancing -- To avoid 0-loss test related issues, a loss tolerance value is used to get as close to good-put as possible. -- -- For C810 100GbE PPS performance and avoiding Tx degradation, MBUFS need to be increased and burst size lowered to 64 test has shown. -- Could also be a factor for other NICs. -- Also consider setting isolcpu at the kernel cmdline for best DPDK performance as per DPDK-Performance-guide documentation -- app/pktgen_constants.h: -- DEFAULT_PKT_BURST = 64, /* Increasing this number consumes memory very fast */ -- DEFAULT_RX_DESC = (DEFAULT_PKT_BURST * 16), -- DEFAULT_TX_DESC = DEFAULT_RX_DESC * 2, -- MAX_MBUFS_PER_PORT = (DEFAULT_TX_DESC * 10), /* number of buffers to support per port */ -- -- -- Load from within PktGen CLI. -- package.path = package.path ..";?.lua;test/?.lua;app/?.lua;../?.lua" require "Pktgen"; local DEBUG = true; local USE_RAMPUP = true; --Rampup traffic flow before starting measurement, less accurate measurement (trial time,drops). local SHOW_STATUS = true; -- -- Logfile for testresult local logfile = "RFC2544_throughput_results.txt"; -- define packet sizes to test local pkt_sizes = { 64, 128, 256, 512, 1024, 1280, 1518 }; -- Set max rate platform profile for PktGen to avoid crashing some interfaces during overload (XXV710/C810) -- Could be mitigated via setting MBUF as top comment describes. -- Need to be evaluated per pktgen platform setup with a loopback test before testing DUT local pktgen_limit = { [64] = { ["rate"] = 78 }, -- 68% eq 200MPPS on 2x100GbE [128] = { ["rate"] = 100 }, [256] = { ["rate"] = 100 }, [512] = { ["rate"] = 100 }, [1024] = { ["rate"] = 100 }, [1280] = { ["rate"] = 100 }, [1518] = { ["rate"] = 100 }, } -- Time in milliseconds to transmit for local duration = 10000; local confirmDuration = 60000; -- If set to 0, Confirm run is skipped. local pauseTime = 3000; local graceTime = 5000; -- Ramp Up grace time local numIterations = 20; -- Maximum number of divide n conquer iterations local retryCount = 3; -- Number of Trial retries if Tx underrun happens local initialRate = 100; -- -- Test tolerance settings local loss_tol = 0.10; local seekPrec = 0.10; -- Divide n conquer seek precision -- define the ports in use TODO: Use all ports configured in PktGen instead. local sendport = "0"; local recvport = "1"; -- Port config to use TODO: Use json config file instead. -- Flows ~ num_ip * num_udpp * 4. Max 10M flows. Requires longer RampUp time in some cases. local num_ip = 10; -- Number of IPs per interface, max 253. Set DUT IP at .254. local num_udpp = 100; -- Number of UDP ports used per IP, max 9999. local srcip = "192.168.10.1"; local max_srcip = "192.168.10."..num_ip; local dstip = "192.168.20.1"; local max_dstip = "192.168.20."..num_ip; local netmask = "/24"; local p0_udpp_src = 10000; local p0_udpp_dst = 20000; local p1_udpp_src = 30000; local p1_udpp_dst = 40000; --local p0_destmac = "b4:96:91:00:00:01"; -- Comment out to test Pktgen loopback --local p1_destmac = "b4:96:91:00:00:02"; -- Set to correct DUT MACs -- Global variables local sent = 0; local loss = 0; local loss_limit = 0; local adjust_rate = 0; local function init() if SHOW_STATUS then pktgen.screen("on"); end pktgen.page("0"); pktgen.pause("[ INFO ] Starting RFC-2544 script\n",5000); print("[NOTICE ] See test log for results: "..logfile); end printf = function(s,...) print(s:format(...)); end sprintf = function(s,...) return s:format(...) end dprint = function(...) if DEBUG then print(...); end end function Round(num, dp) local mult = 10^(dp or 0) return math.floor(num * mult + 0.5)/mult end local function setupTraffic() pktgen.reset("all"); pktgen.set("all", "burst", 64); pktgen.set_type("all", "ipv4"); pktgen.set_proto(sendport..","..recvport, "udp"); -- Set Single packet profile just for visibility in main screen pktgen.set_ipaddr(sendport, "dst", dstip); pktgen.set_ipaddr(sendport, "src", srcip..netmask); pktgen.set_ipaddr(recvport, "dst", srcip); pktgen.set_ipaddr(recvport, "src", dstip..netmask); pktgen.set(sendport, "sport", p0_udpp_src); pktgen.set(sendport, "dport", p0_udpp_dst); pktgen.set(recvport, "sport", p1_udpp_src); pktgen.set(recvport, "dport", p1_udpp_dst); if p0_destmac then pktgen.set_mac(sendport, "dst", p0_destmac); end if p1_destmac then pktgen.set_mac(recvport ,"dst", p1_destmac); end -- Set up Range configuration pktgen.range.ip_proto("all", "udp"); if p0_destmac then pktgen.range.dst_mac(sendport, "start", p0_destmac); end if p1_destmac then pktgen.range.dst_mac(recvport, "start", p1_destmac); end pktgen.range.dst_ip(sendport, "start", dstip); pktgen.range.dst_ip(sendport, "inc", "0.0.0.1"); pktgen.range.dst_ip(sendport, "min", dstip); pktgen.range.dst_ip(sendport, "max", max_dstip); pktgen.range.src_ip(sendport, "start", srcip); pktgen.range.src_ip(sendport, "inc", "0.0.0.1"); pktgen.range.src_ip(sendport, "min", srcip); pktgen.range.src_ip(sendport, "max", max_srcip); pktgen.range.dst_ip(recvport, "start", srcip); pktgen.range.dst_ip(recvport, "inc", "0.0.0.1"); pktgen.range.dst_ip(recvport, "min", srcip); pktgen.range.dst_ip(recvport, "max", max_srcip); pktgen.range.src_ip(recvport, "start",dstip); pktgen.range.src_ip(recvport, "inc", "0.0.0.1"); pktgen.range.src_ip(recvport, "min", dstip); pktgen.range.src_ip(recvport, "max", max_dstip); pktgen.range.src_port(sendport, "start", p0_udpp_src); pktgen.range.src_port(sendport, "inc", 1); pktgen.range.src_port(sendport, "min", p0_udpp_src); pktgen.range.src_port(sendport, "max", p0_udpp_src + num_udpp); pktgen.range.dst_port(sendport, "start", p0_udpp_dst); pktgen.range.dst_port(sendport, "inc", 1); pktgen.range.dst_port(sendport, "min", p0_udpp_dst); pktgen.range.dst_port(sendport, "max", p0_udpp_dst + num_udpp); pktgen.range.src_port(recvport, "start", p1_udpp_src); pktgen.range.src_port(recvport, "inc", 1); pktgen.range.src_port(recvport, "min", p1_udpp_src); pktgen.range.src_port(recvport, "max", p1_udpp_src + num_udpp); pktgen.range.dst_port(recvport, "start", p1_udpp_dst); pktgen.range.dst_port(recvport, "inc", 1); pktgen.range.dst_port(recvport, "min", p1_udpp_dst); pktgen.range.dst_port(recvport, "max", p1_udpp_dst + num_udpp); pktgen.range.ttl("all", "start", 64); pktgen.range.ttl("all", "inc", 0); pktgen.range.ttl("all", "min", 64); pktgen.range.ttl("all", "max", 64); pktgen.set_range("all", "on"); pktgen.pause("[ INFO ] Starting Test\n",2000); end local function logResult(s) print(s); file:write(s.."\n"); file:flush(); end local function getLinkSpeed() local port_link; port_link = string.match(pktgen.linkState(sendport)[tonumber(sendport)],"%d+"); return tonumber(port_link); end -- Get Max PPS for one interface local function getMaxPPS(pkt_size) local max_pps, port_link; port_link = string.match(pktgen.linkState(sendport)[tonumber(sendport)],"%d+"); max_pps = Round((port_link * 10^6)/((pkt_size + 20) * 8)); return max_pps; end local function getDuration(pkt_size, set_rate, num_pkts) local test_dur, max_pps; max_pps = getMaxPPS(pkt_size) * 2; --Bidir test_dur = num_pkts / (max_pps * (set_rate/100)); return(test_dur * 1000); end local function checkRate(pkt_size, set_rate, duration, num_pkts) local calc_pps, max_pps, test_rate, calc_duration, rate_ok = false; -- Verify that actual duration is not much lower than set duration -- This is when PktGen is unable to produce Tx Rate. -- Otherwize use the actual duration to calculate test rate calc_duration = getDuration(pkt_size, set_rate, num_pkts); if calc_duration > (duration - 0.1) then duration = calc_duration; end max_pps = (getMaxPPS(pkt_size) * 2); --x2 for bidir calc_pps = (num_pkts / ((duration) / 1000)); test_rate = Round(((calc_pps / max_pps)*100),3); set_rate = Round(set_rate, 3); dprint("[ DEBUG ] Link:"..(getLinkSpeed()//1000).."GBE | Max MPPS:"..Round(max_pps/10^6,2).." | Test MPPS:"..Round(calc_pps/10^6,2).." | Dur: "..duration.." | Calc Rate:"..test_rate.." < "..set_rate..":Set Rate"); if (test_rate - set_rate) < -0.1 then logResult("[WARNING] Set rate "..set_rate.." is not reached ("..test_rate.." < "..set_rate.."), PktGen is unable to reach Tx target"); adjust_rate = test_rate; rate_ok = false; else adjust_rate = set_rate; rate_ok = true; end return(rate_ok); end local function getTxPkts(set_rate, pkt_size, test_dur) local max_pps, num_pkts; max_pps = getMaxPPS(pkt_size); num_pkts = Round((max_pps * (set_rate/100))*(test_dur/1000)); dprint("[ DEBUG ] getTxPkts Rate: "..set_rate.."% | Num Pkts:"..num_pkts.." | Duration:"..test_dur/1000); return num_pkts; end local function getPPS(num_pkts, test_dur) local test_pps; test_pps = Round(num_pkts / (test_dur/1000)); return test_pps; end local function getMbpsL2(num_pkts, pkt_size, test_dur) local test_mbps, test_pps; test_pps = getPPS(num_pkts, test_dur); test_mbps = Round((test_pps * (pkt_size * 8))/10^6,3); return test_mbps; end local function getMbpsL1(num_pkts, pkt_size, test_dur) local test_mbps, test_pps; test_pps = getPPS(num_pkts, test_dur); test_mbps = Round((test_pps * ((pkt_size + 20)* 8))/10^6,3); return test_mbps; end local function printLine() print("-------------------------------------------------------------------------------------------------------------"); file:write("--------------------------------------------------------------------------------------------------------------\n"); file:flush(); end local function runTrial(pkt_size, rate, duration, count, retry) local num_tx, num_tx1, num_rx, num_dropped, tx_mpps, rx_mpps, tx_gbps1, rx_gbps1, tx_gbps2, rx_gbps2, rate_notif = " "; pktgen.clr(); if count == 1 and retry == 1 then pktgen.page("range"); end pktgen.set("all", "rate", rate); pktgen.set("all", "size", pkt_size); pktgen.range.pkt_size("all", "start", pkt_size); pktgen.range.pkt_size("all", "inc", 0); pktgen.range.pkt_size("all", "min", pkt_size); pktgen.range.pkt_size("all", "max", pkt_size); if count == 1 and retry == 1 then pktgen.pause("[ INFO ] IP Range , setting "..pkt_size.."B\n",5000); pktgen.page("0"); end printLine(); print("[ INFO ] Starting Test..."); -- Use RAMPUP with graceTime seconds of traffic before test or just send exact number of packets if USE_RAMPUP then pktgen.set("all", "count", 0); pktgen.start("all"); -- Bidir print("[ INFO ] Ramping up Flows..."); pktgen.delay(graceTime); pktgen.clr(); else pktgen.set("all", "count", getTxPkts(rate, pkt_size, (duration))); pktgen.start("all"); -- Bidir pktgen.delay(250); -- Extra time to account for start stop events end pktgen.delay(duration); pktgen.stop("all"); pktgen.delay(pauseTime); pktgen.pause("[ INFO ] Waiting for idling packets...\n",2000); statTx = pktgen.portStats(sendport, "port")[tonumber(sendport)]; statRx = pktgen.portStats(recvport, "port")[tonumber(recvport)]; -- Check that port Tx opackets are within reason. -- This happens if one interface LAGs behind, will result in Tx underruns in iterations. -- Included headroom for RAMP-UP mode as it is always lagging for one interface when test stops num_tx = statTx.opackets; num_tx1 = statRx.opackets; if (num_tx + 5000) < num_tx1 then print("[WARNING] Port:"..sendport.." Tx underruns.Results might be inconclusive.("..Round(num_tx - num_tx1)..")"); dprint("[ DEBUG ] CPU probably overloaded."); elseif (num_tx1 + 5000) < num_tx then print("[WARNING] Port:"..recvport.." Tx underruns. Results might be inconclusive.("..Round(num_tx1 - num_tx)..")"); dprint("[ DEBUG ] CPU probably overloaded."); else dprint("[ DEBUG ] No Tx underruns are detected."); end num_tx = num_tx + num_tx1; num_rx = statRx.ipackets + statTx.ipackets; num_dropped = num_tx - num_rx; sent = num_tx; loss = Round((1-(num_rx/num_tx))*100,3); loss_limit = Round(sent*(loss_tol/100)); -- checkRate: Validate PktGen Tx rate and duration to get adjusted rate if tx underruns happens if checkRate(pkt_size, rate, duration, num_tx) then rate_notif = " "; else print("[NOTICE ] Actual Rate is: "..adjust_rate.."%"); rate = adjust_rate; rate_notif = "!"; -- Mark PktGen Max rate in table end duration = getDuration(pkt_size, rate, num_tx); tx_mpps = getPPS(num_tx, duration)/10^6; rx_mpps = getPPS(num_rx, duration)/10^6; tx_gbps1 = getMbpsL1(num_tx, pkt_size, duration)/1000; rx_gbps1 = getMbpsL1(num_rx, pkt_size, duration)/1000; tx_gbps2 = getMbpsL2(num_tx, pkt_size, duration)/1000; rx_gbps2 = getMbpsL2(num_rx, pkt_size, duration)/1000; logResult(sprintf("[ %4dB ] Trial %5s.%1d | Rate:%s %-6.2f%% | Pkt Size: %4dB | Dur.: %3.2fs | Loss Tol.: %-5.3f%%", pkt_size, tostring(count), retry, rate_notif, rate, pkt_size, Round(duration/1000,3), loss_tol)); logResult(sprintf("[ %4dB ] Tx: %-11d | Rx: %-11d | Drop: %-13d (%-6.3f%%) | Loss Lim.: %-10.0f", pkt_size, num_tx, num_rx, num_dropped, loss, loss_limit)); logResult(sprintf("[ %4dB ] MPPS Tx: %-06.2f | MPPS Rx: %-06.2f | Gbps Tx L1/L2: %-6.2f / %-6.2f | Gbps Rx L1/L2: %-6.2f / %-6.2f", pkt_size, tx_mpps, rx_mpps, tx_gbps1, tx_gbps2, rx_gbps1, rx_gbps2)); pktgen.delay(pauseTime); return num_dropped; end local function runThroughputTest(pkt_size) local num_dropped, max_rate, min_rate, trial_rate, max_min_diff, no_adjust = true; max_rate = pktgen_limit[pkt_size].rate; min_rate = 1; trial_rate = initialRate; if trial_rate > max_rate then trial_rate = max_rate; end for count=1, numIterations, 1 do for retry=1, retryCount, 1 do num_dropped = runTrial(pkt_size, trial_rate, duration, count, retry); if (adjust_rate + 0.1) >= trial_rate then break; else -- If adjusted Tx and at 100% whilst packet drops, -- DUT is the bottleneck so no retries is needed. if trial_rate == 100 and num_dropped > loss_limit then break; end end if retry < retryCount then dprint("[ DEBUG ] Retrying Trial..."); pktgen.delay(pauseTime); end end if (adjust_rate + 0.1) < trial_rate then trial_rate = adjust_rate; max_rate = trial_rate; no_adjust = false; else no_adjust = true; end if num_dropped <= loss_limit then -- Undershoot if no_adjust then min_rate = trial_rate; end --if min_rate == pktgen_limit[pkt_size].rate then break; end if min_rate == max_rate then break; end trial_rate = min_rate + 1 + ((max_rate - min_rate)/2); -- Add +1 to get to 100% if initial rate is set to a lower value. if trial_rate > max_rate then trial_rate = max_rate; end else -- Overshoot max_rate = trial_rate; if count > 2 then trial_rate = max_rate - ((max_rate - min_rate)/2); else -- First 2 iterations trying to do rapid find by using loss% to set trial rate trial_rate = trial_rate - ((trial_rate/100)*loss); end end max_min_diff = max_rate - min_rate; dprint("[ DEBUG ] Max - Min rate diff: "..Round(max_min_diff,5)); if max_min_diff <= seekPrec then dprint("[ DEBUG ] Stopping seek iterations, reached seek Precision limit"); break; end end if confirmDuration > 0 then -- Ensure we test confirmation run with the last succesfull drop rate trial_rate = min_rate; -- confirm throughput rate for at least 60 seconds num_dropped = runTrial(pkt_size, trial_rate, confirmDuration, "Final", 0); printLine(); if num_dropped <= loss_limit then print("[RESULT ] Max rate for packet size " .. pkt_size .. "B is: " .. adjust_rate.."%"); file:write("[RESULT ] Max rate for packet size " .. pkt_size .. "B is: " .. adjust_rate .. "%\n\n"); else print("[WARNING] Max rate of " .. trial_rate .. "% could not be confirmed for 60 seconds as required by rfc2544."); file:write("[WARNING] Max rate of " .. trial_rate .. "% could not be confirmed for 60 seconds as required by rfc2544." .. "\n\n"); end file:flush(); end pktgen.delay(pauseTime); end function main() local dummy; file = io.open(logfile, "w"); setupTraffic(); pktgen.pause("[ INFO ] Priming DUT...\n",1000); dummy = runTrial(512, 1, 5000, "Prime",0); file:write("\n"); if dummy >= (sent * loss_tol) then pktgen.pause("[WARNING] DUT seem to be dropping packets, stopping test.\n",1000); else pktgen.pause("[ INFO ] Starting Trial Runs ...\n",3000); for _,size in pairs(pkt_sizes) do runThroughputTest(size); end end printLine(); file:close(); print("[NOTICE ] See test log for results: "..logfile); end init(); main(); ================================================ FILE: scripts/rfc2544_tput_test.lua ================================================ -- RFC2544 Throughput Test -- as defined by https://www.ietf.org/rfc/rfc2544.txt -- SPDX-License-Identifier: BSD-3-Clause package.path = package.path ..";?.lua;test/?.lua;app/?.lua;../?.lua" require "Pktgen"; -- define packet sizes to test local pkt_sizes = { 64, 128, 256, 512, 1024, 1280, 1518 }; -- Time in seconds to transmit for local duration = 10000; local confirmDuration = 60000; local pauseTime = 1000; -- define the ports in use local sendport = "0"; local recvport = "1"; -- ip addresses to use local dstip = "90.90.90.90"; local srcip = "1.1.1.1"; local netmask = "/24"; local initialRate = 50 ; local function setupTraffic() pktgen.set_ipaddr(sendport, "dst", dstip); pktgen.set_ipaddr(sendport, "src", srcip..netmask); pktgen.set_ipaddr(recvport, "dst", srcip); pktgen.set_ipaddr(recvport, "src", dstip..netmask); pktgen.set_proto(sendport..","..recvport, "udp"); -- set Pktgen to send continuous stream of traffic pktgen.set(sendport, "count", 0); end local function runTrial(pkt_size, rate, duration, count) local num_tx, num_rx, num_dropped; pktgen.clr(); pktgen.set(sendport, "rate", rate); pktgen.set(sendport, "size", pkt_size); pktgen.start(sendport); print("Running trial " .. count .. ". % Rate: " .. rate .. ". Packet Size: " .. pkt_size .. ". Duration (mS):" .. duration); file:write("Running trial " .. count .. ". % Rate: " .. rate .. ". Packet Size: " .. pkt_size .. ". Duration (mS):" .. duration .. "\n"); pktgen.delay(duration); pktgen.stop(sendport); pktgen.delay(pauseTime); statTx = pktgen.portStats(sendport, "port")[tonumber(sendport)]; statRx = pktgen.portStats(recvport, "port")[tonumber(recvport)]; num_tx = statTx.opackets; num_rx = statRx.ipackets; num_dropped = num_tx - num_rx; print("Tx: " .. num_tx .. ". Rx: " .. num_rx .. ". Dropped: " .. num_dropped); file:write("Tx: " .. num_tx .. ". Rx: " .. num_rx .. ". Dropped: " .. num_dropped .. "\n"); pktgen.delay(pauseTime); return num_dropped; end local function runThroughputTest(pkt_size) local num_dropped, max_rate, min_rate, trial_rate; max_rate = 100; min_rate = 1; trial_rate = initialRate; for count=1, 10, 1 do num_dropped = runTrial(pkt_size, trial_rate, duration, count); if num_dropped == 0 then min_rate = trial_rate; else max_rate = trial_rate; end trial_rate = min_rate + ((max_rate - min_rate)/2); end -- Ensure we test confirmation run with the last succesfull zero-drop rate trial_rate = min_rate; -- confirm throughput rate for at least 60 seconds num_dropped = runTrial(pkt_size, trial_rate, confirmDuration, "Confirmation"); if num_dropped == 0 then print("Max rate for packet size " .. pkt_size .. "B is: " .. trial_rate); file:write("Max rate for packet size " .. pkt_size .. "B is: " .. trial_rate .. "\n\n"); else print("Max rate of " .. trial_rate .. "% could not be confirmed for 60 seconds as required by rfc2544."); file:write("Max rate of " .. trial_rate .. "% could not be confirmed for 60 seconds as required by rfc2544." .. "\n\n"); end end function main() file = io.open("RFC2544_throughput_results.txt", "w"); setupTraffic(); for _,size in pairs(pkt_sizes) do runThroughputTest(size); end file:close(); end main(); ================================================ FILE: scripts/traffic-profile.lua ================================================ -- CGCS Demo script -- -- SPDX-License-Identifier: BSD-3-Clause package.path = package.path ..";?.lua;test/?.lua;app/?.lua;../?.lua" require "Pktgen"; local time_step = 10; -- seconds local pcnt_rate = { 10, 20, 40, 60, 60, 60, 60, 60, 60, 80, 80, 80, 80, 80, 70, 70, 70, 70, 60, 60, 60, 40, 40, 70, 70, 80, 80, 40, 40, 40 }; sendport = 0; recvport = 0; pkt_size = 64; local dstip = "10.10.0.100"; local srcip = "10.10.0.101"; local netmask = "/24"; total_time = 0; -- Take two lists and create one table with a merged value of the tables. -- Return a set or table = { { timo, rate }, ... } function Set(step, list) local set = { }; -- Must have a empty set first. for i,v in ipairs(list) do set[i] = { timo = step, rate = v }; end return set; end function main() local sending = 0; local trlst = Set(time_step, pcnt_rate); -- Stop the port sending and reset to pktgen.stop(sendport); sleep(2); -- Wait for stop to happen (not really needed) -- Set up the default packet size fixed value for now. pktgen.set(sendport, "size", pkt_size); pktgen.set_ipaddr(sendport, "dst", dstip); pktgen.set_ipaddr(sendport, "src", srcip..netmask); pktgen.set_proto(sendport..","..recvport, "udp"); total_time = 0; -- v is the table to values created by the Set(x,y) function for _,v in pairs(trlst) do printf(" Percent load %d for %d seconds\n", v.rate, v.timo); -- Set the rate to the new value pktgen.set(sendport, "rate", v.rate); -- If not sending packets start sending them if ( sending == 0 ) then pktgen.start(sendport); sending = 1; end -- Sleep until we need to move to the next rate and timeout sleep(v.timo); total_time = total_time + v.timo; end -- Stop the port and do some cleanup pktgen.stop(sendport); sending = 0; end printf("\n**** Traffic Profile Rate for %d byte packets ***\n", pkt_size); main(); printf("\n*** Traffic Profile Done (Total Time %d) ***\n", total_time); ================================================ FILE: style/call_GNU_Indent.pro ================================================ -kr -bad -bap -sob -ut -i8 ================================================ FILE: style/call_GNU_Indent.sh ================================================ #!/bin/sh if [ ! -n "$1" ]; then echo "Syntax is: recurse.sh dirname filesuffix" echo "Syntax is: recurse.sh filename" echo "Example: recurse.sh temp cpp" exit 1 fi if [ -d "$1" ]; then #echo "Dir ${1} exists" if [ -n "$2" ]; then filesuffix=$2 else filesuffix="*" fi #echo "Filtering files using suffix ${filesuffix}" file_list=`find ${1} -name "*.${filesuffix}" -type f` for file2indent in $file_list do echo "Indenting file $file2indent" #!/bin/bash indent "$file2indent" -o indentoutput.tmp mv indentoutput.tmp "$file2indent" done else if [ -f "$1" ]; then echo "Indenting one file $1" #!/bin/bash indent "$1" -o indentoutput.tmp mv indentoutput.tmp "$1" else echo "ERROR: As parameter given directory or file does not exist!" echo "Syntax is: call_GNU_Indent.sh dirname filesuffix" echo "Syntax is: call_GNU_Indent.sh filename" echo "Example: call_GNU_Indent.sh temp cpp" exit 1 fi fi ================================================ FILE: style/call_Uncrustify.cfg ================================================ tok_split_gte=false utf8_byte=false utf8_force=false indent_cmt_with_tabs=false indent_align_string=false indent_braces=false indent_braces_no_func=false indent_braces_no_class=false indent_braces_no_struct=false indent_brace_parent=false indent_namespace=false indent_extern=false indent_class=false indent_class_colon=false indent_else_if=false indent_var_def_cont=false indent_func_call_param=false indent_func_def_param=false indent_func_proto_param=false indent_func_class_param=false indent_func_ctor_var_param=false indent_template_param=false indent_func_param_double=false indent_relative_single_line_comments=false indent_col1_comment=false indent_access_spec_body=false indent_paren_nl=false indent_comma_paren=false indent_bool_paren=false indent_first_bool_expr=false indent_square_nl=false indent_preserve_sql=false indent_align_assign=true sp_balance_nested_parens=false align_keep_tabs=false align_with_tabs=false align_on_tabstop=false align_number_left=false align_func_params=false align_same_func_call_params=false align_var_def_colon=false align_var_def_attribute=false align_var_def_inline=false align_right_cmt_mix=false align_on_operator=false align_mix_var_proto=false align_single_line_func=false align_single_line_brace=false align_nl_cont=false align_left_shift=true align_oc_decl_colon=false nl_collapse_empty_body=false nl_assign_leave_one_liners=false nl_class_leave_one_liners=false nl_enum_leave_one_liners=false nl_getset_leave_one_liners=false nl_func_leave_one_liners=false nl_if_leave_one_liners=false nl_multi_line_cond=false nl_multi_line_define=false nl_before_case=false nl_after_case=false nl_after_return=false nl_after_semicolon=true nl_after_brace_open=false nl_after_brace_open_cmt=false nl_after_vbrace_open=false nl_after_vbrace_open_empty=false nl_after_brace_close=false nl_after_vbrace_close=false nl_define_macro=false nl_squeeze_ifdef=false nl_ds_struct_enum_cmt=false nl_ds_struct_enum_close_brace=false nl_create_if_one_liner=false nl_create_for_one_liner=false nl_create_while_one_liner=false ls_for_split_full=false ls_func_split_full=false nl_after_multiline_comment=false eat_blanks_after_open_brace=true eat_blanks_before_close_brace=true mod_full_brace_if_chain=false mod_pawn_semicolon=false mod_full_paren_if_bool=false mod_remove_extra_semicolon=true mod_sort_import=false mod_sort_using=false mod_sort_include=false mod_move_case_break=false mod_remove_empty_return=false cmt_indent_multi=true cmt_c_group=false cmt_c_nl_start=false cmt_c_nl_end=false cmt_cpp_group=false cmt_cpp_nl_start=false cmt_cpp_nl_end=false cmt_cpp_to_c=false cmt_star_cont=false cmt_multi_check_last=true cmt_insert_before_preproc=false pp_indent_at_level=false pp_region_indent_code=false pp_if_indent_code=false pp_define_at_level=false input_tab_size=8 output_tab_size=8 nl_func_var_def_blk=1 code_width=80 cmt_width=80 cmt_reflow_mode=2 indent_with_tabs=1 sp_arith=force sp_assign=force sp_assign_default=force sp_enum_assign=force sp_pp_concat=force sp_pp_stringify=remove sp_bool=force sp_compare=force sp_inside_paren=remove sp_paren_paren=remove sp_paren_brace=force sp_before_ptr_star=force sp_between_ptr_star=remove sp_after_ptr_star=remove sp_after_ptr_star_func=remove sp_before_ptr_star_func=remove sp_before_byref=remove sp_before_sparen=force sp_inside_sparen=remove sp_after_semi_for_empty=ignore sp_before_square=remove sp_before_squares=remove sp_inside_square=remove sp_after_comma=add sp_before_ellipsis=add sp_after_operator=add sp_after_operator_sym=remove sp_sizeof_paren=remove sp_func_proto_paren=remove sp_func_def_paren=remove sp_inside_fparen=remove sp_fparen_brace=remove sp_func_call_paren=remove sp_attribute_paren=add sp_defined_paren=remove sp_else_brace=add sp_brace_else=add sp_brace_typedef=add sp_not=remove sp_inv=remove sp_addr=remove sp_member=remove sp_deref=remove sp_sign=remove sp_incdec=remove sp_cond_colon=add sp_cond_question=add nl_fdef_brace=add mod_full_brace_do=remove mod_full_brace_for=remove mod_full_brace_if=remove mod_full_brace_while=remove mod_paren_on_return=remove ================================================ FILE: style/call_Uncrustify.sh ================================================ #!/bin/sh if [ ! -n "$1" ]; then echo "Syntax is: recurse.sh dirname filesuffix" echo "Syntax is: recurse.sh filename" echo "Example: recurse.sh temp cpp" exit 1 fi if [ -d "$1" ]; then #echo "Dir ${1} exists" if [ -n "$2" ]; then filesuffix=$2 else filesuffix="*" fi #echo "Filtering files using suffix ${filesuffix}" file_list=`find ${1} -name "*.${filesuffix}" -type f` for file2indent in $file_list do echo "Indenting file $file2indent" #!/bin/bash uncrustify -f "$file2indent" -c "./call_Uncrustify.cfg" -o indentoutput.tmp mv indentoutput.tmp "$file2indent" done else if [ -f "$1" ]; then echo "Indenting one file $1" #!/bin/bash uncrustify -f "$1" -c "./call_Uncrustify.cfg" -o indentoutput.tmp mv indentoutput.tmp "$1" else echo "ERROR: As parameter given directory or file does not exist!" echo "Syntax is: call_Uncrustify.sh dirname filesuffix" echo "Syntax is: call_Uncrustify.sh filename" echo "Example: call_Uncrustify.sh temp cpp" exit 1 fi fi ================================================ FILE: test/dump.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" pktgen.screen("off"); printf("Lua Version : %s\n", pktgen.info.Lua_Version); printf("Pktgen Version : %s\n", pktgen.info.Pktgen_Version); printf("Pktgen Copyright : %s\n", pktgen.info.Pktgen_Copyright); printf("Pktgen Authors : %s\n", pktgen.info.Pktgen_Authors); prints(); ================================================ FILE: test/generate-sequences.sh ================================================ #!/bin/bash # generate-flows.sh # quick and dirty script to generate flows based on the below settings. # Right now assumes just two ports. # # Contributed by gkenaley #TODO -- # Could be improved to do multi-port and parameterized. Maybe prompt # for the information # #For Port 0/1 DEST_MAC=fa:16:3e:b9:de:22 SRC_MAC=fa:16:3e:92:9c:03 SRC_IP=192.168.30.4 DEST_IP=192.168.30.50 VLAN=1 IPMODE=ipv4 PROT=udp PKTSIZE=512 FLOWS=10 #IP Settings below may not be required unless using tcp/ip if [[ "${PROT,,}" != "udp" ]]; then echo "set ip dst 0 $SRC_IP" echo "set ip src 0 $DEST_IP/24" echo "set ip dst 1 $DEST_IP" echo "set ip src 1 $SRC_IP/24" fi echo "set 0,1 size $PKTSIZE" echo "set 0,1 rate 1" #port 0 for flow in `seq 0 $(($FLOWS - 1))` do # #seq dst-Mac src-Mac dst-IP src-IP/nm sport dport ipv4|ipv6 udp|tcp|icmp vid pktsize # echo "seq $flow 0 $DEST_MAC $SRC_MAC $DEST_IP $SRC_IP/24 $((1000 + $flow)) $((2000 + $flow)) $IPMODE $PROT $VLAN $PKTSIZE" #todo maybe add changing something other than port done #port 1 for flow in `seq 0 $(($FLOWS - 1))` do echo "seq $flow 1 $SRC_MAC $DEST_MAC $SRC_IP $DEST_IP/24 $((1000+$flow)) $((2000+$flow)) $IPMODE $PROT $VLAN $PKTSIZE" #todo maybe add changing something other than port done echo "set 0,1 seqCnt $FLOWS" ================================================ FILE: test/gtpu-range.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" -- A list of the test script for Pktgen and Lua. -- Each command somewhat mirrors the pktgen command line versions. -- A couple of the arguments have be changed to be more like the others. -- local function doWait(port, waitTime) local idx; pktgen.delay(1000); if ( waitTime == 0 ) then return; end waitTime = waitTime - 1; -- Try to wait for the total number of packets to be sent. local idx = 0; while( idx < waitTime ) do idx = idx + 1; local sending = pktgen.isSending(port); if ( sending[tonumber(port)] == "n" ) then break; end pktgen.delay(1000); end end pktgen.reset("all"); -- 'set' commands for a number of per port values pktgen.set("all", "rate", 100); pktgen.clear("all"); pktgen.cls(); pktgen.pause("Do range commands\n", 1000); pktgen.page("range"); pktgen.dst_mac("all", "start", "0000:0100:0600"); pktgen.src_mac("all", "start", "0000:0100:0700"); pktgen.delay(1000); pktgen.range.dst_ip("all", "start", "10.10.10.1"); pktgen.range.dst_ip("all", "inc", "0.0.0.1"); pktgen.range.dst_ip("all", "min", "10.10.10.1"); pktgen.range.dst_ip("all", "max", "10.10.10.4"); pktgen.delay(1000); pktgen.range.src_ip("all", "start", "20.20.20.1"); pktgen.range.src_ip("all", "inc", "0.0.0.1"); pktgen.range.src_ip("all", "min", "20.20.20.1"); pktgen.range.src_ip("all", "max", "20.20.20.8"); pktgen.set_type("all", "ipv4"); pktgen.set_proto("all", "udp"); -- GTPU on UDP port 2152 pktgen.delay(1000); pktgen.range.dst_port("all", "start", 2152); pktgen.range.dst_port("all", "inc", 0); pktgen.range.dst_port("all", "min", 2152); pktgen.range.dst_port("all", "max", 2152); pktgen.delay(1000); pktgen.range.src_port("all", "start", 1234); pktgen.range.src_port("all", "inc", 1); pktgen.range.src_port("all", "min", 1234); pktgen.range.src_port("all", "max", 1244); pktgen.delay(1000); pktgen.range.gtpu_teid("all", "start", 100); pktgen.range.gtpu_teid("all", "inc", 1); pktgen.range.gtpu_teid("all", "min", 100); pktgen.range.gtpu_teid("all", "max", 200); pktgen.delay(1000); pktgen.range.vlan_id("all", "start", 100); pktgen.range.vlan_id("all", "inc", 1); pktgen.range.vlan_id("all", "min", 100); pktgen.range.vlan_id("all", "max", 150); pktgen.delay(1000); pktgen.range.pkt_size("all", "start", 150); pktgen.range.pkt_size("all", "inc", 5); pktgen.range.pkt_size("all", "min", 150); pktgen.range.pkt_size("all", "max", 200); pktgen.set_range("all", "on"); pktgen.vlan("all", "on"); pktgen.pause("Wait a second, then go back to main page\n", 1000); pktgen.page("0"); --pktgen.pause("About to do range\n", 2000); pktgen.pause("Wait a second, then go back to main page\n", 1000); pktgen.page("0"); --pktgen.pause("About to do range\n", 2000); pktgen.start("all"); doWait("all", 1); --printf("Done\n"); ================================================ FILE: test/hello-world.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" printf("Lua Version : %s\n", pktgen.info.Lua_Version); printf("Pktgen Version : %s\n", pktgen.info.Pktgen_Version); printf("Pktgen Copyright : %s\n", pktgen.info.Pktgen_Copyright); printf("Pktgen Authors : %s\n", pktgen.info.Pktgen_Authors); printf("\nHello World!!!!\n"); ================================================ FILE: test/info.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" -- A list of the test script for Pktgen and Lua. -- Each command somewhat mirrors the pktgen command line versions. -- A couple of the arguments have be changed to be more like the others. -- function info() printf("Lua Version : %s\n", pktgen.info.Lua_Version); printf("Pktgen Version : %s\n", pktgen.info.Pktgen_Version); printf("Pktgen Copyright : %s\n", pktgen.info.Pktgen_Copyright); prints("pktgen.info", pktgen.info); printf("Port Count %d\n", pktgen.portCount()); printf("Total port Count %d\n", pktgen.totalPorts()); end info() ================================================ FILE: test/mac.pkt.3.4.ip-range-gen.txt ================================================ # mac.pkt # # Contributed by gkenaley # /home/demo/Projects/pktgen-2.8.6/pktgen -c 0xee --initial-lcore 1 -n 2 -m 1024 --proc-type auto --file-prefix pg -- -p 0x6 -m "[6:3].0,[7:2].1" -f ./mac.pkt.3.4.ip-range-gen # OR #./app/app/${target}/pktgen -c 0xff --initial-lcore 1 -n 4 -m 1024 --proc-type auto --file-prefix pg -- -P -T -p 0x3 -m "[6:3].0,[7:2].1" -f ./themes/black-yellow.theme -f ./mac.pkt.3.4.ip-range-gen # seq dst-Mac src-Mac dst-IP src-IP/nm sport dport ipv4|ipv6 udp|tcp|icmp vid pktsize set 0,1 type vlan set 0 vlan 623 set 1 vlan 570 enable 0,1 vlan set 0,1 proto udp set 0,1 rate 1 set 0,1 size 512 # Here is the new and improved (pktgen 3.4+) syntax for generating a range of flows with varying IP src/dst set 0 dst ip 192.168.20.3 range 0 dst ip 192.168.20.3 192.168.20.3 192.168.20.50 0.0.0.1 set 0 src ip 192.168.30.3/24 range 0 src ip 192.168.30.3 192.168.30.3 192.168.30.50 0.0.0.1 set 0 dst ip 192.168.30.3 range 0 dst ip 192.168.30.3 192.168.30.3 192.168.30.50 0.0.0.1 set 0 src ip 192.168.20.3/24 range 0 src ip 192.168.20.3 192.168.20.3 192.168.20.50 0.0.0.1 enable 0,1 process enable 0,1 garp start 0,1 arp gratuitous delay 5000 stop 0,1 disable 0,1 garp start 0,1 # Ramp up throughput over 10 seconds, as is commonly done on Ixia/Spirent # Percentage rates are apparently relative to 40G on 40G capable cards (even 4x10G) # Note that some switches require up to 30 seconds to learn before bi-directional traffic starts! set 0,1 rate 5 delay 3000 set 0,1 rate 10 delay 3000 set 0,1 rate 15 delay 3000 set 0,1 rate 16 ================================================ FILE: test/mac.pkt.sequences.txt ================================================ # mac.pkt # ------- # Note that DPDK cannot use default Nehalem cpu flavor extra spec, because it needs special SSE instructions # # Contributed by gkenaley # # /home/demo/Projects/pktgen-2.8.6/pktgen -c 0xee --initial-lcore 1 -n 2 -m 1024 --proc-type auto --file-prefix pg -- -p 0x6 -m "[6:3].0,[7:2].1" -f ./mac.pkt.sequences # OR #./app/app/${target}/pktgen -c 0xff --initial-lcore 1 -n 4 -m 1024 --proc-type auto --file-prefix pg -- -P -T -p 0x3 -m "[6:3].0,[7:2].1" -f ./themes/black-yellow.theme -f ./mac.pkt.sequences # seq dst-Mac src-Mac dst-IP src-IP/nm sport dport ipv4|ipv6 udp|tcp|icmp vid pktsize vlanid 0 681 vlanid 1 581 vlan 0,1 on proto udp 0,1 # Packet size is the critical variable set 0,1 size 512 set 0,1 rate 1 # see http://www.subnet-calculator.com/ seq 0 0 fa:16:3e:b3:aa:f5 fa:16:3e:d2:a6:b1 192.168.20.3 192.168.30.3/24 2040 2040 ipv4 udp 681 512 seq 1 0 fa:16:3e:b3:aa:f5 fa:16:3e:d2:a6:b1 192.168.20.3 192.168.30.3/24 2041 2041 ipv4 udp 681 512 seq 2 0 fa:16:3e:b3:aa:f5 fa:16:3e:d2:a6:b1 192.168.20.3 192.168.30.3/24 2042 2042 ipv4 udp 681 512 seq 3 0 fa:16:3e:b3:aa:f5 fa:16:3e:d2:a6:b1 192.168.20.3 192.168.30.3/24 2043 2043 ipv4 udp 681 512 seq 0 0 fa:16:3e:b3:aa:f5 fa:16:3e:d2:a6:b1 192.168.20.3 192.168.30.3/24 2044 2044 ipv4 udp 681 512 seq 5 0 fa:16:3e:b3:aa:f5 fa:16:3e:d2:a6:b1 192.168.20.3 192.168.30.3/24 2045 2045 ipv4 udp 681 512 seq 6 0 fa:16:3e:b3:aa:f5 fa:16:3e:d2:a6:b1 192.168.20.3 192.168.30.3/24 2046 2046 ipv4 udp 681 512 seq 7 0 fa:16:3e:b3:aa:f5 fa:16:3e:d2:a6:b1 192.168.20.3 192.168.30.3/24 2047 2047 ipv4 udp 681 512 seq 8 0 fa:16:3e:b3:aa:f5 fa:16:3e:d2:a6:b1 192.168.20.3 192.168.30.3/24 2048 2048 ipv4 udp 681 512 seq 9 0 fa:16:3e:b3:aa:f5 fa:16:3e:d2:a6:b1 192.168.20.3 192.168.30.3/24 2049 2049 ipv4 udp 681 512 seq 0 1 fa:16:3e:d2:a6:b1 fa:16:3e:b3:aa:f5 192.168.30.3 192.168.20.3/24 3040 3040 ipv4 udp 581 512 seq 1 1 fa:16:3e:d2:a6:b1 fa:16:3e:b3:aa:f5 192.168.30.3 192.168.20.3/24 3041 3041 ipv4 udp 581 512 seq 2 1 fa:16:3e:d2:a6:b1 fa:16:3e:b3:aa:f5 192.168.30.3 192.168.20.3/24 3042 3042 ipv4 udp 581 512 seq 3 1 fa:16:3e:d2:a6:b1 fa:16:3e:b3:aa:f5 192.168.30.3 192.168.20.3/24 3043 3043 ipv4 udp 581 512 seq 4 1 fa:16:3e:d2:a6:b1 fa:16:3e:b3:aa:f5 192.168.30.3 192.168.20.3/24 3044 3044 ipv4 udp 581 512 seq 5 1 fa:16:3e:d2:a6:b1 fa:16:3e:b3:aa:f5 192.168.30.3 192.168.20.3/24 3045 3045 ipv4 udp 581 512 seq 6 1 fa:16:3e:d2:a6:b1 fa:16:3e:b3:aa:f5 192.168.30.3 192.168.20.3/24 3046 3046 ipv4 udp 581 512 seq 7 1 fa:16:3e:d2:a6:b1 fa:16:3e:b3:aa:f5 192.168.30.3 192.168.20.3/24 3047 3047 ipv4 udp 581 512 seq 8 1 fa:16:3e:d2:a6:b1 fa:16:3e:b3:aa:f5 192.168.30.3 192.168.20.3/24 3048 3048 ipv4 udp 581 512 seq 9 1 fa:16:3e:d2:a6:b1 fa:16:3e:b3:aa:f5 192.168.30.3 192.168.20.3/24 3049 3049 ipv4 udp 581 512 process 0,1 enable delay 2000 #The next line is what activates sequences. If there are no sequences above, no traffic occurs (unless the next line is commented out). set 0,1 seqCnt 10 start 0,1 # Ramp up throughput over 10 seconds, as is commonly done on Ixia/Spirent # Percentage rates are apparently relative to 40G when the NIC is 40G capable. # Note that some switches require up to 30 seconds to learn before bi-directional traffic starts! delay 5000 set 0,1 rate 5 delay 3000 set 0,1 rate 10 delay 3000 set 0,1 rate 13 delay 3000 set 0,1 rate 20 delay 3000 set 0,1 rate 23 ================================================ FILE: test/main.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" -- A list of the test script for Pktgen and Lua. -- Each command somewhat mirrors the pktgen command line versions. -- A couple of the arguments have be changed to be more like the others. -- pktgen.screen("off"); pktgen.pause("Screen off\n", 1000); pktgen.screen("on"); pktgen.pause("Screen on\n", 1000); pktgen.screen("off"); pktgen.pause("Screen off\n", 1000); printf("delay for 1 second\n"); pktgen.delay(1000); printf("done\n"); -- 'set' commands for a number of per port values pktgen.set("all", "count", 100); pktgen.set("all", "rate", 50); pktgen.set("all", "size", 256); pktgen.set("all", "burst", 128); pktgen.set("all", "sport", 0x5678); pktgen.set("all", "dport", 0x9988); pktgen.set("all", "prime", 3); pktgen.set("all", "seq_cnt", 3); pktgen.rnd("all", 1, 20, "XX111000.. ..xx11"); pktgen.vlanid("all", 55); pktgen.screen("on"); pktgen.pause("Screen on\n", 1000); pktgen.screen("off"); -- sequence command in one line pktgen.seq(0, "all", "0000:4455:6677", "0000:1234:5678", "10.11.0.1", "10.10.0.1/16", 5, 6, "ipv4", "udp", 1, 128); -- sequence command using a table of packet configurations local seq_table = { ["eth_dst_addr"] = "0011:4455:6677", ["eth_src_addr"] = "0011:1234:5678", ["ip_dst_addr"] = "10.12.0.1", ["ip_src_addr"] = "10.12.0.1/16", ["sport"] = 9, ["dport"] = 10, ["ethType"] = "ipv4", ["ipProto"] = "udp", ["vlanid"] = 1, ["pktSize"] = 128 }; pktgen.seqTable(0, "all", seq_table ); pktgen.ports_per_page(2); pktgen.icmp_echo("all", "on"); pktgen.send_arp("all", "g"); pktgen.set_mac("0-2","src", "0001:1122:3344"); pktgen.set_mac("0-2", "dst","0002:1122:3344"); pktgen.mac_from_arp("on"); pktgen.set_ipaddr("0", "dst", "10.10.2.2"); pktgen.set_ipaddr("0", "src", "10.10.1.2/24"); pktgen.set_ipaddr("1", "dst", "10.10.2.2"); pktgen.set_ipaddr("1", "src", "10.10.2.2/24"); pktgen.set_proto("all", "udp"); pktgen.set_type("all", "ipv6"); pktgen.ping4("all"); --pktgen.ping6("all"); --pktgen.show("all", "scan"); pktgen.pcap("all", "on"); pktgen.ports_per_page(4); pktgen.start("all"); pktgen.stop("all"); pktgen.prime("all"); pktgen.delay(1000); pktgen.screen("on"); pktgen.clear("all"); pktgen.cls(); pktgen.reset("all"); pktgen.pause("Do range commands\n", 1000); pktgen.page("range"); pktgen.range.dst_mac("all", "start", "0011:2233:4455"); pktgen..rangesrc_mac("all", "start", "0033:2233:4455"); pktgen.delay(1000); pktgen.range.dst_ip("all", "start", "10.12.0.1"); pktgen.range.dst_ip("all", "inc", "0.0.0.2"); pktgen.range.dst_ip("all", "min", "10.12.0.1"); pktgen.range.dst_ip("all", "max", "10.12.0.64"); pktgen.delay(1000); pktgen.range.src_ip("all", "start", "10.13.0.1"); pktgen.range.src_ip("all", "inc", "0.0.0.3"); pktgen.range.src_ip("all", "min", "10.13.0.1"); pktgen.range.src_ip("all", "max", "10.13.0.64"); pktgen.delay(1000); pktgen.range.dst_port("all", "start", 1234); pktgen.range.dst_port("all", "inc", 4); pktgen.range.dst_port("all", "min", 1234); pktgen.range.dst_port("all", "max", 2345); pktgen.delay(1000); pktgen.range.src_port("all", "start", 5678); pktgen.range.src_port("all", "inc", 5); pktgen.range.src_port("all", "min", 1234); pktgen.range.src_port("all", "max", 9999); pktgen.delay(1000); pktgen.range.vlan_id("all", "start", 1); pktgen.range.vlan_id("all", "inc", 0); pktgen.range.vlan_id("all", "min", 1); pktgen.range.vlan_id("all", "max", 4094); pktgen.delay(1000); pktgen.range.pkt_size("all", "start", 128); pktgen.range.pkt_size("all", "inc", 2); pktgen.range.pkt_size("all", "min", 64); pktgen.range.pkt_size("all", "max", 1518); pktgen.pause("Wait a second, then go back to main page\n", 1000); pktgen.page("0"); pktgen.pause("About to do range\n", 1000); pktgen.set_range("all", "on"); pktgen.port(2); pktgen.process("all", "on"); pktgen.blink("0", "on"); pktgen.pause("Pause for a while, then turn off screen\n", 4000); pktgen.screen("off"); printf("Lua Version : %s\n", pktgen.info.Lua_Version); printf("Pktgen Version : %s\n", pktgen.info.Pktgen_Version); printf("Pktgen Copyright : %s\n", pktgen.info.Pktgen_Copyright); prints("pktgen.info", pktgen.info); printf("Port Count %d\n", pktgen.portCount()); printf("Total port Count %d\n", pktgen.totalPorts()); printf("\nDone\n"); key = pktgen.continue("\nPress any key: "); if ( key == "s" ) then pktgen.set("all", "seq_cnt", 4); pktgen.save("foobar.cmd"); pktgen.continue("Saved foobar.cmd, press key to load that file: "); pktgen.load("foobar.cmd"); end ================================================ FILE: test/port_info.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" -- A list of the test script for Pktgen and Lua. -- Each command somewhat mirrors the pktgen command line versions. -- A couple of the arguments have be changed to be more like the others. -- prints("portInfo", pktgen.portInfo("all")); ================================================ FILE: test/portstats.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" -- A list of the test script for Pktgen and Lua. -- Each command somewhat mirrors the pktgen command line versions. -- A couple of the arguments have be changed to be more like the others. -- prints("portStats", pktgen.portStats("all", "port")); prints("portStats", pktgen.portStats('2', 'port')); ================================================ FILE: test/portstats_with_delay.lua ================================================ package.path = package.path .. ";?.lua;test/?.lua;" require("Pktgen") pktgen.start("0"); TEST_DURATION = 10 pktgen.delay(TEST_DURATION * 1000); pktgen.stop("0"); prints("portStats", pktgen.portStats('0', 'port')); ================================================ FILE: test/range.lua ================================================ pktgen.page("range"); pktgen.range.dst_mac("all", "start", "000d:49ef:016d"); pktgen.range.src_mac("all", "start", "000d:49ef:0163"); pktgen.delay(1000); pktgen.range.dst_ip("all", "start", "10.10.10.1"); pktgen.range.dst_ip("all", "inc", "0.0.0.1"); pktgen.range.dst_ip("all", "min", "10.10.10.1"); pktgen.range.dst_ip("all", "max", "10.10.10.4"); pktgen.delay(1000); pktgen.range.src_ip("all", "start", "20.20.20.1"); pktgen.range.src_ip("all", "inc", "0.0.0.1"); pktgen.range.src_ip("all", "min", "20.20.20.1"); pktgen.range.src_ip("all", "max", "20.20.20.8"); pktgen.delay(1000); pktgen.range.ttl("all", "start", 64); pktgen.range.ttl("all", "inc", 0); pktgen.range.ttl("all", "min", 64); pktgen.range.ttl("all", "max", 64); --pktgen.delay(1000); --pktgen.range.set_type("all", "ipv6"); --pktgen.delay(1000); --pktgen.range.dst_ip("all", "start", "10a:a0a::"); --pktgen.range.dst_ip("all", "inc", "100::"); --pktgen.range.dst_ip("all", "min", "10a:a0a::"); --pktgen.range.dst_ip("all", "max", "40a:a0a::"); --pktgen.delay(1000); --pktgen.range.src_ip("all", "start", "114:1414::"); --pktgen.range.src_ip("all", "inc", "94:1414::"); --pktgen.range.src_ip("all", "min", "114:1414::"); --pktgen.range.src_ip("all", "max", "914:1414::"); --pktgen.delay(1000); --pktgen.range.hop_limits("all", "start", 4); --pktgen.range.hop_limits("all", "inc", 0); --pktgen.range.hop_limits("all", "min", 4); --pktgen.range.hop_limits("all", "max", 4); --pktgen.delay(1000); --pktgen.range.traffic_class("all", "start", 32); --pktgen.range.traffic_class("all", "inc", 0); --pktgen.range.traffic_class("all", "min", 32); --pktgen.range.traffic_class("all", "max", 32); ================================================ FILE: test/screen_off.lua ================================================ -- Lua uses '--' as comment to end of line read the -- manual for more comment options. pktgen.screen("off"); ================================================ FILE: test/set_gtpu_seq.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" -- Lua uses '--' as comment to end of line read the -- manual for more comment options. require "Pktgen" local seq_table = {} seq_table[0] = { -- entries can be in any order ["eth_dst_addr"] = "0011:4455:6677", ["eth_src_addr"] = "0033:1234:5678", ["ip_dst_addr"] = "10.11.0.1", ["ip_src_addr"] = "10.12.0.1/16", -- the 16 is the size of the mask value ["sport"] = 9000, -- Standard port numbers ["dport"] = 2152, -- Standard port numbers ["ethType"] = "ipv4", -- ipv4|ipv6|vlan ["ipProto"] = "udp", -- udp|tcp|icmp ["vlanid"] = 100, -- 1 - 4095 ["pktSize"] = 128, -- 64 - 1518 ["gtpu_teid"] = 1000 -- GTPu TEID (Set dport=2152) }; seq_table[1] = { -- entries can be in any order ["eth_dst_addr"] = "0011:4455:6677", ["eth_src_addr"] = "0033:1234:5678", ["ip_dst_addr"] = "10.11.0.1", ["ip_src_addr"] = "10.12.0.1/16", -- the 16 is the size of the mask value ["sport"] = 9000, -- Standard port numbers ["dport"] = 2152, -- Standard port numbers ["ethType"] = "ipv4", -- ipv4|ipv6|vlan ["ipProto"] = "udp", -- udp|tcp|icmp ["vlanid"] = 100, -- 1 - 4095 ["pktSize"] = 128, -- 64 - 1518 ["gtpu_teid"] = 1000 -- GTPu TEID (Set dport=2152) }; seq_table.n = 2; -- seqTable( seq#, portlist, table ); pktgen.seqTable(0, "all", seq_table[0] ); pktgen.seqTable(1, "all", seq_table[1] ); pktgen.set("all", "seq_cnt", 2); pktgen.set("all", "seqCnt", 4); pktgen.page("seq"); ================================================ FILE: test/set_seq.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" -- Lua uses '--' as comment to end of line read the -- manual for more comment options. require "Pktgen" local seq_table = { -- entries can be in any order ["eth_dst_addr"] = "0011:4455:6677", ["eth_src_addr"] = "0011:1234:5678", ["ip_dst_addr"] = "10.12.0.1", ["ip_src_addr"] = "10.12.0.1/16", -- the 16 is the size of the mask value ["sport"] = 9, -- Standard port numbers ["dport"] = 10, -- Standard port numbers ["ethType"] = "ipv4", -- ipv4|ipv6|vlan ["ipProto"] = "udp", -- udp|tcp|icmp ["vlanid"] = 1, -- 1 - 4095 ["pktSize"] = 128, -- 64 - 1518 ["teid"] = 3, ["cos"] = 5, ["tos"] = 6, ["tcp_flags"] = "fin,ack,cwr" }; -- seqTable( seq#, portlist, table ); pktgen.seqTable(0, "all", seq_table ); pktgen.set("all", "seq_cnt", 4); ================================================ FILE: test/simple_range.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;../?.lua" require "Pktgen"; pkt_size = 64; local dstip = "10.12.0.1"; local min_dstip = "10.12.0.1"; local max_dstip = "10.12.0.64"; local inc_dstip = "0.0.0.1" local srcip = "10.12.0.101"; local min_srcip = "10.12.0.101"; local max_srcip = "10.12.0.101"; local inc_srcip = "0.0.0.0" local dst_port = 5678 local inc_dst_port = 0; local min_dst_port = dst_port local max_dst_port = dst_port; local src_port = 1234 local inc_src_port = 0; local min_src_port = src_port local max_src_port = src_port; printf("\nStarting Experiment!!!\n"); print("Pkt_size is", pkt_size, "\n"); pktgen.set("all", "size", pkt_size) pktgen.page("range") pktgen.range.dst_port("all", "start", dst_port); pktgen.range.dst_port("all", "inc", inc_dst_port); pktgen.range.dst_port("all", "min", min_dst_port); pktgen.range.dst_port("all", "max", max_dst_port); pktgen.range.src_port("all", "start", src_port); pktgen.range.src_port("all", "inc", inc_src_port); pktgen.range.src_port("all", "min", min_src_port); pktgen.range.src_port("all", "max", max_src_port); pktgen.range.dst_ip("all", "start", dstip); pktgen.range.dst_ip("all", "inc", inc_dstip); pktgen.range.dst_ip("all", "min", min_dstip); pktgen.range.dst_ip("all", "max", max_dstip); pktgen.range.src_ip("all", "start", srcip); pktgen.range.src_ip("all", "inc", inc_srcip); pktgen.range.src_ip("all", "min", srcip); pktgen.range.src_ip("all", "max", srcip); pktgen.set_range("0", "on"); ================================================ FILE: test/test.lua ================================================ -- SPDX-License-Identifier: BSD-3-Clause package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" print("**** Execute Range test ********"); pktgen.page("range"); -- Port 0 3c:fd:fe:9c:5c:d8, Port 1 3c:fd:fe:9c:5c:b8 pktgen.range.dst_mac("0", "start", "3c:fd:fe:9c:5c:b8"); pktgen.range.src_mac("0", "start", "3c:fd:fe:9c:5c:d8"); pktgen.range.dst_ip("0", "start", "192.168.1.1"); pktgen.range.dst_ip("0", "inc", "0.0.0.1"); pktgen.range.dst_ip("0", "min", "192.168.1.1"); pktgen.range.dst_ip("0", "max", "192.168.1.128"); pktgen.range.src_ip("0", "start", "192.168.0.1"); pktgen.range.src_ip("0", "inc", "0.0.0.1"); pktgen.range.src_ip("0", "min", "192.168.0.1"); pktgen.range.src_ip("0", "max", "192.168.0.128"); pktgen.set_proto("0", "udp"); pktgen.range.dst_port("0", "start", 2000); pktgen.range.dst_port("0", "inc", 1); pktgen.range.dst_port("0", "min", 2000); pktgen.range.dst_port("0", "max", 4000); pktgen.range.src_port("0", "start", 5000); pktgen.range.src_port("0", "inc", 1); pktgen.range.src_port("0", "min", 5000); pktgen.range.src_port("0", "max", 7000); pktgen.range.pkt_size("0", "start", 64); pktgen.range.pkt_size("0", "inc", 0); pktgen.range.pkt_size("0", "min", 64); pktgen.range.pkt_size("0", "max", 256); -- Set up second port pktgen.range.dst_mac("1", "start", "3c:fd:fe:9c:5c:d8"); pktgen.range.src_mac("1", "start", "3c:fd:fe:9c:5c:b8"); pktgen.range.dst_ip("1", "start", "192.168.0.1"); pktgen.range.dst_ip("1", "inc", "0.0.0.1"); pktgen.range.dst_ip("1", "min", "192.168.0.1"); pktgen.range.dst_ip("1", "max", "192.168.0.128"); pktgen.range.src_ip("1", "start", "192.168.1.1"); pktgen.range.src_ip("1", "inc", "0.0.0.1"); pktgen.range.src_ip("1", "min", "192.168.1.1"); pktgen.range.src_ip("1", "max", "192.168.1.128"); pktgen.set_proto("all", "udp"); pktgen.range.dst_port("1", "start", 5000); pktgen.range.dst_port("1", "inc", 1); pktgen.range.dst_port("1", "min", 5000); pktgen.range.dst_port("1", "max", 7000); pktgen.range.src_port("1", "start", 2000); pktgen.range.src_port("1", "inc", 1); pktgen.range.src_port("1", "min", 2000); pktgen.range.src_port("1", "max", 4000); pktgen.range.pkt_size("1", "start", 64); pktgen.range.pkt_size("1", "inc", 0); pktgen.range.pkt_size("1", "min", 64); pktgen.range.pkt_size("1", "max", 256); pktgen.set_range("0", "on"); pktgen.start(0) ================================================ FILE: test/test1.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" -- A list of the test script for Pktgen and Lua. -- Each command somewhat mirrors the pktgen command line versions. -- A couple of the arguments have be changed to be more like the others. -- seqTable = { [1] = { ["pktSize"] = 128, ["ip_dst_addr"] = "10.12.0.1", ["eth_dst_addr"] = "0011:4455:6677", ["eth_src_addr"] = "0011:1234:5678", ["dport"] = 10, ["sport"] = 9, ["tlen"] = 42, ["ip_src_addr"] = "10.12.0.1/16", ["vlanid"] = 40, ["ipProto"] = "udp", ["ethType"] = "ipv4", }, [2] = { ["pktSize"] = 128, ["ip_dst_addr"] = "10.12.0.1", ["eth_dst_addr"] = "0011:4455:6677", ["eth_src_addr"] = "0011:1234:5678", ["dport"] = 10, ["sport"] = 9, ["tlen"] = 42, ["ip_src_addr"] = "10.12.0.1/16", ["vlanid"] = 40, ["ipProto"] = "udp", ["ethType"] = "ipv4", }, [3] = { ["pktSize"] = 128, ["ip_dst_addr"] = "10.12.0.1", ["eth_dst_addr"] = "0011:4455:6677", ["eth_src_addr"] = "0011:1234:5678", ["dport"] = 10, ["sport"] = 9, ["tlen"] = 42, ["ip_src_addr"] = "10.12.0.1/16", ["vlanid"] = 40, ["ipProto"] = "udp", ["ethType"] = "ipv4", }, [0] = { ["pktSize"] = 128, ["ip_dst_addr"] = "10.12.0.1", ["eth_dst_addr"] = "0011:4455:6677", ["eth_src_addr"] = "0011:1234:5678", ["dport"] = 10, ["sport"] = 9, ["tlen"] = 42, ["ip_src_addr"] = "10.12.0.1/16", ["vlanid"] = 40, ["ipProto"] = "udp", ["ethType"] = "ipv4", }, ["n"] = 4, } prints("seqTable", seqTable); pktgen.seqTable(0, "all", seqTable[0]); pktgen.seqTable(1, "all", seqTable[1]); pktgen.seqTable(2, "all", seqTable[2]); pktgen.seqTable(3, "all", seqTable[3]); pktgen.delay(1000) -- TODO: Need to create a pktgen.seqTableN("all", seqTable); like support ================================================ FILE: test/test2.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" pktgen.seqTable(pktgen.info.startSeqIdx, "all", { ["eth_dst_addr"] = "0011:4455:6677", ["eth_src_addr"] = "0011:1234:5678", ["ip_dst_addr"] = "10.11.0.1", ["ip_src_addr"] = "10.11.0.1/16", ["ethType"] = "ipv4", ["ipProto"] = "udp", ["tlen"] = 42, ["sport"] = 9, ["dport"] = 10, ["pktSize"] = 128, ["vlanid"] = 40 }); prints("seqTable", pktgen.decompile(pktgen.info.startSeqIdx, "all")); pktgen.compile(pktgen.info.startSeqIdx, "all", { ["eth_dst_addr"] = "0022:4455:6677", ["eth_src_addr"] = "0022:1234:5678", ["ip_dst_addr"] = "10.12.0.1", ["ip_src_addr"] = "10.12.0.1/16", ["ethType"] = "ipv4", ["ipProto"] = "tcp", ["tlen"] = 42, ["sport"] = 9, ["dport"] = 10, ["pktSize"] = 128, ["vlanid"] = 40 }); prints("compile", pktgen.decompile(pktgen.info.startSeqIdx, "all")); prints("pktgen", pktgen); ================================================ FILE: test/test3.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" -- A list of the test script for Pktgen and Lua. -- Each command somewhat mirrors the pktgen command line versions. -- A couple of the arguments have be changed to be more like the others. -- prints("linkState", pktgen.linkState("all")); prints("isSending", pktgen.isSending("all")); prints("portSizes", pktgen.portSizes("all")); prints("pktStats", pktgen.pktStats("all")); prints("portRates", pktgen.portStats("all", "rate")); prints("portStats", pktgen.portStats('2', 'port')); ================================================ FILE: test/test_range.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" --pktgen.page("range"); -- Port 0 3c:fd:fe:9c:5c:d8, Port 1 3c:fd:fe:9c:5c:b8 pktgen.range.dst_mac("0", "start", "3c:fd:fe:9c:5c:b8"); pktgen.range.src_mac("0", "start", "3c:fd:fe:9c:5c:d8"); pktgen.range.dst_ip("0", "start", "192.168.1.1"); pktgen.range.dst_ip("0", "inc", "0.0.0.1"); pktgen.range.dst_ip("0", "min", "192.168.1.1"); pktgen.range.dst_ip("0", "max", "192.168.1.128"); pktgen.range.src_ip("0", "start", "192.168.0.1"); pktgen.range.src_ip("0", "inc", "0.0.0.1"); pktgen.range.src_ip("0", "min", "192.168.0.1"); pktgen.range.src_ip("0", "max", "192.168.0.128"); pktgen.set_proto("0", "udp"); pktgen.range.dst_port("0", "start", 2000); pktgen.range.dst_port("0", "inc", 1); pktgen.range.dst_port("0", "min", 2000); pktgen.range.dst_port("0", "max", 4000); pktgen.range.src_port("0", "start", 5000); pktgen.range.src_port("0", "inc", 1); pktgen.range.src_port("0", "min", 5000); pktgen.range.src_port("0", "max", 7000); pktgen.range.pkt_size("0", "start", 64); pktgen.range.pkt_size("0", "inc", 0); pktgen.range.pkt_size("0", "min", 64); pktgen.range.pkt_size("0", "max", 256); -- Set up second port pktgen.range.dst_mac("1", "start", "3c:fd:fe:9c:5c:d8"); pktgen.range.src_mac("1", "start", "3c:fd:fe:9c:5c:b8"); pktgen.range.dst_ip("1", "start", "192.168.0.1"); pktgen.range.dst_ip("1", "inc", "0.0.0.1"); pktgen.range.dst_ip("1", "min", "192.168.0.1"); pktgen.range.dst_ip("1", "max", "192.168.0.128"); pktgen.range.src_ip("1", "start", "192.168.1.1"); pktgen.range.src_ip("1", "inc", "0.0.0.1"); pktgen.range.src_ip("1", "min", "192.168.1.1"); pktgen.range.src_ip("1", "max", "192.168.1.128"); pktgen.set_proto("all", "udp"); pktgen.range.dst_port("1", "start", 5000); pktgen.range.dst_port("1", "inc", 1); pktgen.range.dst_port("1", "min", 5000); pktgen.range.dst_port("1", "max", 7000); pktgen.range.src_port("1", "start", 2000); pktgen.range.src_port("1", "inc", 1); pktgen.range.src_port("1", "min", 2000); pktgen.range.src_port("1", "max", 4000); pktgen.range.pkt_size("1", "start", 64); pktgen.range.pkt_size("1", "inc", 0); pktgen.range.pkt_size("1", "min", 64); pktgen.range.pkt_size("1", "max", 256); pktgen.set_range("all", "on"); ================================================ FILE: test/test_save.lua ================================================ -- -- Pktgen - Ver: 3.0.07 (DPDK 16.07.0-rc3) -- Copyright(c) <2010-2026>, Intel Corporation. All rights reserved., Powered by DPDK -- Command line arguments: (DPDK args are defaults) -- ./app/app/x86_64-native-linux-gcc/app/pktgen -c 3fffe -n 3 -m 512 --proc-type primary -- -T -P -m [2-5:6-9].0 -m [10-13:14-17].1 -f themes/black-yellow.theme -- ####################################################################### -- Pktgen Configuration script information: -- Flags 00000804 -- Number of ports: 2 -- Number ports per page: 4 -- Number descriptors: RX 512 TX: 512 -- Promiscuous mode is Enabled package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" --####################################################################### -- Global configuration: -- geometry 132x44 pktgen.mac_from_arp("disable"); -- ######################### Port 0 ################################## -- -- Port: 0, Burst: 32, Rate:100%, Flags:00020020, TX Count:Forever -- SeqCnt:4, Prime:1 VLAN ID:0001, Link: -- -- Set up the primary port information: pktgen.set('0', 'count', 0); pktgen.set('0', 'size', 64); pktgen.set('0', 'rate', 100); pktgen.set('0', 'burst', 32); pktgen.set('0', 'sport', 1234); pktgen.set('0', 'dport', 5678); pktgen.set('0', 'prime', 1); pktgen.set_type('0', 'ipv4'); pktgen.set_proto('0', 'tcp'); pktgen.set_ipaddr('0', 'dst', '192.168.1.1'); pktgen.set_ipaddr('0', 'src','192.168.0.1/24'); pktgen.set_mac('0', 'dst', '3c:fd:fe:9e:2c:b8'); pktgen.vlanid('0', 1); pktgen.pattern('0', 'abc'); pktgen.jitter('0', 50); pktgen.mpls('0', 'disable'); pktgen.range.mpls_entry('0', '0'); pktgen.qinq('0', 'disable'); pktgen.range.qinqids('0', 0, 0); pktgen.gre('0', 'disable'); pktgen.gre_eth('0', 'disable'); pktgen.range.gre_key('0', 0); -- -- Port flag values: pktgen.icmp_echo('0', 'disable'); pktgen.pcap('0', 'disable'); pktgen.set_range('0', 'disable'); pktgen.latency('0', 'disable'); pktgen.process('0', 'disable'); pktgen.capture('0', 'disable'); pktgen.rxtap('0', 'disable'); pktgen.txtap('0', 'disable'); pktgen.vlan('0', 'disable'); -- -- Range packet information: pktgen.range.src_mac('0', 'start', '3c:fd:fe:9e:29:78'); pktgen.range.src_mac('0', 'min', '00:00:00:00:00:00'); pktgen.range.src_mac('0', 'max', '00:00:00:00:00:00'); pktgen.range.src_mac('0', 'inc', '00:00:00:00:00:00'); pktgen.range.dst_mac('0', 'start', '3c:fd:fe:9e:2c:b8'); pktgen.range.dst_mac('0', 'min', '00:00:00:00:00:00'); pktgen.range.dst_mac('0', 'max', '00:00:00:00:00:00'); pktgen.range.dst_mac('0', 'inc', '00:00:00:00:00:00'); pktgen.range.src_ip('0', 'start', '192.168.0.1'); pktgen.range.src_ip('0', 'min', '192.168.0.1'); pktgen.range.src_ip('0', 'max', '192.168.0.254'); pktgen.range.src_ip(';0', 'inc', '0.0.0.0'); pktgen.range.dst_ip('0', 'start', '192.168.1.1'); pktgen.range.dst_ip('0', 'min', '192.168.1.1'); pktgen.range.dst_ip('0', 'max', '192.168.1.254'); pktgen.range.dst_ip('0', 'inc', '0.0.0.1'); pktgen.ip_proto('0', 'tcp'); pktgen.range.src_port('0', 'start', 0); pktgen.range.src_port('0', 'min', 0); pktgen.range.src_port('0', 'max', 254); pktgen.range.src_port('0', 'inc', 1); pktgen.range.dst_port('0', 'start', 0); pktgen.range.dst_port('0', 'min', 0); pktgen.range.dst_port('0', 'max', 254); pktgen.range.dst_port('0', 'inc', 1); pktgen.range.vlan_id('0', 'start', 1); pktgen.range.vlan_id('0', 'min', 1); pktgen.range.vlan_id('0', 'max', 4095); pktgen.range.vlan_id('0', 'inc', 0); pktgen.range.pkt_size('0', 'start', 64); pktgen.range.pkt_size('0', 'min', 64); pktgen.range.pkt_size('0', 'max', 1518); pktgen.range.pkt_size('0', 'inc', 0); -- -- Set up the sequence data for the port. pktgen.set('0', 'seq_cnt', 4); -- (seqnum, port, dst_mac, src_mac, ip_dst, ip_src, sport, dport, ethType, proto, vlanid, pktSize, gtpu_teid) -- pktgen.seq(0, '0', '3c:fd:fe:9e:2c:b8' '3c:fd:fe:9e:29:78', '192.168.1.1', '192.168.0.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); -- pktgen.seq(1, '0', '3c:fd:fe:9e:2c:b8' '3c:fd:fe:9e:29:78', '192.168.1.1', '192.168.0.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); -- pktgen.seq(2, '0', '3c:fd:fe:9e:2c:b8' '3c:fd:fe:9e:29:78', '192.168.1.1', '192.168.0.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); -- pktgen.seq(3, '0', '3c:fd:fe:9e:2c:b8' '3c:fd:fe:9e:29:78', '192.168.1.1', '192.168.0.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); local seq_table = {} seq_table[0] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:b8', ['eth_src_addr'] = '3c:fd:fe:9e:29:78', ['ip_dst_addr'] = '192.168.1.1', ['ip_src_addr'] = '192.168.0.1', ['sport'] = 1234, ['dport'] = 5678, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[1] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:b8', ['eth_src_addr'] = '3c:fd:fe:9e:29:78', ['ip_dst_addr'] = '192.168.1.1', ['ip_src_addr'] = '192.168.0.1', ['sport'] = 1234, ['dport'] = 5678, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[2] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:b8', ['eth_src_addr'] = '3c:fd:fe:9e:29:78', ['ip_dst_addr'] = '192.168.1.1', ['ip_src_addr'] = '192.168.0.1', ['sport'] = 1234, ['dport'] = 5678, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[3] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:b8', ['eth_src_addr'] = '3c:fd:fe:9e:29:78', ['ip_dst_addr'] = '192.168.1.1', ['ip_src_addr'] = '192.168.0.1', ['sport'] = 1234, ['dport'] = 5678, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } pktgen.seqTable(0, '0', seq_table[0]); pktgen.seqTable(1, '0', seq_table[1]); pktgen.seqTable(2, '0', seq_table[2]); pktgen.seqTable(3, '0', seq_table[3]); -- Rnd bitfeilds pktgen.rnd('0', 0, 100, 'XXX...111.000...111.XXX.........'); pktgen.rnd('0', 1, 100, 'X11...111.000...111.XXX.........'); -- ######################### Port 1 ################################## -- -- Port: 1, Burst: 32, Rate:100%, Flags:00020020, TX Count:Forever -- SeqCnt:4, Prime:1 VLAN ID:0001, Link: -- -- Set up the primary port information: pktgen.set('1', 'count', 0); pktgen.set('1', 'size', 64); pktgen.set('1', 'rate', 100); pktgen.set('1', 'burst', 32); pktgen.set('1', 'sport', 1234); pktgen.set('1', 'dport', 5678); pktgen.set('1', 'prime', 1); pktgen.set_type('1', 'ipv4'); pktgen.set_proto('1', 'tcp'); pktgen.set_ipaddr('1', 'dst', '192.168.0.1'); pktgen.set_ipaddr('1', 'src','192.168.1.1/24'); pktgen.set_mac('1', 'dst', '3c:fd:fe:9e:29:78'); pktgen.vlanid('1', 1); pktgen.pattern('1', 'abc'); pktgen.jitter('1', 50); pktgen.mpls('1', 'disable'); pktgen.mpls_entry('1', '0'); pktgen.qinq('1', 'disable'); pktgen.qinqids('1', 0, 0); pktgen.gre('1', 'disable'); pktgen.gre_eth('1', 'disable'); pktgen.gre_key('1', 0); -- -- Port flag values: pktgen.icmp_echo('1', 'disable'); pktgen.pcap('1', 'disable'); pktgen.set_range('1', 'disable'); pktgen.latency('1', 'disable'); pktgen.process('1', 'disable'); pktgen.capture('1', 'disable'); pktgen.rxtap('1', 'disable'); pktgen.txtap('1', 'disable'); pktgen.vlan('1', 'disable'); -- -- Range packet information: pktgen.range.src_mac('1', 'start', '3c:fd:fe:9e:2c:b8'); pktgen.range.src_mac('1', 'min', '00:00:00:00:00:00'); pktgen.range.src_mac('1', 'max', '00:00:00:00:00:00'); pktgen.range.src_mac('1', 'inc', '00:00:00:00:00:00'); pktgen.range.dst_mac('1', 'start', '3c:fd:fe:9e:29:78'); pktgen.range.dst_mac('1', 'min', '00:00:00:00:00:00'); pktgen.range.dst_mac('1', 'max', '00:00:00:00:00:00'); pktgen.range.dst_mac('1', 'inc', '00:00:00:00:00:00'); pktgen.range.src_ip('1', 'start', '192.168.1.1'); pktgen.range.src_ip('1', 'min', '192.168.1.1'); pktgen.range.src_ip('1', 'max', '192.168.1.254'); pktgen.range.src_ip(';1', 'inc', '0.0.0.0'); pktgen.range.dst_ip('1', 'start', '192.168.2.1'); pktgen.range.dst_ip('1', 'min', '192.168.2.1'); pktgen.range.dst_ip('1', 'max', '192.168.2.254'); pktgen.range.dst_ip('1', 'inc', '0.0.0.1'); pktgen.ip_proto('1', 'tcp'); pktgen.range.src_port('1', 'start', 256); pktgen.range.src_port('1', 'min', 256); pktgen.range.src_port('1', 'max', 510); pktgen.range.src_port('1', 'inc', 1); pktgen.range.dst_port('1', 'start', 256); pktgen.range.dst_port('1', 'min', 256); pktgen.range.dst_port('1', 'max', 510); pktgen.range.dst_port('1', 'inc', 1); pktgen.range.vlan_id('1', 'start', 1); pktgen.range.vlan_id('1', 'min', 1); pktgen.range.vlan_id('1', 'max', 4095); pktgen.range.vlan_id('1', 'inc', 0); pktgen.range.pkt_size('1', 'start', 64); pktgen.range.pkt_size('1', 'min', 64); pktgen.range.pkt_size('1', 'max', 1518); pktgen.range.pkt_size('1', 'inc', 0); -- -- Set up the sequence data for the port. pktgen.set('1', 'seq_cnt', 4); -- (seqnum, port, dst_mac, src_mac, ip_dst, ip_src, sport, dport, ethType, proto, vlanid, pktSize, gtpu_teid) -- pktgen.seq(0, '1', '3c:fd:fe:9e:29:78' '3c:fd:fe:9e:2c:b8', '192.168.0.1', '192.168.1.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); -- pktgen.seq(1, '1', '3c:fd:fe:9e:29:78' '3c:fd:fe:9e:2c:b8', '192.168.0.1', '192.168.1.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); -- pktgen.seq(2, '1', '3c:fd:fe:9e:29:78' '3c:fd:fe:9e:2c:b8', '192.168.0.1', '192.168.1.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); -- pktgen.seq(3, '1', '3c:fd:fe:9e:29:78' '3c:fd:fe:9e:2c:b8', '192.168.0.1', '192.168.1.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); local seq_table = {} seq_table[0] = { ['eth_dst_addr'] = '3c:fd:fe:9e:29:78', ['eth_src_addr'] = '3c:fd:fe:9e:2c:b8', ['ip_dst_addr'] = '192.168.0.1', ['ip_src_addr'] = '192.168.1.1', ['sport'] = 1234, ['dport'] = 5678, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[1] = { ['eth_dst_addr'] = '3c:fd:fe:9e:29:78', ['eth_src_addr'] = '3c:fd:fe:9e:2c:b8', ['ip_dst_addr'] = '192.168.0.1', ['ip_src_addr'] = '192.168.1.1', ['sport'] = 1234, ['dport'] = 5678, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[2] = { ['eth_dst_addr'] = '3c:fd:fe:9e:29:78', ['eth_src_addr'] = '3c:fd:fe:9e:2c:b8', ['ip_dst_addr'] = '192.168.0.1', ['ip_src_addr'] = '192.168.1.1', ['sport'] = 1234, ['dport'] = 5678, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[3] = { ['eth_dst_addr'] = '3c:fd:fe:9e:29:78', ['eth_src_addr'] = '3c:fd:fe:9e:2c:b8', ['ip_dst_addr'] = '192.168.0.1', ['ip_src_addr'] = '192.168.1.1', ['sport'] = 1234, ['dport'] = 5678, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } pktgen.seqTable(0, '1', seq_table[0]); pktgen.seqTable(1, '1', seq_table[1]); pktgen.seqTable(2, '1', seq_table[2]); pktgen.seqTable(3, '1', seq_table[3]); -- Rnd bitfeilds pktgen.rnd('1', 1, 100, 'X11...111.000...111.XXX.........'); -- ################################ Done ################################# ================================================ FILE: test/test_seq.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" -- -- Set up the sequence data for the port. pktgen.set('0', 'seq_cnt', 8); -- (seqnum, port, dst_mac, src_mac, ip_dst, ip_src, sport, dport, ethType, proto, vlanid, pktSize, gtpu_teid) -- pktgen.seq(0, '0', '3c:fd:fe:9e:2c:b8' '3c:fd:fe:9e:29:78', '192.168.1.1', '192.168.0.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); -- pktgen.seq(1, '0', '3c:fd:fe:9e:2c:b8' '3c:fd:fe:9e:29:78', '192.168.1.1', '192.168.0.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); -- pktgen.seq(2, '0', '3c:fd:fe:9e:2c:b8' '3c:fd:fe:9e:29:78', '192.168.1.1', '192.168.0.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); -- pktgen.seq(3, '0', '3c:fd:fe:9e:2c:b8' '3c:fd:fe:9e:29:78', '192.168.1.1', '192.168.0.1/24', 1234, 5678, 'ipv4', 'tcp', 1, 64, 0); local seq_table = {} seq_table[0] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:b8', ['eth_src_addr'] = '3c:fd:fe:9e:29:78', ['ip_dst_addr'] = '192.168.1.1', ['ip_src_addr'] = '192.168.0.1/24', ['sport'] = 12340, ['dport'] = 56780, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[1] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:b9', ['eth_src_addr'] = '3c:fd:fe:9e:29:79', ['ip_dst_addr'] = '192.168.2.1', ['ip_src_addr'] = '192.168.0.2/24', ['sport'] = 12341, ['dport'] = 56781, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[2] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:ba', ['eth_src_addr'] = '3c:fd:fe:9e:29:7a', ['ip_dst_addr'] = '192.168.3.1', ['ip_src_addr'] = '192.168.0.3/24', ['sport'] = 12342, ['dport'] = 56782, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[3] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:bb', ['eth_src_addr'] = '3c:fd:fe:9e:29:7b', ['ip_dst_addr'] = '192.168.4.1', ['ip_src_addr'] = '192.168.0.4/24', ['sport'] = 12343, ['dport'] = 56783, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[4] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:bc', ['eth_src_addr'] = '3c:fd:fe:9e:29:7c', ['ip_dst_addr'] = '192.168.5.1', ['ip_src_addr'] = '192.168.0.5/24', ['sport'] = 12344, ['dport'] = 56784, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[5] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:bd', ['eth_src_addr'] = '3c:fd:fe:9e:29:7d', ['ip_dst_addr'] = '192.168.6.1', ['ip_src_addr'] = '192.168.0.6/24', ['sport'] = 12345, ['dport'] = 56785, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[6] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:be', ['eth_src_addr'] = '3c:fd:fe:9e:29:7e', ['ip_dst_addr'] = '192.168.7.1', ['ip_src_addr'] = '192.168.0.7/24', ['sport'] = 12346, ['dport'] = 56786, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } seq_table[7] = { ['eth_dst_addr'] = '3c:fd:fe:9e:2c:bf', ['eth_src_addr'] = '3c:fd:fe:9e:29:7f', ['ip_dst_addr'] = '192.168.8.1', ['ip_src_addr'] = '192.168.0.8/24', ['sport'] = 12347, ['dport'] = 56787, ['ethType'] = 'ipv4', ['ipProto'] = 'tcp', ['vlanid'] = 1, ['pktSize'] = 60, ['gtpu_teid'] = 0 } pktgen.seqTable(0, '0', seq_table[0]); pktgen.seqTable(1, '0', seq_table[1]); pktgen.seqTable(2, '0', seq_table[2]); pktgen.seqTable(3, '0', seq_table[3]); pktgen.seqTable(4, '0', seq_table[4]); pktgen.seqTable(5, '0', seq_table[5]); pktgen.seqTable(6, '0', seq_table[6]); pktgen.seqTable(7, '0', seq_table[7]); ================================================ FILE: test/tx-rx-loopback.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen"; local pkt_size = 64; local duration = 10000; local pauseTime = 1000; local sendport = "0"; local recvport = "1"; local num_tx, num_rx, num_dropped; -- 'set' commands for a number of per port values pktgen.set("all", "rate", 100); pktgen.set("all", "size", pkt_size); -- send continuous stream of traffic pktgen.set("all", "count", 0); -- pktgen.set("all", "burst", 128); pktgen.set("all", "sport", 0x5678); pktgen.set("all", "dport", 0x9988); -- pktgen.set("all", "prime", 3); pktgen.set_ipaddr("0", "dst", "172.17.25.150"); pktgen.set_ipaddr("0", "src", "172.17.25.15/24"); pktgen.set_ipaddr("1", "dst", "172.17.25.15"); pktgen.set_ipaddr("1", "src", "172.17.25.150/24"); pktgen.set_proto("all", "udp"); -- pktgen.set_type("all", "ipv4"); --pktgen.clr(); pktgen.start("all"); pktgen.delay(duration); pktgen.stop("all"); pktgen.delay(pauseTime); statTx = pktgen.portStats(sendport, "port")[tonumber(sendport)]; statRx = pktgen.portStats(recvport, "port")[tonumber(recvport)]; num_tx = statTx.opackets; num_rx = statRx.ipackets; num_dropped = num_tx - num_rx; print("Tx: " .. num_tx .. ". Rx: " .. num_rx .. ". Dropped: " .. num_dropped); prints("DEBUG portStats", pktgen.portStats("all", "port")); ================================================ FILE: test/vlan_udp_range.lua ================================================ package.path = package.path ..";?.lua;test/?.lua;app/?.lua;" require "Pktgen" -- A list of the test script for Pktgen and Lua. -- Each command somewhat mirrors the pktgen command line versions. -- A couple of the arguments have be changed to be more like the others. -- local function doWait(port, waitTime) local idx; pktgen.delay(1000); if ( waitTime == 0 ) then return; end waitTime = waitTime - 1; -- Try to wait for the total number of packets to be sent. local idx = 0; while( idx < waitTime ) do idx = idx + 1; local sending = pktgen.isSending(port); if ( sending[tonumber(port)] == "n" ) then break; end pktgen.delay(1000); end end pktgen.reset("all"); -- 'set' commands for a number of per port values pktgen.set("all", "count", 100); pktgen.set("all", "rate", 1); pktgen.set("all", "size", 64); pktgen.set("all", "burst", 128); pktgen.vlanid("all", 55); pktgen.set_type("all", "ipv4"); pktgen.set_proto("all", "udp"); pktgen.clear("all"); pktgen.cls(); pktgen.pause("Do range commands\n", 1000); pktgen.page("range"); pktgen.range.dst_mac("all", "start", "0011:2233:4455"); pktgen.range.src_mac("all", "start", "0033:2233:4455"); --pktgen.delay(1000); pktgen.range.dst_ip("all", "start", "10.10.10.1"); pktgen.range.dst_ip("all", "inc", "0.0.0.1"); pktgen.range.dst_ip("all", "min", "10.10.10.1"); pktgen.range.dst_ip("all", "max", "10.10.10.64"); --pktgen.delay(1000); pktgen.range.src_ip("all", "start", "11.11.11.1"); pktgen.range.src_ip("all", "inc", "0.0.0.1"); pktgen.range.src_ip("all", "min", "11.11.11.1"); pktgen.range.src_ip("all", "max", "11.11.11.64"); pktgen.set_proto("all", "udp"); --pktgen.delay(1000); pktgen.range.dst_port("all", "start", 2152); pktgen.range.dst_port("all", "inc", 0); pktgen.range.dst_port("all", "min", 2152); pktgen.range.dst_port("all", "max", 2152); pktgen.delay(1000); pktgen.range.src_port("all", "start", 1000); pktgen.range.src_port("all", "inc", 1); pktgen.range.src_port("all", "min", 1000); pktgen.range.src_port("all", "max", 9999); --pktgen.delay(1000); pktgen.range.vlan_id("all", "start", 100); pktgen.range.vlan_id("all", "inc", 1); pktgen.range.vlan_id("all", "min", 100); pktgen.range.vlan_id("all", "max", 4094); --pktgen.delay(1000); pktgen.range.pkt_size("all", "start", 128); pktgen.range.pkt_size("all", "inc", 2); pktgen.range.pkt_size("all", "min", 64); pktgen.range.pkt_size("all", "max", 1518); pktgen.set_range("all", "on"); pktgen.vlan("all", "on"); pktgen.pause("Wait a second, then go back to main page\n", 1000); pktgen.page("0"); pktgen.pause("About to do range\n", 2000); pktgen.start("all"); printf("Waiting for TX to run\n"); doWait("0", 10); printf("Done\n"); printf("Port Count %d\n", pktgen.portCount()); printf("Total port Count %d\n", pktgen.totalPorts()); ================================================ FILE: themes/black-yellow.theme ================================================ theme default white white off theme top.spinner cyan none bold theme top.ports green none bold theme top.page white none bold theme top.copyright yellow none off theme top.poweredby blue none bold theme sep.dash blue none off theme sep.text white none off theme stats.port.label blue none bold theme stats.port.flags blue none bold theme stats.port.data blue none off theme stats.port.status green none off theme stats.port.linklbl green none bold theme stats.port.link green none off theme stats.port.ratelbl white none bold theme stats.port.rate white none off theme stats.port.sizelbl cyan none bold theme stats.port.sizes cyan none off theme stats.port.errlbl red none bold theme stats.port.errors red none off theme stats.port.totlbl blue none bold theme stats.port.totals blue none off theme stats.dyn.label blue none bold theme stats.dyn.values green none off theme stats.stat.label magenta none off theme stats.stat.values white none off theme stats.total.label red none bold theme stats.total.data blue none bold theme stats.colon blue none bold theme stats.rate.count blue none bold theme stats.bdf blue none off theme stats.mac green none off theme stats.ip cyan none off theme pktgen.prompt green none off theme on cls ================================================ FILE: themes/white-black.theme ================================================ theme default white none off theme top.spinner cyan none bold theme top.ports green none bold theme top.page white none off theme top.copyright yellow none off theme top.poweredby blue none bold theme sep.dash blue none off theme sep.text white none off theme stats.port.label blue none bold theme stats.port.flags blue none bold theme stats.port.status green none bold theme stats.dyn.label yellow none off theme stats.dyn.values yellow none off theme stats.stat.label magenta none off theme stats.stat.values white none off theme stats.total.label red none bold theme stats.colon blue none bold theme stats.rate.count yellow none off theme stats.bdf blue none bold theme stats.mac green none bold theme stats.ip cyan none bold theme pktgen.prompt green none off theme on cls ================================================ FILE: tools/call-sphinx-build.py ================================================ #! /usr/bin/env python3 # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2020-2026> Intel Corporation # import sys import os from os.path import join from subprocess import run, PIPE, STDOUT from distutils.version import StrictVersion (sphinx, src, dst) = sys.argv[1:] # assign parameters to variables # for sphinx version >= 1.7 add parallelism using "-j auto" ver = run([sphinx, '--version'], stdout=PIPE, stderr=STDOUT).stdout.decode().split()[-1] sphinx_cmd =[sphinx] if StrictVersion(ver) >= StrictVersion('1.7'): sphinx_cmd += ['-j', 'auto'] # find all the files sphinx will process so we can write them as dependencies srcfiles = [] for root, dirs, files in os.walk(src): srcfiles.extend([join(root, f) for f in files]) # run sphinx, putting the html output in a "html" directory process = run(sphinx_cmd + ['-b', 'html', src, join(dst, 'html')], check=True) print(str(process.args) + ' Done OK') # create a gcc format .d file giving all the dependencies of this doc build with open(join(dst, '.html.d'), 'w') as d: d.write('html: ' + ' '.join(srcfiles) + '\n') ================================================ FILE: tools/dpdk-version.sh ================================================ #!/usr/bin/env sh # # Copyright(c) <2020-2025> Intel Corporation. All rights reserved. # # SPDX-License-Identifier: BSD-3-Clause # # Locate the rte_version.h file and parse out the version strings. # # If RTE_SDK is set, then it is used to locate the file. If the # RTE_SDK variable is not set then the current working directory # is used. # # See usage below for more details. # fname=rte_version.h usage() { cat < Intel Corporation # set up map-to-def script using python, either built-in or external python3 = import('python').find_installation(required: false) if python3.found() py3 = [python3] else py3 = ['meson', 'runpython'] endif sphinx_wrapper = py3 + files('call-sphinx-build.py') ================================================ FILE: tools/pktgen-build.sh ================================================ #!/bin/bash # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) <2019-2025> Intel Corporation # A simple script to help build Pktgen using meson/ninja tools. # The script also creates an installed directory called ./usr in the top level directory. # The install directory will contain all of the includes and libraries # for external applications to build and link with Pktgen. # # using 'pktgen-build.sh help' or 'pktgen-build.sh -h' or 'pktgen-build.sh --help' to see help information. # buildtype="release" currdir=`pwd` script_dir=$(cd "${BASH_SOURCE[0]%/*}" & pwd -P) export sdk_path="${PKTGEN_SDK:-${script_dir%/*}}" export target_dir="${PKTGEN_TARGET:-usr/local}" export build_dir="${PKTGEN_BUILD:-${currdir}/builddir}" install_path="${PKTGEN_DESTDIR:-${currdir}}" export lua_enabled="-Denable_lua=false" configure="setup" if [[ "${build_dir}" = /* ]]; then # absolute path to build dir. Do not prepend workdir. build_path=$build_dir else build_path=${currdir}/$build_dir fi if [[ ! "${install_path}" = /* ]]; then # relative path for install path detected # prepend with currdir install_path=${currdir}/${install_path} fi if [[ "${target_dir}" = .* ]]; then echo "target_dir starts with . or .. if different install prefix required then use PKTGEN_DESTDIR instead" exit 1 fi if [[ "${target_dir}" = /* ]]; then echo "target_dir absolute path detected removing leading '/'" export target_dir=${target_dir##/} fi target_path=${install_path%/}/${target_dir%/} echo ">> SDK Path : "$sdk_path echo ">> Install Path : "$install_path echo ">> Build Directory : "$build_dir echo ">> Target Directory : "$target_dir echo ">> Build Path : "$build_path echo ">> Target Path : "$target_path echo "" function dump_options() { echo " Build and install values:" echo " lua_enabled : "$lua_enabled echo "" } function run_meson() { btype="-Dbuildtype="$buildtype echo "meson $configure $btype $lua_enabled $build_dir" if ! meson $configure $btype $lua_enabled $build_dir; then echo "*** ERROR: meson $configure $btype $lua_enabled $build_dir" configure="" return 1 fi configure="" return 0 } function ninja_build() { echo ">>> Ninja build in '"$build_path"' buildtype="$buildtype if [[ -d $build_path ]] || [[ -f $build_path/build.ninja ]]; then # add reconfigure command if meson dir already exists configure="configure" # sdk_dir must be empty if we're reconfiguring sdk_dir="" fi if ! run_meson; then return 1 fi ninja -C $build_path if [[ $? -ne 0 ]]; then return 1; fi return 0 } function ninja_build_docs() { echo ">>> Ninja build documents in '"$build_path"'" if [[ ! -d $build_path ]] || [[ ! -f $build_path/build.ninja ]]; then if ! run_meson; then return 1 fi fi ninja -C $build_path docs if [[ $? -ne 0 ]]; then return 1; fi return 0 } ninja_install() { echo ">>> Ninja install to '"$target_path"'" DESTDIR=$install_path ninja -C $build_path install if [[ $? -ne 0 ]]; then echo "*** Install failed!!" return 1; fi return 0 } ninja_uninstall() { echo ">>> Ninja uninstall to '"$target_path"'" DESTDIR=$install_path ninja -C $build_path uninstall if [[ $? -ne 0 ]]; then echo "*** Uninstall failed!!" return 1; fi return 0 } usage() { echo " Usage: Build Pktgen using Meson/Ninja tools" echo " ** Must be in the top level directory for Pktgen" echo " This tool is in tools/pktgen-build.sh, but use 'make' which calls this script" echo " Use 'make' to build Pktgen as it allows for multiple targets i.e. 'make clean debug'" echo "" echo " Command Options:" echo " - create the '"$build_dir"' directory if not present and compile Pktgen" echo " If the '"$build_dir"' directory exists it will use ninja to build Pktgen without" echo " running meson unless one of the meson.build files were changed" echo " build - same as 'make' with no arguments" echo " buildlua - same as 'make build' except enable Lua build" echo " debug - turn off optimization, may need to do 'clean' then 'debug' the first time" echo " debugopt - turn optimization on with -O2, may need to do 'clean' then 'debugopt' the first time" echo " clean - remove the following directory: "$build_path echo " install - install the includes/libraries into "$target_path" directory" echo " uninstall - uninstall the includes/libraries into "$target_path" directory" echo " docs - create the document files" echo "" echo " Build and install environment variables:" echo " PKTGEN_INSTALL_PATH - The install path, defaults to Pktgen SDK directory" echo " PKTGEN_TARGET - The target directory appended to install path, defaults to 'usr'" echo " PKTGEN_BUILD - The build directory appended to install path, default to 'builddir'" echo " PKTGEN_DESTDIR - The install destination directory" echo "" dump_options exit } for cmd in "$@" do case "$cmd" in 'help' | '-h' | '--help') usage ;; 'build') dump_options ninja_build && ninja_install ;; 'buildlua') lua_enabled="-Denable_lua=true" dump_options ninja_build && ninja_install ;; 'debuglua') lua_enabled="-Denable_lua=true" buildtype="debug" dump_options ninja_build && ninja_install ;; 'debug') buildtype="debug" dump_options ninja_build && ninja_install ;; 'debugopt') echo ">>> Debug Optimized build in '"$build_path"' and '"$target_path"'" buildtype="debugoptimized" dump_options ninja_build && ninja_install ;; 'clean') dump_options echo "*** Removing '"$build_path"' directory" rm -fr $build_path ;; 'install') dump_options echo ">>> Install the includes/libraries into '"$target_path"' directory" ninja_install ;; 'uninstall') dump_options echo ">>> Uninstall the includes/libraries from '"$target_path"' directory" ninja_uninstall ;; 'docs') dump_options echo ">>> Create the documents '"$build_path"' directory" ninja_build_docs ;; *) if [[ $# -gt 0 ]]; then usage else echo ">>> Build and install Pktgen" dump_options ninja_build && ninja_install fi ;; esac done ================================================ FILE: tools/run.py ================================================ #! /usr/bin/env python3 # # Copyright(c) <2020-2026> Intel Corporation. All rights reserved. # # SPDX-License-Identifier: BSD-3-Clause # import sys import os import getopt import subprocess import glob import types import importlib.machinery import importlib.util import shutil def usage(): '''Print usage information for the program''' argv0 = os.path.basename(sys.argv[0]) print(""" Usage: ------ %(argv0)s [options] [config_name] Where config_name is the one of the defined configuration files if no config file is listed then the ./default.cfg file will be used. If a cfg directory is located in the current directory then it will be searched for a match. The config_name is the name of the file without path and .cfg extension. Options: -------- -h, --help | -u, --usage: Display usage information and quit -s, --setup: Setup the environment for running the configuration file. -l, --list: Print a list of known configuration files -n, --no-run: Just create the command line and output the line without running Pktgen. -v, --verbose Print out more information Examples: --------- To display current list of configuration files: %(argv0)s --list To run a config file: %(argv0)s default The configuration file name is always suffixed by .cfg as in default.cfg. The search location of the configuration files is .:./cfg """ % locals()) # replace items from local variables sys.exit(0) def err_exit(str): ''' print the error string and exit ''' print(str) sys.exit(1) def find_file(fn, t): ''' Find the first file matching the arg value ''' f = os.path.splitext(fn) if f[1] == t: fn = f[0] for f in file_list('cfg', t): b = os.path.basename(f) if os.path.splitext(b)[0] == fn: return f return None def mk_tuple(lst, s): ''' Convert a string to a tuple if needed ''' t = {} if type(lst[s]) != tuple: if verbose: print('Not a Tuple', type(lst[s]), lst[s]) t[s] = tuple([lst[s],]) else: if verbose: print('A tuple', type(lst[s]), lst[s]) t[s] = lst[s] if verbose: print('New t[s]', type(t[s]), t[s]) return t[s] def add_ld_options(s, arg_list): ''' Append LD_LIBRARY_PATH option to arg list ''' if s in cfg.run: str = 'LD_LIBRARY_PATH=.' for a in mk_tuple(cfg.run, s): _p = a % globals() str = str + ':' + _p arg_list.append(str) def add_run_options(s, arg_list, p): ''' Append options to arg list ''' if s in cfg.run: for a in mk_tuple(cfg.run, s): if p is not None: arg_list.append(p) _p = a % globals() arg_list.append(_p) def add_setup_options(s, arg_list): ''' Append options to arg list ''' if s in cfg.setup: for a in mk_tuple(cfg.setup, s): arg_list.extend(a.split(' ')) def file_list(directory, file_extension): ''' Return list of configuration files ''' fileiter = (os.path.join(root, f) for root, _, files in os.walk(directory) for f in files) return (f for f in fileiter if os.path.splitext(f)[1] == file_extension) def load_cfg(fname): ''' Load the configuration or .cfg file as a python data file ''' if not os.path.exists(fname): err_exit("Config file %s does not exists\n" % fname) try: configuration_file = open(fname) except: err_exit("Error: unable to open file %s\n" % fname) global cfg loader = importlib.machinery.SourceFileLoader('cfg', fname) spec = importlib.util.spec_from_loader(loader.name, loader) cfg = importlib.util.module_from_spec(spec) loader.exec_module(cfg) print(cfg) configuration_file.close() shutil.rmtree('cfg/__pycache__') return cfg def show_configs(): ''' Show configuration files ''' print("Configurations:") print(" %-16s - %s" % ("Name", "Description")) print(" %-16s %s" % ("----", "-----------")) for fname in file_list('cfg', '.cfg'): base = os.path.splitext(os.path.basename(fname))[0] try: cfg = load_cfg(fname) if not cfg.description: cfg.description = "" print(" %-16s - %s" % (base, cfg.description)) except NameError: sys.stderr.write("We were unable to load the module " + fname + \ " If you do not plan to use this module you can safely ignore this " \ "message.\n") finally: # reset the description to empty, for next loop/file cfg.description = "" sys.exit(0) def run_cfg(cfg_file): ''' Run the configuration in the .cfg file ''' cfg = load_cfg(cfg_file) args = [] add_run_options('exec', args, None) add_ld_options('ld_path', args) if not 'app_path' in cfg.run: err_exit("'app_path' variable is missing from cfg.run in config file") if not 'app_name' in cfg.run: err_exit("'app_name' variable is missing from cfg.run in config file") # convert the cfg.run['app_name'] into a global variable used in # the creation of the application/path. app_name must be a global variable. global app_name app_name = cfg.run['app_name'] # Try all of the different path versions till we find one. fname = None for app in cfg.run['app_path']: fn = app % globals() print(" Trying %s" % fn) if os.path.exists(fn): fname = fn if verbose: print("Found %s" % fn) break if not fname: err_exit("Error: Unable to locate application %s" % cfg.run['app_name']) args.extend([fname]) add_run_options('cores', args, '-l') add_run_options('nrank', args, '-n') add_run_options('proc', args, '--proc-type') add_run_options('log', args, '--log-level') add_run_options('prefix', args, '--file-prefix') add_run_options('shared', args, '-d') add_run_options('blocklist', args, '-b') add_run_options('allowlist', args, '-a') add_run_options('vdev', args, '--vdev') add_run_options('plugin', args, '-d') args.extend(["--"]) add_run_options('opts', args, None) add_run_options('map', args, '-m') add_run_options('pcap', args, '-s') add_run_options('theme', args, '-f') add_run_options('loadfile', args, '-f') add_run_options('logfile', args, '-l') # Convert the args list to a single string with spaces. str = "" for a in args: str = str + "%s " % a # Output the command line print(str) if norun: return if verbose: print("Command line:") print(args) subprocess.call(args) subprocess.call(['stty', 'sane']) def num_sockets(hpath): ''' Count the number of sockets in the system ''' sockets = 0 for i in range(0, 8): if os.path.exists(hpath % i): sockets = sockets + 1 return sockets def setup_cfg(cfg_file): ''' Setup the system by adding modules and ports to dpdk control ''' cfg = load_cfg(cfg_file) print("Setup DPDK to run '%s' application from %s file" % (cfg.run['app_name'], cfg_file)) sys_node = '/sys/devices/system/node/node%d/hugepages' hugepage_path = sys_node + '/hugepages-2048kB/nr_hugepages' # calculate the number of sockets in the system. nb_sockets = int(num_sockets(hugepage_path)) if nb_sockets == 0: nb_sockets = 1 p = subprocess.Popen(['sysctl', '-n', 'vm.nr_hugepages'], stdout=subprocess.PIPE) # split the number of hugepages between the sockets nb_hugepages = int(p.communicate()[0]) / nb_sockets if verbose: print(" Hugepages per socket %d" % nb_hugepages) if verbose: print(" modprobe the 'uio' required module") if 'uio' in cfg.setup: u = cfg.setup['uio'] if u == 'igb_uio': subprocess.call(['sudo', 'modprobe', "uio"]) if verbose: print(" Remove %s if already installed" % u) ret = subprocess.call(['sudo', 'rmmod', u]) if ret > 0: print(" Remove of %s, displayed an error ignore it" % u) uio = ("%s/%s/kmod/%s.ko" % (sdk, target, u)) if verbose: print(" insmode the %s module" % uio) subprocess.call(['sudo', 'insmod', uio]) if u == 'vfio-pci': ret = subprocess.call(['sudo', 'rmmod', u]) if ret > 0: print(" Remove of %s, displayed an error ignore it" % u) if verbose: print(" modprobe the %s module" % u) subprocess.call(['sudo', 'modprobe', u]) if u == 'uio_pci_generic': ret = subprocess.call(['sudo', 'rmmod', u]) if ret > 0: print(" Remove of %s, displayed an error ignore it" % u) if verbose: print(" insmode the %s module" % u) subprocess.call(['sudo', 'modprobe', u]) else: if u == 'vfio-pci': ret = subprocess.call(['sudo', 'rmmod', u]) if ret > 0: print(" Remove of %s, displayed an error ignore it" % u) if verbose: print(" modprobe the %s module" % u) subprocess.call(['sudo', 'modprobe', u]) for i in range(0, nb_sockets): fn = (hugepage_path % i) if verbose: print(" Set %d socket to %d hugepages" % (i, nb_hugepages)) subprocess.call(['sudo', 'sh', '-c', 'eval', 'echo %s > %s' % (nb_hugepages, fn)]) # locate the binding tool if os.path.exists("/usr/local/bin/dpdk-devbind.py"): script ="/usr/local/bin/dpdk-devbind.py" else: if os.path.exists("%s/bin/dpdk-devbind.py" % sdk): script = "%s/bin/dpdk-devbind.py" % sdk else: if os.path.exists("%s/usertools/dpdk-devbind.py" % sdk): script = "%s/usertools/dpdk-devbind.py" % sdk else: err_exit("Error: Failed to find dpdk-devbind.py or dpdk_nic_bind.py") # build up the system command line to be executed args = [] add_setup_options('exec', args) args.extend([script]) args.append('-b') args.append(cfg.setup['uio']) add_setup_options('devices', args) if verbose: print(" Bind following devices to DPDK:") for a in cfg.setup['devices']: print(" %s" % a) print(args) subprocess.call(args) def parse_args(): ''' Parse the command arguments ''' global run_flag, verbose, norun run_flag = True verbose = False norun = False cfg_file = "./cfg/default.cfg" if len(sys.argv) <= 1: print("*** Pick one of the following config files\n") show_configs() try: opts, args = getopt.getopt(sys.argv[1:], "hulsvn", ["help", "usage", "list", "setup", "verbose", "no-run", ]) except getopt.GetoptError as error: print(str(error)) usage() for opt, _ in opts: if opt == "--help" or opt == "-h": usage() if opt == "--usage" or opt == "-u": usage() if opt == "--list" or opt == "-l": show_configs() if opt == "--setup" or opt == "-s": run_flag = False if opt == "--verbose" or opt == "-v": verbose = True if opt == "--no-run" or opt == "-n": norun = True if not args or len(args) > 1: usage() fn = find_file(args[0], '.cfg') if not fn: f = args[0] if os.path.splitext(args[0])[1] != '.cfg': f = args[0] + '.cfg' print("*** Config file '%s' not found" % f) print(" Make sure you are running this command in pktgen top directory") print(" e.g. cd Pktgen-DPDK; ./tools/run.py default") show_configs() else: cfg_file = fn return cfg_file def main(): '''program main function''' global sdk, target sdk = os.getenv("RTE_SDK") target = os.getenv("RTE_TARGET") print(">>> sdk '%s', target '%s'" % (sdk, target)) cfg_file = parse_args() if run_flag: run_cfg(cfg_file) else: setup_cfg(cfg_file) if __name__ == "__main__": main() ================================================ FILE: tools/sudoGDB ================================================ # # Wrapper for running a CNDP application with GDB. Normally used with vscode debugging. # CNDP needs to run in sudo or privileged mode, use this wrapper # to run a application if needing to debug the application. dir=`pwd` abs_path=${dir}/usr/local/lib/x86_64-linux-gnu rel_path=${dir}/../lib/x86_64-linux-gnu ld_paths=${abs_path}:${rel_path} sudo LD_LIBRARY_PATH=${ld_paths} /usr/bin/gdb "$@" ================================================ FILE: tools/vfio-setup.sh ================================================ echo 0 > /sys/bus/pci/devices/0000\:82\:00.0/sriov_numvfs sleep 2 echo 2 > /sys/bus/pci/devices/0000\:82\:00.0/sriov_numvfs ifconfig ens260f0 down ip link set dev ens260f0 vf 0 mac 00:11:22:33:44:01 ip link set dev ens260f0 vf 1 mac 00:11:22:33:44:02 ../dpdk/usertools/dpdk-devbind.py -b vfio-pci 82:02.0 82:02.1