├── .github ├── linters │ ├── .golangci.yml │ └── .yaml-lint.yml └── workflows │ ├── build-dpdk.yml │ ├── build-go-p4pack.yml │ ├── build-p4c.yml │ ├── go-build-lint-test.yml │ └── linter.yml ├── .gitignore ├── .golangci.yml ├── CONTRIBUTERS ├── LICENSE ├── README.md ├── build ├── dpdk-pipeline │ ├── Dockerfile │ ├── build.sh │ └── scripts │ │ ├── build.sh │ │ ├── os_ver_details.sh │ │ └── set_hugepages.sh ├── dpdk │ ├── Dockerfile │ ├── apply-patches.sh │ ├── build.sh │ ├── install.sh │ └── patches │ │ └── 007-Fix-pipeline-structs-initialization.patch ├── go-p4pack │ ├── Dockerfile │ └── build.sh └── p4c │ ├── Dockerfile │ └── build.sh ├── cmd ├── dpdkinfra │ ├── main.go │ ├── root.go │ └── shellroot.go ├── gnmi_cli │ ├── README.md │ ├── gnmi_cli.go │ └── gnmi_cli_test.go └── p4vswitch │ ├── README.md │ └── main.go ├── doc ├── README.md └── drawings │ ├── design.drawio.svg │ └── example-network.drawio.svg ├── examples ├── default │ ├── bf-rt.json │ ├── build.sh │ ├── config-ethdev.json │ ├── config.json │ ├── context.json │ ├── createenv.sh │ ├── default.p4 │ ├── default.spec │ └── p4info.proto.txt ├── ipdk-simple_l3 │ ├── build.sh │ ├── l3_table.txt │ ├── p4info.proto.txt │ ├── simple_l3.cli │ ├── simple_l3.p4 │ └── simple_l3.spec ├── linux_networking │ ├── README_LINUX_NETWORKING.md │ ├── README_LINUX_NETWORKING_WITH_ECMP.md │ ├── bf-rt.json │ ├── build.sh │ ├── config.json │ ├── context.json │ ├── headers.p4 │ ├── linux_networking.p4 │ ├── linux_networking.spec │ ├── lnw_ct.p4 │ ├── metadata.p4 │ ├── p4info.proto.txt │ ├── parser.p4 │ ├── routing.p4 │ ├── topology_linux_networking.PNG │ ├── topology_linux_networking_with_ecmp.PNG │ └── tunnel.p4 ├── vxlan-dpdk │ ├── vxlan.cli │ ├── vxlan.spec │ ├── vxlan_pcap.cli │ ├── vxlan_table.py │ └── vxlan_table.txt └── vxlan │ ├── build.sh │ ├── old │ ├── p4info.proto.txt │ ├── vxlan.cli │ ├── vxlan.p4 │ ├── vxlan.spec │ └── vxlan_table.txt │ ├── p4info.proto.txt │ ├── tools │ ├── packet.pcap │ ├── packet.txt │ ├── vxlan_pcap.cli │ └── vxlan_table.py │ ├── vxlan.cli │ ├── vxlan.p4 │ ├── vxlan.spec │ └── vxlan_table.txt ├── go-p4pack.sh ├── go.mod ├── go.sum └── pkg ├── cli └── cli.go ├── config ├── config.go └── config_test.go ├── dpdkinfra ├── cli │ ├── common.go │ ├── interface.go │ ├── interfacecreate.go │ ├── interfacedevice.go │ ├── interfaceshow.go │ ├── interfacestats.go │ ├── pipeline.go │ ├── pktmbuf.go │ └── root.go ├── config │ ├── config.go │ ├── deviceconfig.go │ ├── interfaceconfig.go │ ├── pipelineconfig.go │ └── pktmbufconfig.go ├── dpdkinfra.go ├── pipemngr │ └── pipemngr.go ├── portmngr │ ├── cgo-shared.go │ └── portmngr.go └── store │ ├── kvstore │ ├── kvstore.go │ └── kvstore_test.go │ └── store.go ├── dpdkswx ├── README.md ├── common │ ├── cgo-shared.go │ └── common.go ├── device │ └── device.go ├── dpdkswx.go ├── eal │ ├── cgo-shared.go │ └── eal.go ├── ethdev │ ├── cgo-shared.go │ └── ethdev.go ├── netlink │ └── netlink.go ├── pipeline │ ├── actions.go │ ├── cgo-shared.go │ ├── info.go │ ├── learner.go │ ├── meter.go │ ├── pipeline-ctl.go │ ├── pipeline.go │ ├── register.go │ ├── stats.go │ └── tables.go ├── pktmbuf │ ├── cgo-shared.go │ └── pktmbuf.go ├── ring │ ├── cgo-shared.go │ └── ring.go ├── sourcesink │ ├── cgo-shared.go │ ├── sink.go │ └── source.go ├── swxruntime │ ├── cgo-shared.go │ ├── runtime.go │ ├── thread.c │ ├── thread.go │ └── thread.h └── tap │ ├── cgo-shared.go │ └── tap.go ├── flowtest ├── README.md ├── cli │ ├── root.go │ ├── start.go │ └── stop.go ├── config.go ├── config_test.go ├── flowset.go ├── flowtest.go ├── list.go └── list_test.go ├── gnmi ├── README.md ├── generate.go ├── generate.sh ├── gnmi.go └── oc │ ├── acl │ └── acl-0.go │ ├── enum.go │ ├── enum_map.go │ ├── interfaces │ └── interfaces-0.go │ ├── keychain │ └── keychain-0.go │ ├── lacp │ └── lacp-0.go │ ├── lldp │ └── lldp-0.go │ ├── networkinstance │ └── networkinstance-0.go │ ├── ocpath │ └── ocpath.go │ ├── platform │ └── platform-0.go │ ├── qos │ └── qos-0.go │ ├── routingpolicy │ └── routingpolicy-0.go │ ├── schema.go │ ├── structs-0.go │ ├── system │ └── system-0.go │ └── union.go ├── kernel └── kernel.go ├── logging ├── cli │ ├── get.go │ ├── list.go │ ├── root.go │ └── set.go ├── config.go ├── kafka.go ├── level.go ├── logger.go ├── logger_test.go ├── output.go └── register.go ├── p4device ├── README.md ├── p4device.go └── p4device_test.go ├── p4rt ├── README.md └── p4rt.go ├── pcidevices ├── README.md ├── bind.go ├── bind_test.go ├── cli │ ├── pci.go │ └── root.go ├── common.go ├── const.go └── pci.go ├── signals └── signals.go ├── sshshell ├── auth.go ├── config.go ├── key.go ├── shell.go ├── ssh.go └── sshshell.go └── tdi ├── README.md ├── targets └── p4-dpdk-target │ └── p4-dpdk-target.go └── tdi.go /.github/linters/.golangci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ######################### 3 | ######################### 4 | ## Golang Linter rules ## 5 | ######################### 6 | ######################### 7 | 8 | # configure golangci-lint 9 | # see https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml 10 | run: 11 | timeout: 30m 12 | skip-dirs: 13 | - gnmi/oc 14 | issues: 15 | exclude-rules: 16 | - path: _test\.go 17 | linters: 18 | - dupl 19 | - gosec 20 | - goconst 21 | linters: 22 | disable-all: true 23 | enable: 24 | - gosec 25 | - unconvert 26 | - goconst 27 | - goimports 28 | - gofmt 29 | - gocritic 30 | - govet 31 | - revive 32 | - staticcheck 33 | - unconvert 34 | - unparam 35 | - unused 36 | - varcheck 37 | - wastedassign 38 | - whitespace 39 | linters-settings: 40 | errcheck: 41 | # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; 42 | # default is false: such cases aren't reported by default. 43 | check-blank: true 44 | govet: 45 | # report about shadowed variables 46 | check-shadowing: false 47 | maligned: 48 | # print struct with more effective memory layout or not, false by default 49 | suggest-new: true 50 | gocritic: 51 | disabled-checks: 52 | - singleCaseSwitch 53 | - appendAssign 54 | -------------------------------------------------------------------------------- /.github/linters/.yaml-lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ########################################### 3 | # These are the rules used for # 4 | # linting all the yaml files in the stack # 5 | # NOTE: # 6 | # You can disable line with: # 7 | # # yamllint disable-line # 8 | ########################################### 9 | rules: 10 | braces: 11 | level: warning 12 | min-spaces-inside: 0 13 | max-spaces-inside: 0 14 | min-spaces-inside-empty: 1 15 | max-spaces-inside-empty: 5 16 | brackets: 17 | level: warning 18 | min-spaces-inside: 0 19 | max-spaces-inside: 0 20 | min-spaces-inside-empty: 1 21 | max-spaces-inside-empty: 5 22 | colons: 23 | level: warning 24 | max-spaces-before: 0 25 | max-spaces-after: 1 26 | commas: 27 | level: warning 28 | max-spaces-before: 0 29 | min-spaces-after: 1 30 | max-spaces-after: 1 31 | comments: disable 32 | comments-indentation: disable 33 | document-end: disable 34 | document-start: 35 | level: warning 36 | present: true 37 | empty-lines: 38 | level: warning 39 | max: 2 40 | max-start: 0 41 | max-end: 0 42 | hyphens: 43 | level: warning 44 | max-spaces-after: 1 45 | indentation: 46 | level: warning 47 | spaces: consistent 48 | indent-sequences: true 49 | check-multi-line-strings: false 50 | key-duplicates: enable 51 | line-length: 52 | level: warning 53 | max: 120 54 | allow-non-breakable-words: true 55 | allow-non-breakable-inline-mappings: true 56 | new-line-at-end-of-file: disable 57 | new-lines: 58 | type: unix 59 | trailing-spaces: disable -------------------------------------------------------------------------------- /.github/workflows/build-dpdk.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'build-dpdk' 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | paths: 8 | - 'build/dpdk/**' 9 | pull_request: 10 | branches: [main] 11 | paths: 12 | - 'build/dpdk/**' 13 | workflow_dispatch: 14 | 15 | concurrency: 16 | # if workflow for PR or push is already running stop it, and start new one 17 | group: build-dpdk-${{ github.ref }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | build_containers: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v3 27 | 28 | - name: Login to GitHub Container Registry 29 | if: github.event_name != 'pull_request' 30 | uses: docker/login-action@v2 31 | with: 32 | registry: ghcr.io 33 | username: ${{ github.repository_owner }} 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: DPDK image build and push 37 | if: github.event_name != 'pull_request' 38 | run: | 39 | build/dpdk/build.sh --push 40 | 41 | - name: DPDK image build (TODO add export later) 42 | if: github.event_name == 'pull_request' 43 | run: | 44 | build/dpdk/build.sh -------------------------------------------------------------------------------- /.github/workflows/build-go-p4pack.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'build-go-p4pack' 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | paths: 8 | - 'build/go-p4pack/**' 9 | pull_request: 10 | branches: [main] 11 | paths: 12 | - 'build/go-p4pack/**' 13 | schedule: 14 | - cron: '1 1 * * *' # Run every day at 01:01 AM (UTC) 15 | workflow_dispatch: 16 | 17 | concurrency: 18 | # if workflow for PR or push is already running stop it, and start new one 19 | group: build-go-p4pack-${{ github.ref }} 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | build_containers: 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v3 29 | 30 | - name: Login to GitHub Container Registry 31 | if: github.event_name != 'pull_request' 32 | uses: docker/login-action@v2 33 | with: 34 | registry: ghcr.io 35 | username: ${{ github.repository_owner }} 36 | password: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | - name: Go-P4Pack image build and push 39 | if: github.event_name != 'pull_request' 40 | run: | 41 | build/go-p4pack/build.sh --push 42 | 43 | - name: Go-P4Pack image build (TODO add export later) 44 | if: github.event_name == 'pull_request' 45 | run: | 46 | build/go-p4pack/build.sh -------------------------------------------------------------------------------- /.github/workflows/build-p4c.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'build-p4c' 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | paths: 8 | - 'build/p4c/**' 9 | pull_request: 10 | branches: [main] 11 | paths: 12 | - 'build/p4c/**' 13 | schedule: 14 | - cron: '1 1 * * *' # Run every day at 01:01 AM (UTC) 15 | workflow_dispatch: 16 | 17 | concurrency: 18 | # if workflow for PR or push is already running stop it, and start new one 19 | group: build-p4c-${{ github.ref }} 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | build_containers: 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v3 29 | 30 | - name: Login to GitHub Container Registry 31 | if: github.event_name != 'pull_request' 32 | uses: docker/login-action@v2 33 | with: 34 | registry: ghcr.io 35 | username: ${{ github.repository_owner }} 36 | password: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | - name: DPDK image build and push 39 | if: github.event_name != 'pull_request' 40 | run: | 41 | build/p4c/build.sh --push 42 | 43 | - name: DPDK image build (TODO add export later) 44 | if: github.event_name == 'pull_request' 45 | run: | 46 | build/p4c/build.sh -------------------------------------------------------------------------------- /.github/workflows/go-build-lint-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Go-Build-Lint-Test 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | golangci: 12 | name: Build-Lint-Test 13 | runs-on: ubuntu-latest 14 | env: 15 | DPDK_VERSION: 22.07 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | - name: Generate cache keys 20 | id: get_ref_keys 21 | run: | 22 | echo "ccache=ccache-dpdk-${{ env.DPDK_VERSION }}" >> "$GITHUB_OUTPUT" 23 | - name: Retrieve ccache cache 24 | uses: actions/cache@v3 25 | with: 26 | path: ~/.ccache 27 | key: ${{ steps.get_ref_keys.outputs.ccache }}-${{ github.run_id }} 28 | restore-keys: 29 | ${{ steps.get_ref_keys.outputs.ccache }}- 30 | - name: Set up Go 31 | uses: actions/setup-go@v3 32 | with: 33 | go-version: 1.18.x 34 | - name: Set DPDK location 35 | run: | 36 | echo "DPDK_HOME=$GITHUB_WORKSPACE/dpdk" >> "$GITHUB_ENV" 37 | - name: build-dpdk 38 | run: | 39 | cd build/dpdk 40 | ./install.sh 41 | cd ~ 42 | - name: print-ccache-stats 43 | run: ccache -s 44 | - name: golangci-lint 45 | uses: golangci/golangci-lint-action@v3 46 | with: 47 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 48 | version: latest 49 | 50 | # Optional: working directory, useful for monorepos 51 | # working-directory: somedir 52 | 53 | # Optional: golangci-lint command line arguments. 54 | # args: --issues-exit-code=0 55 | 56 | # Optional: show only new issues if it's a pull request. The default value is `false`. 57 | # only-new-issues: true 58 | 59 | # Optional: if set to true then the all caching functionality will be complete disabled, 60 | # takes precedence over all other caching options. 61 | # skip-cache: true 62 | 63 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 64 | # skip-pkg-cache: true 65 | 66 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 67 | # skip-build-cache: true 68 | - name: Build-dpdkinfra 69 | run: go build -v ./cmd/dpdkinfra/... 70 | - name: Test 71 | run: go test -v -coverprofile=profile.cov ./... 72 | - name: Send coverage 73 | uses: shogo82148/actions-goveralls@v1 74 | with: 75 | path-to-profile: profile.cov -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lint Code Base 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | # Remove the line above to run when pushing to master 8 | pull_request: 9 | branches: [main] 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | name: Lint Code Base 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout Code 19 | uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | - name: Lint Code Base 23 | uses: github/super-linter/slim@v4 24 | env: 25 | VALIDATE_ALL_CODEBASE: false 26 | VALIDATE_JSCPD: false 27 | VALIDATE_GO: false 28 | VALIDATE_CLANG_FORMAT: false 29 | VALIDATE_CPP: false 30 | IGNORE_GENERATED_FILES: true 31 | DEFAULT_BRANCH: main 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/launch.json 2 | hostkey 3 | __debug_bin 4 | test.log 5 | zoldsrc -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | ./.github/linters/.golangci.yml -------------------------------------------------------------------------------- /CONTRIBUTERS: -------------------------------------------------------------------------------- 1 | Sander Tolsma (github@tolsma.net) -------------------------------------------------------------------------------- /build/dpdk-pipeline/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2022 - Sander Tolsma. All rights reserved 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | ARG BASE_IMG=${BASE_IMG} 5 | 6 | FROM ${BASE_IMG} AS base 7 | ARG BASE_IMG 8 | 9 | SHELL ["/bin/bash", "-c"] 10 | 11 | RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections 12 | 13 | # Installing dependent packages required for DPDK Build 14 | RUN apt-get update 15 | 16 | RUN apt-get install -y apt-utils \ 17 | git \ 18 | libtool \ 19 | clang \ 20 | gcc \ 21 | g++ \ 22 | autoconf \ 23 | automake \ 24 | autoconf-archive \ 25 | libconfig++-dev \ 26 | libgc-dev \ 27 | unifdef \ 28 | libffi-dev \ 29 | libboost-iostreams-dev \ 30 | libboost-graph-dev \ 31 | llvm \ 32 | pkg-config \ 33 | flex libfl-dev \ 34 | zlib1g-dev \ 35 | python \ 36 | python3-setuptools \ 37 | python3-pip \ 38 | python3-wheel \ 39 | python3-cffi \ 40 | sudo && \ 41 | if [ "$BASE_IMG" = "ubuntu:18.04" ] ; then \ 42 | apt-get -y install python-pip; \ 43 | else \ 44 | apt-get -y install pip; \ 45 | fi && \ 46 | apt-get -y clean all 47 | 48 | # Installing all PYTHON packages 49 | RUN python3 -m pip install --no-cache-dir --upgrade pip && \ 50 | python3 -m pip install --no-cache-dir cmake>=3.15.0 \ 51 | pyelftools \ 52 | meson==0.59.4 \ 53 | ninja>=1.8.2 54 | 55 | # Build dpdk-infra driver 56 | FROM base AS build-dpdk-infra 57 | WORKDIR /root 58 | COPY ./scripts scripts 59 | RUN /root/scripts/build.sh /root 60 | 61 | # Create lean dpdk-infra image based on Ubuntu base image 62 | FROM ${BASE_IMG} AS dpdk-infra 63 | LABEL maintainer="github@tolsma.net" 64 | SHELL ["/bin/bash", "-c"] 65 | RUN apt-get update && apt-get install -y libatomic1 libbsd0 && apt-get -y clean all 66 | COPY --from=build-dpdk-infra /root/dpdk_infra ./ 67 | COPY ./scripts scripts 68 | -------------------------------------------------------------------------------- /build/dpdk-pipeline/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Copyright (C) 2022 Sander Tolsma 3 | #SPDX-License-Identifier: Apache-2.0 4 | 5 | set -eo pipefail 6 | 7 | function echoerr { 8 | >&2 echo "$@" 9 | } 10 | 11 | function print_usage { 12 | echoerr "Usage: $0 [--push]" 13 | } 14 | 15 | PUSH=false 16 | while [[ $# -gt 0 ]] 17 | do 18 | key="$1" 19 | case $key in 20 | --push) 21 | PUSH=true 22 | shift 23 | ;; 24 | -h|--help) 25 | print_usage 26 | exit 0 27 | ;; 28 | *) # unknown option 29 | echoerr "Unknown option $1" 30 | exit 1 31 | ;; 32 | esac 33 | done 34 | 35 | if [ -z "$UBUNTU_VERSION" ]; then 36 | UBUNTU_VERSION=20.04 37 | fi 38 | 39 | THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 40 | 41 | pushd $THIS_DIR > /dev/null 42 | docker pull "ubuntu:$UBUNTU_VERSION" 43 | docker build -t stolsma/dpdk-infra --build-arg BASE_IMG=ubuntu:"$UBUNTU_VERSION" . 44 | if $PUSH; then 45 | docker push stolsma/dpdk-infra 46 | fi 47 | popd > /dev/null -------------------------------------------------------------------------------- /build/dpdk-pipeline/scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Copyright (C) 2022 Sander Tolsma 3 | #SPDX-License-Identifier: Apache-2.0 4 | 5 | set -e 6 | 7 | # shellcheck source=build/dpdk-pipeline/scripts/os_ver_details.sh 8 | . scripts/os_ver_details.sh 9 | get_os_ver_details 10 | 11 | if [ -z "$1" ] 12 | then 13 | echo "-Missing mandatory arguments:" 14 | echo " - Usage: ./build.sh " 15 | return 1 16 | fi 17 | 18 | WORKDIR=$1 19 | cd "${WORKDIR}" || exit 20 | 21 | echo "Removing p4pipeline directory if it already exists" 22 | if [ -d "p4pipeline" ]; then rm -Rf p4pipeline; fi 23 | mkdir "$1/p4pipeline" && cd "$1/p4pipeline" || exit 24 | #..Setting Environment Variables..# 25 | echo "Exporting Environment Variables....." 26 | export SDE="${PWD}" 27 | export SDE_INSTALL="$SDE/install" 28 | mkdir "$SDE_INSTALL" || exit 29 | 30 | #...Package Config Path...# 31 | if [ "${OS}" = "Ubuntu" ] || [ "${VER}" = "20.04" ] ; then 32 | export PKG_CONFIG_PATH=${SDE_INSTALL}/lib/x86_64-linux-gnu/pkgconfig 33 | else 34 | export PKG_CONFIG_PATH=${SDE_INSTALL}/lib64/pkgconfig 35 | fi 36 | 37 | #..Runtime Path...# 38 | export LD_LIBRARY_PATH=$SDE_INSTALL/lib 39 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$SDE_INSTALL/lib64 40 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib64 41 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib 42 | 43 | echo "SDE environment variable" 44 | echo "$SDE" 45 | echo "$SDE_INSTALL" 46 | echo "$PKG_CONFIG_PATH" 47 | 48 | #Read the number of CPUs in a system and derive the NUM threads 49 | get_num_cores 50 | echo "Number of Parallel threads used: $NUM_THREADS ..." 51 | echo "" 52 | 53 | cd "$SDE" || exit 54 | echo "Removing p4vswitch repository if it already exists" 55 | if [ -d "p4vswitch" ]; then rm -Rf p4vswitch; fi 56 | echo "Compiling dpdk-pipeline" 57 | git clone https://github.com/stolsma/p4vswitch.git p4vswitch 58 | pushd "$SDE/p4vswitch" 59 | git checkout main 60 | git submodule update --init --recursive 61 | 62 | # compile dpdk-pipeline 63 | cd src 64 | ./apply_patch.sh > /dev/null 2>&1 65 | cd dpdk_src 66 | meson -Dexamples=pipeline build 67 | cd build 68 | ninja 69 | ninja install 70 | cd ../.. 71 | 72 | # create special dpdk-pipeline version 73 | echo "Compiling dpdk_infra" 74 | make -C infra install_dir=/root 75 | 76 | set +e -------------------------------------------------------------------------------- /build/dpdk-pipeline/scripts/os_ver_details.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Copyright (C) 2021 Intel Corporation 3 | #SPDX-License-Identifier: Apache-2.0 4 | 5 | set -e 6 | 7 | get_os_ver_details() { 8 | if [ -f /etc/os-release ]; then 9 | # freedesktop.org and systemd 10 | # shellcheck source=/dev/null 11 | . /etc/os-release 12 | OS=$NAME 13 | VER=$VERSION_ID 14 | elif type lsb_release >/dev/null 2>&1; then 15 | # linuxbase.org 16 | OS=$(lsb_release -si) 17 | VER=$(lsb_release -sr) 18 | elif [ -f /etc/lsb-release ]; then 19 | # For some versions of Debian/Ubuntu without lsb_release command 20 | # shellcheck source=/dev/null 21 | . /etc/lsb-release 22 | OS=$DISTRIB_ID 23 | VER=$DISTRIB_RELEASE 24 | elif [ -f /etc/debian_version ]; then 25 | # Older Debian/Ubuntu/etc. 26 | OS=Debian 27 | VER=$(cat /etc/debian_version) 28 | else 29 | # Fall back to uname, e.g. "Linux ", also works for BSD, etc. 30 | OS=$(uname -s) 31 | export OS 32 | VER=$(uname -r) 33 | export VER 34 | fi 35 | } 36 | 37 | # 38 | # NOTE: We set NUM_THREADS to NUM_CORES / 4 to ensure the build process doesn't overrun 39 | # the amount of memory on the build host. It's not an exact science, but it helps 40 | # prevent the OOM process from killing compilers and causing issues with the 41 | # build. 42 | # 43 | get_num_cores() { 44 | # First check if NUM_CORES and NUM_THREADS are already set, and exit if so. 45 | if [[ -n $NUM_CORES && -n $NUM_THREADS ]] 46 | then 47 | echo "NUM_CORES and NUM_THREADS already set." 48 | return 49 | fi 50 | 51 | nproc_exist=$(command -v nproc) 52 | if [ -n "$nproc_exist" ]; 53 | then 54 | NUM_CORES=$(nproc --all) 55 | echo "Num cores on a system: $NUM_CORES" 56 | if [ "$NUM_CORES" -gt 4 ] 57 | then 58 | NUM_THREADS=$((NUM_CORES / 4)) 59 | NUM_THREADS=-j$NUM_THREADS 60 | else 61 | NUM_THREADS=-j${NUM_CORES} 62 | fi 63 | else 64 | NUM_CORES=1 65 | NUM_THREADS=-j1 66 | fi 67 | } -------------------------------------------------------------------------------- /build/dpdk-pipeline/scripts/set_hugepages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Copyright (C) 2021 Intel Corporation 3 | #SPDX-License-Identifier: Apache-2.0 4 | 5 | #...Setting Hugepages...# 6 | mkdir -p /mnt/huge 7 | if [ "$(mount | grep hugetlbfs)" == "" ] 8 | then 9 | mount -t hugetlbfs nodev /mnt/huge 10 | fi 11 | 12 | if [ -e /etc/fstab ]; then 13 | if [ "$(grep huge < /etc/fstab)" == "" ] 14 | then 15 | echo -e "nodev /mnt/huge hugetlbfs\n" >> /etc/fstab 16 | fi 17 | fi 18 | 19 | # Get pagesize in MegaBytes, take only 1st result (head -1): 20 | pagesizeM=$(grep hugetlbfs < /proc/mounts | head -1) 21 | # Remove Prefix of = from: hugetlbfs /dev/hugepages hugetlbfs rw,relatime,pagesize=512M 0 0 22 | pagesizeM=${pagesizeM#*=} 23 | # Remove Suffix of M from: hugetlbfs /dev/hugepages hugetlbfs rw,relatime,pagesize=512M 0 0 24 | pagesizeM=${pagesizeM%M*} 25 | 26 | # 2 GB Total size 27 | total_sizeM=2048 28 | num_pages=$(( "$total_sizeM" / "$pagesizeM" )) 29 | pagesizeKB=$(( "$pagesizeM" * 1024 )) 30 | 31 | if [ "$(grep nr_hugepages < /etc/sysctl.conf)" == "" ] 32 | then 33 | echo "vm.nr_hugepages = ${num_pages}" >> /etc/sysctl.conf 34 | #sysctl -p /etc/sysctl.conf 35 | fi 36 | 37 | # 38 | # Check if the kernel/mm version of hugepages exists, and set hugepages if so. 39 | # 40 | if [ -d "/sys/kernel/mm/hugepages/hugepages-${pagesizeKB}kB" ] ; then 41 | echo "${num_pages}" | tee "/sys/kernel/mm/hugepages/hugepages-${pagesizeKB}kB/nr_hugepages" 42 | fi 43 | 44 | # 45 | # Check if the node version of hugepages exists, and set hugepages if so. 46 | # 47 | if [ -d "/sys/devices/system/node/node0/hugepages/hugepages-${pagesizeKB}kB" ] ; then 48 | echo "${num_pages}" | sudo tee "/sys/devices/system/node/node0/hugepages/hugepages-${pagesizeKB}kB/nr_hugepages" 49 | fi 50 | if [ -d "/sys/devices/system/node/node1/hugepages/hugepages-${pagesizeKB}kB" ] ; then 51 | echo "${num_pages}" | sudo tee "/sys/devices/system/node/node1/hugepages/hugepages-${pagesizeKB}kB/nr_hugepages" 52 | fi 53 | -------------------------------------------------------------------------------- /build/dpdk/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG UBUNTU_VERSION 2 | 3 | FROM ubuntu:$UBUNTU_VERSION 4 | 5 | ARG DPDK_VERSION 6 | ARG DPDK_STUFF=dpdk-$DPDK_VERSION.tar.xz 7 | ARG DPDK_HOME=/dpdk 8 | 9 | WORKDIR / 10 | COPY install.sh /scripts/ 11 | COPY apply-patches.sh /scripts/ 12 | COPY patches /scripts/patches/ 13 | 14 | WORKDIR /scripts 15 | RUN ./install.sh && rm -Rf /root/.ccache 16 | -------------------------------------------------------------------------------- /build/dpdk/apply-patches.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## 3 | ## Copyright(c) 2021 Intel Corporation. 4 | ## Copyright(c) 2022 Sander Tolsma. 5 | ## 6 | ## Licensed under the Apache License, Version 2.0 (the "License"); 7 | ## you may not use this file except in compliance with the License. 8 | ## You may obtain a copy of the License at 9 | ## 10 | ## http://www.apache.org/licenses/LICENSE-2.0 11 | ## 12 | ## Unless required by applicable law or agreed to in writing, software 13 | ## distributed under the License is distributed on an "AS IS" BASIS, 14 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ## See the License for the specific language governing permissions and 16 | ## limitations under the License. 17 | ## 18 | 19 | apply_patch() 20 | { 21 | local PATCH_FILES=(007-Fix-pipeline-structs-initialization.patch) 22 | 23 | CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 24 | 25 | DPDK_HOME=$1 26 | readonly DEFAULT_DPDK_HOME="${CURRENT_PATH}"/dpdk 27 | DPDK_SRC_PATH=${DPDK_HOME:-$DEFAULT_DPDK_HOME} 28 | DPDK_PATCH_PATH="${CURRENT_PATH}"/patches 29 | DPDK_SRC_GIT_FILE="${DPDK_SRC_PATH}"/.git 30 | 31 | echo ******************************************************************************* 32 | echo Patch script: 33 | echo "DPDK Home : $DPDK_HOME" 34 | echo "Patch DPDK SRC path : $DPDK_SRC_PATH" 35 | echo "Patch SRC path : $DPDK_PATCH_PATH" 36 | echo "Src .git file : $DPDK_SRC_GIT_FILE" 37 | echo 38 | 39 | # Skip patching if branch is already compiled (i.e. .../build dir exists). 40 | if [ -d "${DPDK_SRC_PATH}/build" ]; then 41 | exit 1; 42 | fi 43 | 44 | # Need to check .git in dpdk_src. There are some cases where we remove .git after patch 45 | # apply and start the compilation. 46 | if [ -e "${DPDK_SRC_GIT_FILE}" ]; then 47 | # Let's clean all the changes 48 | (cd "${DPDK_SRC_PATH}" || exit; git checkout ./*) 49 | 50 | # Validate and apply the patch 51 | for i in "${PATCH_FILES[@]}"; do 52 | echo "Try to apply: ${DPDK_PATCH_PATH}/${i}" 53 | if [ -e "${DPDK_PATCH_PATH}/${i}" ]; then 54 | cd "${DPDK_SRC_PATH}" || exit; 55 | git apply "${DPDK_PATCH_PATH}/${i}"; 56 | echo "Applied: ${DPDK_PATCH_PATH}/${i}" 57 | echo 58 | fi 59 | done 60 | else 61 | # This is special case where we don't have .git dir. For example downloaded and extracted source files 62 | # from tar. As a workaround we have to apply the patch using 63 | 64 | # Validate and apply the patch 65 | for i in "${PATCH_FILES[@]}"; do 66 | echo "Try to apply: ${DPDK_PATCH_PATH}/${i}" 67 | if [ -e "${DPDK_PATCH_PATH}/${i}" ]; then 68 | cd "${DPDK_SRC_PATH}" || exit; 69 | patch -p1 < "${DPDK_PATCH_PATH}/${i}"; 70 | echo "Applied: ${DPDK_PATCH_PATH}/${i}" 71 | echo 72 | fi 73 | done 74 | fi 75 | echo ******************************************************************************* 76 | } 77 | 78 | apply_patch "$@" 79 | -------------------------------------------------------------------------------- /build/dpdk/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | function echoerr { 6 | >&2 echo "$@" 7 | } 8 | 9 | function print_usage { 10 | echoerr "Usage: $0 [--push]" 11 | } 12 | 13 | PUSH=false 14 | while [[ $# -gt 0 ]] 15 | do 16 | key="$1" 17 | case $key in 18 | --push) 19 | PUSH=true 20 | shift 21 | ;; 22 | -h|--help) 23 | print_usage 24 | exit 0 25 | ;; 26 | *) # unknown option 27 | echoerr "Unknown option $1" 28 | exit 1 29 | ;; 30 | esac 31 | done 32 | 33 | readonly DEFAULT_UBUNTU_VERSION=20.04 34 | readonly DEFAULT_DPDK_VERSION=22.07 35 | readonly DEFAULT_IMAGE_NAME=ghcr.io/stolsma/dpdk-base 36 | 37 | export UBUNTU_VERSION=${UBUNTU_VERSION:-$DEFAULT_UBUNTU_VERSION} 38 | export DPDK_VERSION=${DPDK_VERSION:-$DEFAULT_DPDK_VERSION} 39 | export IMAGE_NAME=${IMAGE_NAME:-$DEFAULT_IMAGE_NAME} 40 | 41 | readonly DPDK_TAG=$IMAGE_NAME:dpdk-$DPDK_VERSION-ubuntu-$UBUNTU_VERSION 42 | 43 | echo Ubuntu version: "$UBUNTU_VERSION" 44 | echo DPDK version: "$DPDK_VERSION" 45 | echo DPDK Tag: "$DPDK_TAG" 46 | 47 | THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 48 | 49 | pushd "$THIS_DIR" > /dev/null 50 | docker pull "ubuntu:$UBUNTU_VERSION" 51 | docker build \ 52 | --build-arg UBUNTU_VERSION="$UBUNTU_VERSION" \ 53 | --build-arg DPDK_VERSION="$DPDK_VERSION" \ 54 | --progress=plain \ 55 | --tag "$DPDK_TAG" \ 56 | . 57 | if $PUSH; then 58 | docker push "$DPDK_TAG" 59 | fi 60 | popd > /dev/null 61 | -------------------------------------------------------------------------------- /build/dpdk/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # let alias work in non interactive bash shells! 4 | shopt -s expand_aliases 5 | 6 | # Builds are run as root in containers, no need for sudo 7 | [ "$(id -u)" != '0' ] || alias sudo= 8 | 9 | sudo apt-get -y update 10 | sudo DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \ 11 | build-essential \ 12 | libtool \ 13 | ccache \ 14 | pkg-config \ 15 | autoconf \ 16 | automake \ 17 | clang \ 18 | gcc \ 19 | g++ \ 20 | autoconf-archive \ 21 | libxml2-dev \ 22 | libdw-dev \ 23 | libbsd-dev \ 24 | libpcap-dev \ 25 | libibverbs-dev \ 26 | libcrypto++-dev \ 27 | libfdt-dev \ 28 | libjansson-dev \ 29 | libarchive-dev \ 30 | libnuma-dev \ 31 | python3-pip \ 32 | python3-pyelftools \ 33 | python3-setuptools \ 34 | python3-wheel\ 35 | ca-certificates \ 36 | pciutils \ 37 | kmod \ 38 | iperf \ 39 | iperf3 \ 40 | iproute2 \ 41 | wget \ 42 | xz-utils \ 43 | git \ 44 | net-tools \ 45 | iputils-ping \ 46 | sudo 47 | sudo pip3 install \ 48 | meson \ 49 | ninja 50 | sudo apt-get -qq clean 51 | sudo rm --recursive --force /var/lib/apt/lists/* /tmp/* /var/tmp/* 52 | 53 | # Set all arguments from default if not given 54 | readonly DEFAULT_DPDK_VERSION=22.07 55 | DPDK_VERSION=${DPDK_VERSION:-$DEFAULT_DPDK_VERSION} 56 | readonly DEFAULT_DPDK_STUFF=dpdk-$DPDK_VERSION.tar.xz 57 | DPDK_STUFF=${DPDK_STUFF:-$DEFAULT_DPDK_STUFF} 58 | readonly DEFAULT_DPDK_HOME=/dpdk 59 | DPDK_HOME=${DPDK_HOME:-$DEFAULT_DPDK_HOME} 60 | 61 | echo ******************************************************************************* 62 | echo Getting DPDK source files! 63 | echo "DPDK version: $DPDK_VERSION" 64 | echo "DPDK Tar : $DPDK_STUFF" 65 | echo "DPDK Home : $DPDK_HOME" 66 | echo 67 | 68 | # Download and extract the requested DPDK source version 69 | wget --quiet http://fast.dpdk.org/rel/"$DPDK_STUFF" 70 | mkdir --parents "$DPDK_HOME" 71 | tar --extract --file="$DPDK_STUFF" --directory="$DPDK_HOME" --strip-components 1 72 | rm --force "$DPDK_STUFF" 73 | 74 | echo 75 | echo ******************************************************************************* 76 | 77 | # Apply the needed patches on the DPDK source files 78 | ./apply-patches.sh "$DPDK_HOME" 79 | 80 | # Build DPDK 81 | pushd "$DPDK_HOME" > /dev/null || exit 82 | meson -Dcpu_instruction_set=generic build 83 | cd build || exit 84 | sed -e 's/RTE_CPUFLAG_AVX512BW\,//' -i rte_build_config.h 85 | sed -e 's/RTE_CPUFLAG_AVX512CD\,//' -i rte_build_config.h 86 | sed -e 's/RTE_CPUFLAG_AVX512DQ\,//' -i rte_build_config.h 87 | sed -e 's/RTE_CPUFLAG_AVX512F\,//' -i rte_build_config.h 88 | sed -e 's/RTE_CPUFLAG_AVX512VL\,//' -i rte_build_config.h 89 | ninja 90 | sudo ninja install 91 | sudo ldconfig 92 | popd > /dev/null || exit 93 | 94 | # And remove all DPDK source and build files 95 | rm --recursive --force "$DPDK_HOME" 96 | 97 | echo ******************************************************************************* 98 | echo DPDK Build and Install Ready! 99 | echo ******************************************************************************* 100 | -------------------------------------------------------------------------------- /build/dpdk/patches/007-Fix-pipeline-structs-initialization.patch: -------------------------------------------------------------------------------- 1 | From 8b9083a0c53786178ce9e5ef88efc1032a4d9840 Mon Sep 17 00:00:00 2001 2 | From: yjangra 3 | Date: Thu, 2 Jun 2022 11:49:43 +0530 4 | Subject: [PATCH] pipeline: Fix structs initialization 5 | 6 | Added reinitialization of structs. This fix the packet corruption 7 | by the same thread in the same packet buffer. 8 | 9 | Signed-off-by: Kamalakannan R 10 | Signed-off-by: Yogesh Jangra 11 | Acked-by: Cristian Dumitrescu 12 | --- 13 | lib/pipeline/rte_swx_pipeline_internal.h | 2 ++ 14 | 1 file changed, 2 insertions(+) 15 | 16 | diff --git a/lib/pipeline/rte_swx_pipeline_internal.h b/lib/pipeline/rte_swx_pipeline_internal.h 17 | index 5feee8eff6..c9c654b806 100644 18 | --- a/lib/pipeline/rte_swx_pipeline_internal.h 19 | +++ b/lib/pipeline/rte_swx_pipeline_internal.h 20 | @@ -2068,6 +2068,8 @@ __instr_hdr_emit_many_exec(struct rte_swx_pipeline *p __rte_unused, 21 | 22 | uint8_t *hi_ptr = t->structs[struct_id]; 23 | 24 | + t->structs[struct_id] = hi->ptr0; 25 | + 26 | if (!MASK64_BIT_GET(valid_headers, header_id)) { 27 | TRACE("[Thread %2u]: emit header %u (invalid)\n", 28 | p->thread_id, 29 | -- 30 | 2.25.1 31 | -------------------------------------------------------------------------------- /build/go-p4pack/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG DPDK_TAG 2 | 3 | # create the go-build environment 4 | # hadolint ignore=DL3006 5 | FROM $DPDK_TAG as go-build 6 | ARG GOLANG_VERSION 7 | 8 | # Install Go 9 | RUN wget --progress=dot:giga https://golang.org/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz \ 10 | && tar -C /usr/local -xzf go${GOLANG_VERSION}.linux-amd64.tar.gz \ 11 | && rm -f go${GOLANG_VERSION}.linux-amd64.tar.gz 12 | 13 | ENV PATH "$PATH:/usr/local/go/bin" 14 | 15 | # Build the go app #as dpdkinfra-build 16 | FROM go-build 17 | ARG GOP4PACK_HOME=/go-p4pack 18 | 19 | ENV GOPATH /go 20 | ENV CGO_CFLAGS_ALLOW .* 21 | ENV CGO_LDFLAGS_ALLOW .* 22 | ENV PKG_CONFIG_PATH /usr/local/share/pkgconfig:$PKG_CONFIG_PATH 23 | 24 | # hadolint ignore=DL3003 25 | RUN mkdir --parents $GOP4PACK_HOME/build \ 26 | && cd "$GOP4PACK_HOME/build" \ 27 | && git clone https://github.com/stolsma/go-p4pack.git \ 28 | && cd go-p4pack/ \ 29 | && go mod tidy \ 30 | && go build -v ./cmd/dpdkinfra/... \ 31 | && mv ./dpdkinfra $GOP4PACK_HOME \ 32 | && mv ./examples $GOP4PACK_HOME \ 33 | && mv ./README.md $GOP4PACK_HOME \ 34 | && cd $GOP4PACK_HOME \ 35 | && rm -Rf "$GOP4PACK_HOME/build" 36 | 37 | WORKDIR ${GOP4PACK_HOME} 38 | -------------------------------------------------------------------------------- /build/go-p4pack/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | function echoerr { 6 | >&2 echo "$@" 7 | } 8 | 9 | function print_usage { 10 | echoerr "Usage: $0 [--push]" 11 | } 12 | 13 | PUSH=false 14 | while [[ $# -gt 0 ]] 15 | do 16 | key="$1" 17 | case $key in 18 | --push) 19 | PUSH=true 20 | shift 21 | ;; 22 | -h|--help) 23 | print_usage 24 | exit 0 25 | ;; 26 | *) # unknown option 27 | echoerr "Unknown option $1" 28 | exit 1 29 | ;; 30 | esac 31 | done 32 | 33 | readonly DEFAULT_DPDK_TAG=ghcr.io/stolsma/dpdk-base:dpdk-22.07-ubuntu-20.04 34 | readonly DEFAULT_IMAGE_NAME=ghcr.io/stolsma/go-p4pack 35 | readonly DEFAULT_GOP4PACK_VERSION=1.0rc1 36 | readonly DEFAULT_GOLANG_VERSION=1.18 37 | 38 | export DPDK_TAG=${DPDK_TAG:-$DEFAULT_DPDK_TAG} 39 | export IMAGE_NAME=${IMAGE_NAME:-$DEFAULT_IMAGE_NAME} 40 | export GOP4PACK_VERSION=${GOP4PACK_VERSION:-$DEFAULT_GOP4PACK_VERSION} 41 | export GOLANG_VERSION=${GOLANG_VERSION:-$DEFAULT_GOLANG_VERSION} 42 | 43 | readonly INFRA_TAG=$IMAGE_NAME:$GOP4PACK_VERSION 44 | readonly INFRA_TAG_LATEST=$IMAGE_NAME:latest 45 | 46 | echo Golang version: "$GOLANG_VERSION" 47 | echo DPDK source Tag: "$DPDK_TAG" 48 | echo GOP4PACK version: "$GOP4PACK_VERSION" 49 | echo GOP4PACK Tag: "$INFRA_TAG" 50 | 51 | THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 52 | 53 | pushd "$THIS_DIR" > /dev/null 54 | docker build \ 55 | --no-cache \ 56 | --build-arg DPDK_TAG="$DPDK_TAG" \ 57 | --build-arg GOLANG_VERSION="$GOLANG_VERSION" \ 58 | --tag "$INFRA_TAG" \ 59 | --tag "$INFRA_TAG_LATEST" \ 60 | . 61 | if $PUSH; then 62 | docker push -a "$IMAGE_NAME" 63 | fi 64 | popd > /dev/null 65 | -------------------------------------------------------------------------------- /build/p4c/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG UBUNTU_VERSION=20.04 2 | FROM ubuntu:${UBUNTU_VERSION} as p4c-build 3 | 4 | ENV P4C_BUILD_DEPS bison \ 5 | build-essential \ 6 | libboost-dev \ 7 | libboost-iostreams-dev \ 8 | ca-certificates \ 9 | cmake \ 10 | libfl-dev \ 11 | libgc-dev \ 12 | git \ 13 | libgmp-dev \ 14 | protobuf-compiler \ 15 | libprotobuf-dev \ 16 | python3 17 | 18 | RUN apt-get update && \ 19 | DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends $P4C_BUILD_DEPS && \ 20 | apt-get clean && \ 21 | rm -rf /var/lib/apt/lists/* 22 | 23 | ARG P4C_URL=https://github.com/p4lang/p4c.git 24 | ARG P4C_REF=main 25 | 26 | WORKDIR / 27 | 28 | RUN git clone $P4C_URL 29 | 30 | WORKDIR /p4c/ 31 | 32 | RUN git checkout $P4C_REF 33 | RUN git submodule update --init --recursive 34 | 35 | #COPY apply-patches.sh / 36 | #COPY patches /patches/ 37 | 38 | #RUN /apply-patches.sh 39 | 40 | WORKDIR /p4c/build 41 | 42 | RUN cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_CXX_FLAGS:STRING=-O3 -DENABLE_GTESTS=OFF -DENABLE_P4C_GRAPHS=OFF .. && \ 43 | make && \ 44 | make DESTDIR=/output install 45 | 46 | FROM ubuntu:${UBUNTU_VERSION} 47 | LABEL maintainer="Sander Tolsma " 48 | LABEL description="A image for the p4lang/p4c compiler based on Ubuntu" 49 | 50 | # runtime dependencies 51 | # python is required by the p4c driver 52 | # python3-minimal is missing shlex 53 | ENV P4C_RUNTIME_DEPS libboost-iostreams1.71.0 \ 54 | libgc1c2 \ 55 | cpp \ 56 | libgmp10 \ 57 | python3 58 | 59 | RUN apt-get update && \ 60 | DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends $P4C_RUNTIME_DEPS && \ 61 | apt-get clean && \ 62 | rm -rf /var/lib/apt/lists/* 63 | 64 | COPY --from=p4c-build /output/usr/local /usr/local/ -------------------------------------------------------------------------------- /build/p4c/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | function echoerr { 6 | >&2 echo "$@" 7 | } 8 | 9 | function print_usage { 10 | echoerr "Usage: $0 [--push]" 11 | } 12 | 13 | PUSH=false 14 | while [[ $# -gt 0 ]] 15 | do 16 | key="$1" 17 | case $key in 18 | --push) 19 | PUSH=true 20 | shift 21 | ;; 22 | -h|--help) 23 | print_usage 24 | exit 0 25 | ;; 26 | *) # unknown option 27 | echoerr "Unknown option $1" 28 | exit 1 29 | ;; 30 | esac 31 | done 32 | 33 | readonly DEFAULT_UBUNTU_VERSION=20.04 34 | readonly DEFAULT_IMAGE_NAME=ghcr.io/stolsma/p4c 35 | 36 | export UBUNTU_VERSION=${UBUNTU_VERSION:-$DEFAULT_UBUNTU_VERSION} 37 | export IMAGE_NAME=${IMAGE_NAME:-$DEFAULT_IMAGE_NAME} 38 | 39 | readonly DPDK_TAG=$IMAGE_NAME:latest 40 | 41 | THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 42 | 43 | pushd "$THIS_DIR" > /dev/null 44 | docker pull "ubuntu:$UBUNTU_VERSION" 45 | docker build \ 46 | -t "$DPDK_TAG" \ 47 | --build-arg UBUNTU_VERSION="$UBUNTU_VERSION" \ 48 | . 49 | 50 | if $PUSH; then 51 | docker push "$DPDK_TAG" 52 | fi 53 | popd > /dev/null -------------------------------------------------------------------------------- /cmd/dpdkinfra/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "strings" 9 | "time" 10 | 11 | "github.com/stolsma/go-p4pack/pkg/config" 12 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra" 13 | dpdkiConfig "github.com/stolsma/go-p4pack/pkg/dpdkinfra/config" 14 | "github.com/stolsma/go-p4pack/pkg/flowtest" 15 | "github.com/stolsma/go-p4pack/pkg/logging" 16 | "github.com/stolsma/go-p4pack/pkg/signals" 17 | "github.com/stolsma/go-p4pack/pkg/sshshell" 18 | ) 19 | 20 | var log logging.Logger 21 | 22 | func init() { 23 | // keep the logger up to date, also after new log config 24 | logging.Register("main", func(logger logging.Logger) { 25 | log = logger 26 | }) 27 | } 28 | 29 | type Config struct { 30 | *dpdkiConfig.Config `json:"chassis"` 31 | FlowTest *flowtest.Config `json:"flowtest"` 32 | Logging *logging.Config `json:"logging"` 33 | SSHShell *sshshell.Config `json:"sshshell"` 34 | } 35 | 36 | func main() { 37 | // the context for the app with cancel function 38 | appCtx, cancelAppCtx := context.WithCancel(context.Background()) 39 | 40 | // get application arguments 41 | cmd := CreateCmd() 42 | cmd.Execute() 43 | dpdkArgs, _ := cmd.Flags().GetString("dpdkargs") 44 | configFile, _ := cmd.Flags().GetString("config") 45 | 46 | // get configuration 47 | conf := &Config{Config: dpdkiConfig.Create()} 48 | err := config.LoadConfig(configFile, conf) 49 | if err != nil { 50 | log.Fatalf("Configuration load failed: %v", err) 51 | } 52 | 53 | // configure logging with our app requirements 54 | if conf.Logging != nil { 55 | err = conf.Logging.Apply() 56 | if err != nil { 57 | log.Fatalf("Applying logging config failed: %v", err) 58 | } 59 | } 60 | 61 | // initialize the dpdkinfra singleton 62 | dpdki, err := dpdkinfra.CreateAndInit(strings.Split(dpdkArgs, " ")) 63 | if err != nil { 64 | log.Fatalf("DPDKInfraInit failed: %v", err) 65 | } 66 | 67 | // Apply given dpdkinfra configuration 68 | if conf.Config != nil { 69 | err = conf.Config.Apply() 70 | if err != nil { 71 | log.Fatalf("Applying chassis config failed: %v", err) 72 | } 73 | } 74 | 75 | // initialize the flowtest singleton 76 | _, err = flowtest.CreateAndInit(appCtx) 77 | if err != nil { 78 | log.Fatalf("Flow tests initialization failed: %v", err) 79 | } 80 | 81 | // apply given flowtest configuration 82 | if conf.FlowTest != nil { 83 | err = conf.FlowTest.Apply() 84 | if err != nil { 85 | log.Fatalf("Applying predefined flowtests failed: %v", err) 86 | } 87 | } 88 | 89 | // start ssh shell cli server with our CLI 90 | if conf.SSHShell != nil { 91 | sshshell.StartSSHShell(appCtx, createShellRoot, conf.SSHShell) 92 | } 93 | 94 | // initialize wait for signals to react on during packet processing 95 | log.Info("p4vswitch pipeline and SSH CLI server running!") 96 | stopCh := signals.RegisterSignalHandlers() 97 | 98 | // wait for stop signal CTRL-C or forced termination 99 | <-stopCh 100 | 101 | // Cancel the App context to let all running SSH sessions and Flow tests close in a neat way 102 | cancelAppCtx() 103 | println() 104 | log.Info("p4vswitch pipeline requested to stop!") 105 | 106 | // cleanup DpdkInfra environment 107 | err = dpdki.Cleanup() 108 | if err != nil { 109 | log.Infof("dpdki cleanup failed! err: %v", err) 110 | } 111 | 112 | // Wait a small time to let everything shutdown... 113 | time.Sleep(1000 * time.Millisecond) 114 | 115 | // All is handled... 116 | log.Info("p4vswitch stopped!") 117 | } 118 | -------------------------------------------------------------------------------- /cmd/dpdkinfra/root.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // Create CLI handler 11 | func CreateCmd() *cobra.Command { 12 | root := &cobra.Command{ 13 | Use: "", 14 | Short: "DPDKInfra is a Go/DPDK SWX example program", 15 | Long: `Testing Go with DPDK - SWX. Complete documentation is available at https://github.com/stolsma/go-p4pack/`, 16 | Run: func(cmd *cobra.Command, args []string) {}, 17 | } 18 | var config, dpdkargs string 19 | root.Flags().StringVarP(&config, "config", "c", "./examples/default/config.json", "The config file to use.") 20 | // "dummy -c 3 -n 4" 21 | // "dummy -c 3 --log-level .*,8" 22 | root.Flags().StringVarP(&dpdkargs, "dpdkargs", "d", "dummy -c 3 -n 4", "The DPDK arguments to use.") 23 | 24 | return root 25 | } 26 | -------------------------------------------------------------------------------- /cmd/dpdkinfra/shellroot.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | dpdkinfracli "github.com/stolsma/go-p4pack/pkg/dpdkinfra/cli" 9 | flowtestcli "github.com/stolsma/go-p4pack/pkg/flowtest/cli" 10 | loggingcli "github.com/stolsma/go-p4pack/pkg/logging/cli" 11 | pcidevicescli "github.com/stolsma/go-p4pack/pkg/pcidevices/cli" 12 | ) 13 | 14 | // create SSH shell CLI handling interface 15 | func createShellRoot() *cobra.Command { 16 | cliRoot := &cobra.Command{ 17 | Use: "", 18 | Short: "DPDKInfra is a Go/DPDK SWX pipeline test program", 19 | Long: `Testing Go with DPDK SWX pipeline. Complete documentation is available at https://github.com/stolsma/go-p4pack/`, 20 | Run: func(cmd *cobra.Command, args []string) {}, 21 | } 22 | cliRoot.CompletionOptions.DisableDefaultCmd = true // no completion create command 23 | initExit(cliRoot) 24 | initVersion(cliRoot) 25 | pcidevicescli.GetCommand(cliRoot) 26 | dpdkinfracli.GetCommand(cliRoot) 27 | loggingcli.GetCommand(cliRoot) 28 | flowtestcli.GetCommand(cliRoot) 29 | return cliRoot 30 | } 31 | 32 | func initExit(parent *cobra.Command) { 33 | var exitCmd = &cobra.Command{ 34 | Use: "exit", 35 | Short: "exit", 36 | Run: func(cmd *cobra.Command, args []string) { 37 | cmd.Root().Annotations["exit"] = "exit" 38 | }, 39 | } 40 | 41 | parent.AddCommand(exitCmd) 42 | } 43 | 44 | func initVersion(parent *cobra.Command) { 45 | parent.AddCommand(&cobra.Command{ 46 | Use: "version", 47 | Short: "Print the version number of DPDKInfra", 48 | Long: `All software has versions. This is DPDKInfra's`, 49 | Run: func(cmd *cobra.Command, args []string) { 50 | cmd.Println("DPDKInfra v0.01 -- HEAD") 51 | }, 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /cmd/gnmi_cli/README.md: -------------------------------------------------------------------------------- 1 | # gNMI CLI example program 2 | 3 | gNMI Cli is copied from the [openconfig organization](https://github.com/openconfig/gnmi/tree/master/cmd/gnmi_cli) GitHub repository. -------------------------------------------------------------------------------- /cmd/gnmi_cli/gnmi_cli_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Google Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package main 15 | 16 | import ( 17 | "reflect" 18 | "testing" 19 | ) 20 | 21 | func TestParseQuery(t *testing.T) { 22 | tests := []struct { 23 | name string 24 | input string 25 | delim string 26 | output []string 27 | err bool 28 | }{ 29 | {name: "Invalid delimiter", delim: "ab", err: true}, 30 | {name: "Dot delimiter", input: "a.b", delim: ".", output: []string{"a", "b"}}, 31 | {name: "Leading delimiter", input: "/foo", delim: "/", output: []string{"foo"}}, 32 | {name: "Trailing delimiter", input: "foo/", delim: "/", output: []string{"foo"}}, 33 | {name: "Leading and trailing delimiter", input: "/foo/", delim: "/", output: []string{"foo"}}, 34 | {name: "No leading and trailing delimiter", input: "foo", delim: "/", output: []string{"foo"}}, 35 | {name: "Leading delimiter multi", input: "/foo/bar", delim: "/", output: []string{"foo", "bar"}}, 36 | {name: "Trailing delimiter multi", input: "foo/bar/", delim: "/", output: []string{"foo", "bar"}}, 37 | {name: "Leading and trailing delimiter multi", input: "/foo/bar/", delim: "/", output: []string{"foo", "bar"}}, 38 | {name: "No leading and trailing delimiter multi", input: "foo/bar", delim: "/", output: []string{"foo", "bar"}}, 39 | {name: "Key value", input: "foo[key=value]/bar", delim: "/", output: []string{"foo[key=value]", "bar"}}, 40 | {name: "Key value contains delimiter", input: "foo[key=a/b]/bar", delim: "/", output: []string{"foo[key=a/b]", "bar"}}, 41 | {name: "Multiple key value contains delimiter", input: "foo[key=a/b]/bar[key=c/d]/blah", delim: "/", output: []string{"foo[key=a/b]", "bar[key=c/d]", "blah"}}, 42 | {name: "Missing [ for key value", input: "fookey=a/b]/bar", err: true}, 43 | {name: "Missing ] for key value", input: "foo[key=a/b/bar", err: true}, 44 | {name: "Nested [] for key value", input: "foo[key=[nest]]/bar", err: true}, 45 | } 46 | for _, test := range tests { 47 | t.Run(test.name, func(t *testing.T) { 48 | got, err := parseQuery(test.input, test.delim) 49 | switch { 50 | case test.err && err != nil: 51 | return 52 | case test.err && err == nil: 53 | t.Errorf("parseQuery(%q): want error, got nil", test.input) 54 | case err != nil: 55 | t.Errorf("parseQuery(%q): got error %v, want nil", test.input, err) 56 | default: 57 | if !reflect.DeepEqual(got, test.output) { 58 | t.Errorf("parseQuery(%q): got %q, want %q", test.input, got, test.output) 59 | } 60 | } 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /cmd/p4vswitch/README.md: -------------------------------------------------------------------------------- 1 | # This example program is under construction -------------------------------------------------------------------------------- /cmd/p4vswitch/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | 13 | log "github.com/golang/glog" 14 | 15 | p4rtpb "github.com/p4lang/p4runtime/go/p4/v1" 16 | "google.golang.org/grpc" 17 | codes "google.golang.org/grpc/codes" 18 | "google.golang.org/grpc/credentials/insecure" 19 | status "google.golang.org/grpc/status" 20 | 21 | "github.com/stolsma/go-p4pack/pkg/p4device" 22 | p4DpdkTarget "github.com/stolsma/go-p4pack/pkg/tdi/targets/p4-dpdk-target" 23 | ) 24 | 25 | func waitForSignal() { 26 | sigs := make(chan os.Signal, 1) 27 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 28 | done := make(chan bool, 1) 29 | 30 | go func() { 31 | <-sigs 32 | done <- true 33 | }() 34 | 35 | <-done 36 | } 37 | 38 | // First start the device threads 39 | // then add the supported targets to the device 40 | func main() { 41 | fmt.Println("Hello, Modules!") 42 | fmt.Printf("Process pid: %d\n", os.Getpid()) 43 | 44 | device, err := p4device.New("") 45 | if err != nil { 46 | log.Errorf("failed to start p4device: %v", err) 47 | return 48 | } 49 | defer device.Stop() 50 | 51 | conn, err := grpc.Dial(device.Addr()+":"+p4device.P4RTPort, grpc.WithTransportCredentials(insecure.NewCredentials())) 52 | if err != nil { 53 | log.Errorf("failed to Dial p4device: %v", err) 54 | return 55 | } 56 | 57 | want := &p4rtpb.CapabilitiesRequest{} 58 | 59 | cP4RT := p4rtpb.NewP4RuntimeClient(conn) 60 | _, err = cP4RT.Capabilities(context.Background(), want) 61 | if err == nil { 62 | log.Errorf("p4rt.Capabilities worked and that shouldnt! ;-)") 63 | return 64 | } 65 | if err != nil { 66 | e, _ := status.FromError(err) 67 | if e.Code() != codes.Unimplemented { 68 | log.Errorf("Unexpected p4rt.Capabilities error: %v", err) 69 | return 70 | } 71 | } 72 | 73 | fmt.Println("p4runtime grpc call worked!") 74 | 75 | // Initialize the first supported target: p4-DPDK 76 | err = p4DpdkTarget.InitTarget() 77 | if err != nil { 78 | log.Errorf("Unexpected p4DpdkTarget.InitTarget error: %v", err) 79 | return 80 | } 81 | 82 | // wait for signals to react on 83 | waitForSignal() 84 | 85 | fmt.Println("Telnet stopped!") 86 | } 87 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Go-P4Pack developers documentation 2 | 3 | TODO Expand -------------------------------------------------------------------------------- /examples/default/bf-rt.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema_version" : "1.0.0", 3 | "tables" : [ 4 | { 5 | "name" : "pipe.ingress.ipv4_host", 6 | "id" : 43484571, 7 | "table_type" : "MatchAction_Direct", 8 | "size" : 65536, 9 | "annotations" : [], 10 | "depends_on" : [], 11 | "has_const_default_action" : true, 12 | "key" : [ 13 | { 14 | "id" : 1, 15 | "name" : "hdr.ipv4.dst_addr", 16 | "repeated" : false, 17 | "annotations" : [], 18 | "mandatory" : false, 19 | "match_type" : "Exact", 20 | "type" : { 21 | "type" : "bytes", 22 | "width" : 32 23 | } 24 | } 25 | ], 26 | "action_specs" : [ 27 | { 28 | "id" : 18638031, 29 | "name" : "ingress.send", 30 | "action_scope" : "TableAndDefault", 31 | "annotations" : [], 32 | "data" : [ 33 | { 34 | "id" : 1, 35 | "name" : "port", 36 | "repeated" : false, 37 | "mandatory" : true, 38 | "read_only" : false, 39 | "annotations" : [], 40 | "type" : { 41 | "type" : "bytes", 42 | "width" : 32 43 | } 44 | } 45 | ] 46 | }, 47 | { 48 | "id" : 33281717, 49 | "name" : "ingress.drop", 50 | "action_scope" : "TableAndDefault", 51 | "annotations" : [], 52 | "data" : [] 53 | }, 54 | { 55 | "id" : 21257015, 56 | "name" : "NoAction", 57 | "action_scope" : "DefaultOnly", 58 | "annotations" : [ 59 | { 60 | "name" : "@defaultonly" 61 | } 62 | ], 63 | "data" : [] 64 | } 65 | ], 66 | "data" : [], 67 | "supported_operations" : [], 68 | "attributes" : ["EntryScope"] 69 | } 70 | ], 71 | "learn_filters" : [] 72 | } -------------------------------------------------------------------------------- /examples/default/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2022 - Sander Tolsma. All rights reserved 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 7 | 8 | docker run --rm -u 1000:1000 -v "${THIS_DIR}":/p4ccode -w /p4ccode ghcr.io/stolsma/p4c:latest /bin/bash -c "p4c-dpdk --arch psa -o default.spec --p4runtime-files p4info.proto.txt --bf-rt-schema bf-rt.json --context context.json default.p4" -------------------------------------------------------------------------------- /examples/default/context.json: -------------------------------------------------------------------------------- 1 | { 2 | "program_name" : "default", 3 | "build_date" : "Tue Jan 17 10:03:55 2023", 4 | "compile_command" : "p4c-dpdk --arch psa -o default.spec --p4runtime-files p4info.proto.txt --bf-rt-schema bf-rt.json --context context.json default.p4", 5 | "compiler_version" : "0.1 (SHA: ea553dded BUILD: RELEASE)", 6 | "schema_version" : "0.1", 7 | "target" : "DPDK", 8 | "tables" : [ 9 | { 10 | "name" : "ingress.ipv4_host", 11 | "target_name" : "ingress.ipv4_host", 12 | "direction" : "ingress", 13 | "handle" : 65536, 14 | "table_type" : "match", 15 | "size" : 65536, 16 | "p4_hidden" : false, 17 | "add_on_miss" : false, 18 | "idle_timeout_with_auto_delete" : false, 19 | "stateful_table_refs" : [], 20 | "statistics_table_refs" : [], 21 | "meter_table_refs" : [], 22 | "match_key_fields" : [ 23 | { 24 | "name" : "hdr.ipv4.dst_addr", 25 | "instance_name" : "hdr.ipv4", 26 | "field_name" : "dst_addr", 27 | "match_type" : "exact", 28 | "start_bit" : 0, 29 | "bit_width" : 32, 30 | "bit_width_full" : 32, 31 | "position" : 0 32 | } 33 | ], 34 | "actions" : [ 35 | { 36 | "name" : "ingress.send", 37 | "target_name" : "ingress.send", 38 | "handle" : 131072, 39 | "constant_default_action" : false, 40 | "is_compiler_added_action" : false, 41 | "allowed_as_hit_action" : true, 42 | "allowed_as_default_action" : false, 43 | "p4_parameters" : [ 44 | { 45 | "name" : "port", 46 | "start_bit" : 0, 47 | "bit_width" : 32, 48 | "position" : 0, 49 | "byte_array_index" : 0 50 | } 51 | ] 52 | }, 53 | { 54 | "name" : "ingress.drop", 55 | "target_name" : "ingress.drop_1", 56 | "handle" : 131073, 57 | "constant_default_action" : true, 58 | "is_compiler_added_action" : false, 59 | "allowed_as_hit_action" : true, 60 | "allowed_as_default_action" : true, 61 | "p4_parameters" : [] 62 | }, 63 | { 64 | "name" : "NoAction", 65 | "target_name" : "NoAction", 66 | "handle" : 131074, 67 | "constant_default_action" : false, 68 | "is_compiler_added_action" : false, 69 | "allowed_as_hit_action" : true, 70 | "allowed_as_default_action" : false, 71 | "p4_parameters" : [] 72 | } 73 | ], 74 | "match_attributes" : { 75 | "stage_tables" : [ 76 | { 77 | "action_format" : [ 78 | { 79 | "action_name" : "ingress.send", 80 | "action_handle" : 131072, 81 | "immediate_fields" : [ 82 | { 83 | "param_name" : "port", 84 | "dest_start" : 0, 85 | "dest_width" : 32 86 | } 87 | ] 88 | }, 89 | { 90 | "action_name" : "ingress.drop", 91 | "action_handle" : 131073, 92 | "immediate_fields" : [] 93 | }, 94 | { 95 | "action_name" : "NoAction", 96 | "action_handle" : 131074, 97 | "immediate_fields" : [] 98 | } 99 | ] 100 | } 101 | ] 102 | }, 103 | "default_action_handle" : 131073 104 | } 105 | ], 106 | "externs" : [] 107 | } -------------------------------------------------------------------------------- /examples/default/createenv.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright 2022 - Sander Tolsma. All rights reserved 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | # let alias work in non interactive bash shells! 7 | shopt -s expand_aliases 8 | 9 | # Builds are run as root in containers, no need for sudo 10 | [ "$(id -u)" != '0' ] || alias sudo= 11 | 12 | # Remove leftovers before going on... 13 | sudo ip netns del host1 14 | sudo ip netns del host2 15 | sudo ip netns del host3 16 | sudo ip netns del host4 17 | 18 | # Create host1 19 | sudo ip netns add host1 20 | sudo ip link set sw1 netns host1 21 | sudo ip netns exec host1 sudo ip link set lo up 22 | sudo ip netns exec host1 sudo ip link set sw1 address 32:fb:fa:c6:67:01 23 | sudo ip netns exec host1 sudo ip -4 addr add 192.168.222.1/24 dev sw1 24 | sudo ip netns exec host1 sudo ip link set sw1 up 25 | sudo ip netns exec host1 sudo arp -s 192.168.222.2 32:fb:fa:c6:67:02 26 | sudo ip netns exec host1 sudo arp -s 192.168.222.3 32:fb:fa:c6:67:03 27 | sudo ip netns exec host1 sudo arp -s 192.168.222.4 32:fb:fa:c6:67:04 28 | 29 | # Create host2 30 | sudo ip netns add host2 31 | sudo ip link set sw2 netns host2 32 | sudo ip netns exec host2 sudo ip link set lo up 33 | sudo ip netns exec host2 sudo ip link set sw2 address 32:fb:fa:c6:67:02 34 | sudo ip netns exec host2 sudo ip -4 addr add 192.168.222.2/24 dev sw2 35 | sudo ip netns exec host2 sudo ip link set sw2 up 36 | sudo ip netns exec host2 sudo arp -s 192.168.222.1 32:fb:fa:c6:67:01 37 | sudo ip netns exec host2 sudo arp -s 192.168.222.3 32:fb:fa:c6:67:03 38 | sudo ip netns exec host2 sudo arp -s 192.168.222.4 32:fb:fa:c6:67:04 39 | 40 | # Create host3 41 | sudo ip netns add host3 42 | sudo ip link set sw3 netns host3 43 | sudo ip netns exec host3 sudo ip link set lo up 44 | sudo ip netns exec host3 sudo ip link set sw3 address 32:fb:fa:c6:67:03 45 | sudo ip netns exec host3 sudo ip -4 addr add 192.168.222.3/24 dev sw3 46 | sudo ip netns exec host3 sudo ip link set sw3 up 47 | sudo ip netns exec host3 sudo arp -s 192.168.222.1 32:fb:fa:c6:67:01 48 | sudo ip netns exec host3 sudo arp -s 192.168.222.2 32:fb:fa:c6:67:02 49 | sudo ip netns exec host3 sudo arp -s 192.168.222.4 32:fb:fa:c6:67:04 50 | 51 | # Create host4 52 | sudo ip netns add host4 53 | sudo ip link set sw4 netns host4 54 | sudo ip netns exec host4 sudo ip link set lo up 55 | sudo ip netns exec host4 sudo ip link set sw4 address 32:fb:fa:c6:67:04 56 | sudo ip netns exec host4 sudo ip -4 addr add 192.168.222.4/24 dev sw4 57 | sudo ip netns exec host4 sudo ip link set sw4 up 58 | sudo ip netns exec host4 sudo arp -s 192.168.222.1 32:fb:fa:c6:67:01 59 | sudo ip netns exec host4 sudo arp -s 192.168.222.2 32:fb:fa:c6:67:02 60 | sudo ip netns exec host4 sudo arp -s 192.168.222.3 32:fb:fa:c6:67:03 61 | -------------------------------------------------------------------------------- /examples/default/default.spec: -------------------------------------------------------------------------------- 1 | 2 | struct ethernet_h { 3 | bit<48> dst_addr 4 | bit<48> src_addr 5 | bit<16> ether_type 6 | } 7 | 8 | struct vlan_tag_h { 9 | bit<16> pcp_cfi_vid 10 | bit<16> ether_type 11 | } 12 | 13 | struct ipv4_h { 14 | bit<8> version_ihl 15 | bit<8> diffserv 16 | bit<16> total_len 17 | bit<16> identification 18 | bit<16> flags_frag_offset 19 | bit<8> ttl 20 | bit<8> protocol 21 | bit<16> hdr_checksum 22 | bit<32> src_addr 23 | bit<32> dst_addr 24 | } 25 | 26 | struct psa_ingress_output_metadata_t { 27 | bit<8> class_of_service 28 | bit<8> clone 29 | bit<16> clone_session_id 30 | bit<8> drop 31 | bit<8> resubmit 32 | bit<32> multicast_group 33 | bit<32> egress_port 34 | } 35 | 36 | struct psa_egress_output_metadata_t { 37 | bit<8> clone 38 | bit<16> clone_session_id 39 | bit<8> drop 40 | } 41 | 42 | struct psa_egress_deparser_input_metadata_t { 43 | bit<32> egress_port 44 | } 45 | 46 | struct send_arg_t { 47 | bit<32> port 48 | } 49 | 50 | header ethernet instanceof ethernet_h 51 | header vlan_tag instanceof vlan_tag_h 52 | header ipv4 instanceof ipv4_h 53 | 54 | struct my_ingress_metadata_t { 55 | bit<32> psa_ingress_input_metadata_ingress_port 56 | bit<8> psa_ingress_output_metadata_drop 57 | bit<32> psa_ingress_output_metadata_egress_port 58 | } 59 | metadata instanceof my_ingress_metadata_t 60 | 61 | action NoAction args none { 62 | return 63 | } 64 | 65 | action send args instanceof send_arg_t { 66 | mov m.psa_ingress_output_metadata_egress_port t.port 67 | return 68 | } 69 | 70 | action drop_1 args none { 71 | mov m.psa_ingress_output_metadata_drop 1 72 | return 73 | } 74 | 75 | table ipv4_host { 76 | key { 77 | h.ipv4.dst_addr exact 78 | } 79 | actions { 80 | send 81 | drop_1 82 | NoAction 83 | } 84 | default_action drop_1 args none const 85 | size 0x10000 86 | } 87 | 88 | 89 | apply { 90 | rx m.psa_ingress_input_metadata_ingress_port 91 | mov m.psa_ingress_output_metadata_drop 0x0 92 | extract h.ethernet 93 | jmpeq INGRESS_PARSER_PARSE_VLAN_TAG h.ethernet.ether_type 0x8100 94 | jmpeq INGRESS_PARSER_PARSE_IPV4 h.ethernet.ether_type 0x800 95 | jmp INGRESS_PARSER_ACCEPT 96 | INGRESS_PARSER_PARSE_VLAN_TAG : extract h.vlan_tag 97 | jmpeq INGRESS_PARSER_PARSE_IPV4 h.vlan_tag.ether_type 0x800 98 | jmp INGRESS_PARSER_ACCEPT 99 | INGRESS_PARSER_PARSE_IPV4 : extract h.ipv4 100 | INGRESS_PARSER_ACCEPT : table ipv4_host 101 | jmpneq LABEL_DROP m.psa_ingress_output_metadata_drop 0x0 102 | emit h.ethernet 103 | emit h.vlan_tag 104 | emit h.ipv4 105 | tx m.psa_ingress_output_metadata_egress_port 106 | LABEL_DROP : drop 107 | } 108 | 109 | 110 | -------------------------------------------------------------------------------- /examples/default/p4info.proto.txt: -------------------------------------------------------------------------------- 1 | pkg_info { 2 | arch: "psa" 3 | } 4 | tables { 5 | preamble { 6 | id: 43484571 7 | name: "ingress.ipv4_host" 8 | alias: "ipv4_host" 9 | } 10 | match_fields { 11 | id: 1 12 | name: "hdr.ipv4.dst_addr" 13 | bitwidth: 32 14 | match_type: EXACT 15 | } 16 | action_refs { 17 | id: 18638031 18 | } 19 | action_refs { 20 | id: 33281717 21 | } 22 | action_refs { 23 | id: 21257015 24 | annotations: "@defaultonly" 25 | scope: DEFAULT_ONLY 26 | } 27 | const_default_action_id: 33281717 28 | size: 65536 29 | } 30 | actions { 31 | preamble { 32 | id: 21257015 33 | name: "NoAction" 34 | alias: "NoAction" 35 | annotations: "@noWarn(\"unused\")" 36 | } 37 | } 38 | actions { 39 | preamble { 40 | id: 18638031 41 | name: "ingress.send" 42 | alias: "send" 43 | } 44 | params { 45 | id: 1 46 | name: "port" 47 | bitwidth: 32 48 | type_name { 49 | name: "PortId_t" 50 | } 51 | } 52 | } 53 | actions { 54 | preamble { 55 | id: 33281717 56 | name: "ingress.drop" 57 | alias: "drop" 58 | } 59 | } 60 | type_info { 61 | new_types { 62 | key: "PortId_t" 63 | value { 64 | translated_type { 65 | uri: "p4.org/psa/v1/PortId_t" 66 | sdn_bitwidth: 32 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/ipdk-simple_l3/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2022 - Sander Tolsma. All rights reserved 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | docker run --rm -u 1000:1000 -v "${PWD}":/p4ccode -w /p4ccode stolsma/p4c-all:latest /bin/bash -c "p4c-dpdk -o simple_l3.spec --p4runtime-files p4info.proto.txt simple_l3.p4" -------------------------------------------------------------------------------- /examples/ipdk-simple_l3/l3_table.txt: -------------------------------------------------------------------------------- 1 | match 0xc0a8de01 action send port 0 2 | match 0xc0a8de02 action send port 1 3 | match 0xc0a8de03 action send port 2 4 | match 0xc0a8de04 action send port 3 5 | -------------------------------------------------------------------------------- /examples/ipdk-simple_l3/p4info.proto.txt: -------------------------------------------------------------------------------- 1 | pkg_info { 2 | arch: "v1model" 3 | } 4 | tables { 5 | preamble { 6 | id: 43484571 7 | name: "ingress.ipv4_host" 8 | alias: "ipv4_host" 9 | } 10 | match_fields { 11 | id: 1 12 | name: "hdr.ipv4.dst_addr" 13 | bitwidth: 32 14 | match_type: EXACT 15 | } 16 | action_refs { 17 | id: 18638031 18 | } 19 | action_refs { 20 | id: 33281717 21 | } 22 | action_refs { 23 | id: 21257015 24 | annotations: "@defaultonly" 25 | scope: DEFAULT_ONLY 26 | } 27 | const_default_action_id: 33281717 28 | size: 65536 29 | } 30 | actions { 31 | preamble { 32 | id: 21257015 33 | name: "NoAction" 34 | alias: "NoAction" 35 | annotations: "@noWarn(\"unused\")" 36 | } 37 | } 38 | actions { 39 | preamble { 40 | id: 18638031 41 | name: "ingress.send" 42 | alias: "send" 43 | } 44 | params { 45 | id: 1 46 | name: "port" 47 | bitwidth: 32 48 | type_name { 49 | name: "PortId_t" 50 | } 51 | } 52 | } 53 | actions { 54 | preamble { 55 | id: 33281717 56 | name: "ingress.drop" 57 | alias: "drop" 58 | } 59 | } 60 | type_info { 61 | new_types { 62 | key: "PortId_t" 63 | value { 64 | translated_type { 65 | uri: "p4.org/psa/v1/PortId_t" 66 | sdn_bitwidth: 32 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/ipdk-simple_l3/simple_l3.cli: -------------------------------------------------------------------------------- 1 | ; SPDX-License-Identifier: BSD-3-Clause 2 | ; Copyright(c) 2020 Intel Corporation 3 | ; Copyright(c) 2022 Sander Tolsma 4 | 5 | mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0 6 | 7 | tap sw0 8 | tap sw1 9 | tap sw2 10 | tap sw3 11 | 12 | pipeline PIPELINE0 create 0 13 | 14 | pipeline PIPELINE0 port in 0 tap sw0 mempool MEMPOOL0 mtu 1500 bsz 1 15 | pipeline PIPELINE0 port in 1 tap sw1 mempool MEMPOOL0 mtu 1500 bsz 1 16 | pipeline PIPELINE0 port in 2 tap sw2 mempool MEMPOOL0 mtu 1500 bsz 1 17 | pipeline PIPELINE0 port in 3 tap sw3 mempool MEMPOOL0 mtu 1500 bsz 1 18 | 19 | pipeline PIPELINE0 port out 0 tap sw0 bsz 1 20 | pipeline PIPELINE0 port out 1 tap sw1 bsz 1 21 | pipeline PIPELINE0 port out 2 tap sw2 bsz 1 22 | pipeline PIPELINE0 port out 3 tap sw3 bsz 1 23 | 24 | pipeline PIPELINE0 build ./examples/ipdk-simple_l3/simple_l3.spec 25 | pipeline PIPELINE0 table ipv4_host add ./examples/ipdk-simple_l3/l3_table.txt 26 | 27 | pipeline PIPELINE0 commit 28 | 29 | thread 1 pipeline PIPELINE0 enable 30 | -------------------------------------------------------------------------------- /examples/ipdk-simple_l3/simple_l3.spec: -------------------------------------------------------------------------------- 1 | 2 | struct ethernet_h { 3 | bit<48> dst_addr 4 | bit<48> src_addr 5 | bit<16> ether_type 6 | } 7 | 8 | struct vlan_tag_h { 9 | bit<16> pcp_cfi_vid 10 | bit<16> ether_type 11 | } 12 | 13 | struct ipv4_h { 14 | bit<8> version_ihl 15 | bit<8> diffserv 16 | bit<16> total_len 17 | bit<16> identification 18 | bit<16> flags_frag_offset 19 | bit<8> ttl 20 | bit<8> protocol 21 | bit<16> hdr_checksum 22 | bit<32> src_addr 23 | bit<32> dst_addr 24 | } 25 | 26 | struct psa_ingress_output_metadata_t { 27 | bit<8> class_of_service 28 | bit<8> clone 29 | bit<16> clone_session_id 30 | bit<8> drop 31 | bit<8> resubmit 32 | bit<32> multicast_group 33 | bit<32> egress_port 34 | } 35 | 36 | struct psa_egress_output_metadata_t { 37 | bit<8> clone 38 | bit<16> clone_session_id 39 | bit<8> drop 40 | } 41 | 42 | struct psa_egress_deparser_input_metadata_t { 43 | bit<32> egress_port 44 | } 45 | 46 | struct send_arg_t { 47 | bit<32> port 48 | } 49 | 50 | header ethernet instanceof ethernet_h 51 | header vlan_tag instanceof vlan_tag_h 52 | header ipv4 instanceof ipv4_h 53 | 54 | struct my_ingress_metadata_t { 55 | bit<32> psa_ingress_input_metadata_ingress_port 56 | bit<8> psa_ingress_output_metadata_drop 57 | bit<32> psa_ingress_output_metadata_egress_port 58 | } 59 | metadata instanceof my_ingress_metadata_t 60 | 61 | action NoAction args none { 62 | return 63 | } 64 | 65 | action send args instanceof send_arg_t { 66 | mov m.psa_ingress_output_metadata_egress_port t.port 67 | return 68 | } 69 | 70 | action drop_1 args none { 71 | mov m.psa_ingress_output_metadata_drop 1 72 | return 73 | } 74 | 75 | table ipv4_host { 76 | key { 77 | h.ipv4.dst_addr exact 78 | } 79 | actions { 80 | send 81 | drop_1 82 | NoAction 83 | } 84 | default_action drop_1 args none const 85 | size 0x10000 86 | } 87 | 88 | 89 | apply { 90 | rx m.psa_ingress_input_metadata_ingress_port 91 | mov m.psa_ingress_output_metadata_drop 0x0 92 | extract h.ethernet 93 | jmpeq INGRESS_PARSER_PARSE_VLAN_TAG h.ethernet.ether_type 0x8100 94 | jmpeq INGRESS_PARSER_PARSE_IPV4 h.ethernet.ether_type 0x800 95 | jmp INGRESS_PARSER_ACCEPT 96 | INGRESS_PARSER_PARSE_VLAN_TAG : extract h.vlan_tag 97 | jmpeq INGRESS_PARSER_PARSE_IPV4 h.vlan_tag.ether_type 0x800 98 | jmp INGRESS_PARSER_ACCEPT 99 | INGRESS_PARSER_PARSE_IPV4 : extract h.ipv4 100 | INGRESS_PARSER_ACCEPT : table ipv4_host 101 | jmpneq LABEL_DROP m.psa_ingress_output_metadata_drop 0x0 102 | emit h.ethernet 103 | emit h.vlan_tag 104 | emit h.ipv4 105 | tx m.psa_ingress_output_metadata_egress_port 106 | LABEL_DROP : drop 107 | } 108 | -------------------------------------------------------------------------------- /examples/linux_networking/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2022 - Sander Tolsma. All rights reserved 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 7 | 8 | docker run --rm -u 1000:1000 -v "${THIS_DIR}":/p4ccode -w /p4ccode ghcr.io/stolsma/p4c:latest /bin/bash -c "p4c-dpdk --arch pna --target dpdk -o linux_networking.spec --p4runtime-files p4info.proto.txt --bf-rt-schema bf-rt.json --context context.json linux_networking.p4" -------------------------------------------------------------------------------- /examples/linux_networking/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sshshell":{ 3 | "bind": ":2222", 4 | "users": { 5 | "user": { 6 | "password": "notsecure" 7 | } 8 | }, 9 | "hostkeyfile": "./hostkey" 10 | }, 11 | "chassis": { 12 | "pktmbufs" : [{ 13 | "name": "MEMPOOL0", 14 | "buffersize": 2304, 15 | "poolsize": 32768, 16 | "cachesize": 256, 17 | "cpuid": 0 18 | }], 19 | "devices": [ 20 | "virtio_user0,path=/dev/vhost-net,queues=1,queue_size=32,iface=sw3", 21 | "virtio_user1,path=/dev/vhost-net,queues=1,queue_size=32,iface=sw4" 22 | ], 23 | "interfaces" : [{ 24 | "name": "sw1", 25 | "tap": { 26 | "rx": { 27 | "pktmbuf": "MEMPOOL0", 28 | "mtu": 1514 29 | } 30 | } 31 | },{ 32 | "name": "sw2", 33 | "tap": { 34 | "rx": { 35 | "pktmbuf": "MEMPOOL0", 36 | "mtu": 1514 37 | } 38 | } 39 | },{ 40 | "name": "sw3", 41 | "ethdev": { 42 | "portname": "virtio_user0", 43 | "rx": { 44 | "mtu": 1500, 45 | "nqueues": 1, 46 | "queuesize": 32, 47 | "pktmbuf": "MEMPOOL0", 48 | "promiscuous": true 49 | }, 50 | "tx": { 51 | "nqueues": 1, 52 | "queuesize": 32 53 | } 54 | } 55 | },{ 56 | "name": "sw4", 57 | "ethdev": { 58 | "portname": "virtio_user1", 59 | "rx": { 60 | "mtu": 1500, 61 | "nqueues": 1, 62 | "queuesize": 32, 63 | "pktmbuf": "MEMPOOL0", 64 | "promiscuous": true 65 | }, 66 | "tx": { 67 | "nqueues": 1, 68 | "queuesize": 32 69 | } 70 | } 71 | }], 72 | "pipelines" :[{ 73 | "name": "PIPELINE0", 74 | "numanode": 0, 75 | "threadid": 1, 76 | "spec": "./linux_networking.spec", 77 | "inputports": [{ 78 | "ifacename": "sw1", 79 | "rxqueue": 0, 80 | "bsz": 1 81 | },{ 82 | "ifacename": "sw2", 83 | "rxqueue": 0, 84 | "bsz": 1 85 | },{ 86 | "ifacename": "sw3", 87 | "rxqueue": 0, 88 | "bsz": 1 89 | },{ 90 | "ifacename": "sw4", 91 | "rxqueue": 0, 92 | "bsz": 1 93 | }], 94 | "outputports": [{ 95 | "ifacename": "sw1", 96 | "txqueue": 0, 97 | "bsz": 1 98 | },{ 99 | "ifacename": "sw2", 100 | "txqueue": 0, 101 | "bsz": 1 102 | },{ 103 | "ifacename": "sw3", 104 | "txqueue": 0, 105 | "bsz": 1 106 | },{ 107 | "ifacename": "sw4", 108 | "txqueue": 0, 109 | "bsz": 1 110 | }] 111 | }] 112 | }, 113 | "logging": { 114 | "loggers": { 115 | "root": { 116 | "level": "info", 117 | "output": { 118 | "stdout": { 119 | "sink": "stdout" 120 | }, 121 | "file": { 122 | "sink": "file" 123 | } 124 | } 125 | } 126 | }, 127 | "sinks": { 128 | "stdout": { 129 | "type": "stdout", 130 | "encoding": "console", 131 | "stdout": {} 132 | }, 133 | "file": { 134 | "type": "file", 135 | "encoding": "json", 136 | "file": { 137 | "path": "./test.log" 138 | } 139 | } 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /examples/linux_networking/headers.p4: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Intel Corporation. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at: 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | #ifndef LINUX_NETWORKING_HEADERS_P4_ 20 | #define LINUX_NETWORKING_HEADERS_P4_ 21 | 22 | #define ETHERTYPE_IPV4 0x0800 23 | #define ETHERTYPE_IPV6 0x86dd 24 | #define ETHERTYPE_ARP 0x0806 25 | #define ETHERTYPE_LLDP 0x88cc 26 | #define ETHERTYPE_VLAN 0x8100 27 | #define ETHERTYPE_MAC 0x88e7 28 | 29 | #define IP_PROTOCOL_IP_IP 0x04 30 | #define IP_PROTOCOL_IPV6 0x29 31 | #define IP_PROTOCOL_TCP 0x06 32 | #define IP_PROTOCOL_UDP 0x11 33 | #define IP_PROTOCOL_ICMP 0x01 34 | #define IP_PROTOCOL_ICMPV6 0x3a 35 | #define IP_PROTOCOLS_GRE 0x2f 36 | #define IP_PROTOCOLS_UDP_PORT_VXLAN 4789 37 | 38 | #define GRE_PROTOCOL_ERSPAN 0x88be 39 | 40 | #define ERSPAN_VERSION_TYPE_II 1 41 | 42 | #define TUNNEL_TYPE_VXLAN 2 43 | 44 | typedef bit<48> ethernet_addr_t; 45 | typedef bit<32> ipv4_addr_t; 46 | typedef bit<12> vlan_id_t; 47 | 48 | // -- Protocol headers --------------------------------------------------------- 49 | 50 | #define ETHERNET_HEADER_BYTES 14 51 | 52 | header ethernet_t { 53 | ethernet_addr_t dst_addr; 54 | ethernet_addr_t src_addr; 55 | bit<16> ether_type; 56 | } 57 | 58 | header vlan_t { 59 | bit<16> pcp_cfi_vid; 60 | bit<16> ether_type; 61 | } 62 | 63 | #define IPV4_HEADER_BYTES 20 64 | 65 | header ipv4_t { 66 | bit<8> version_ihl; 67 | bit<8> dscp_ecn; 68 | bit<16> total_len; 69 | bit<16> identification; 70 | bit<16> flags_frag_offset; 71 | bit<8> ttl; 72 | bit<8> protocol; 73 | bit<16> header_checksum; 74 | ipv4_addr_t src_addr; 75 | ipv4_addr_t dst_addr; 76 | } 77 | 78 | #define IPV6_HEADER_BYTES 40 79 | 80 | header ipv6_t { 81 | bit <32> version_dscp_ecn_flowlabel; 82 | bit<16> payload_length; 83 | bit<8> next_header; 84 | bit<8> hop_limit; 85 | bit <64> src_addr_hi; 86 | bit <64> src_addr_lo; 87 | bit <64> dst_addr_hi; 88 | bit <64> dst_addr_lo; 89 | } 90 | 91 | 92 | #define UDP_HEADER_BYTES 8 93 | 94 | header udp_t { 95 | bit<16> src_port; 96 | bit<16> dst_port; 97 | bit<16> hdr_length; 98 | bit<16> checksum; 99 | } 100 | 101 | header tcp_t { 102 | bit<16> src_port; 103 | bit<16> dst_port; 104 | bit<32> seq_no; 105 | bit<32> ack_no; 106 | bit <8> data_offset_res; 107 | bit<8> flags; 108 | bit<16> window; 109 | bit<16> checksum; 110 | bit<16> urgent_ptr; 111 | } 112 | 113 | header icmp_t { 114 | bit<8> type; 115 | bit<8> code; 116 | bit<16> checksum; 117 | } 118 | 119 | header arp_t { 120 | bit<16> hw_type; 121 | bit<16> proto_type; 122 | bit<8> hw_addr_len; 123 | bit<8> proto_addr_len; 124 | bit<16> opcode; 125 | bit<48> sender_hw_addr; 126 | bit<32> sender_proto_addr; 127 | bit<48> target_hw_addr; 128 | bit<32> target_proto_addr; 129 | } 130 | 131 | #define VXLAN_HEADER_BYTES 8 132 | 133 | // VXLAN -- RFC 7348 134 | header vxlan_t { 135 | bit<8> flags; 136 | bit<24> reserved; 137 | bit<24> vni; 138 | bit<8> reserved2; 139 | } 140 | 141 | #endif // LINUX_NETWORKING_HEADERS_P4_ 142 | -------------------------------------------------------------------------------- /examples/linux_networking/metadata.p4: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Intel Corporation. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at: 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | #ifndef LINUX_NETWORKING_METADATA_P4_ 20 | #define LINUX_NETWORKING_METADATA_P4_ 21 | 22 | #include "pna.p4" 23 | #include "headers.p4" 24 | 25 | typedef bit<16> ActionRef_t; 26 | typedef bit<24> ModDataPtr_t; 27 | 28 | struct vendor_metadata_t { 29 | // The modify action to be done at the end of a pass 30 | ActionRef_t mod_action_ref; 31 | // A pointer to any data needed by the action mod_action_ref 32 | ModDataPtr_t mod_data_ptr; 33 | } 34 | 35 | const ActionRef_t VXLAN_ENCAP = (ActionRef_t) 1; 36 | const ActionRef_t VXLAN_DECAP_OUTER_IPV4 = (ActionRef_t) 2; 37 | const ActionRef_t NEIGHBOR = (ActionRef_t) 3; 38 | 39 | #define MIN_TABLE_SIZE 64 40 | 41 | typedef bit<24> tunnel_id_t; 42 | typedef bit<24> vni_id_t; 43 | typedef bit<8> vrf_id_t; 44 | typedef bit<16> router_interface_id_t; 45 | typedef bit<24> neighbor_id_t; 46 | 47 | struct headers_t { 48 | ethernet_t outer_ethernet; 49 | vlan_t[2] outer_vlan; 50 | arp_t outer_arp; 51 | ipv4_t outer_ipv4; 52 | tcp_t outer_tcp; 53 | udp_t outer_udp; 54 | icmp_t outer_icmp; 55 | vxlan_t vxlan; 56 | ethernet_t ethernet; 57 | vlan_t[2] vlan; 58 | arp_t arp; 59 | ipv4_t ipv4; 60 | udp_t udp; 61 | tcp_t tcp; 62 | icmp_t icmp; 63 | } 64 | 65 | struct port_metadata_t { 66 | bool admin_state; /* SAI_PORT_ATTR_ADMIN_STATE */ 67 | bool ingress_filtering; /* SAI_PORT_ATTR_INGRESS_FILTERING */ 68 | bool drop_untagged; /* SAI_PORT_ATTR_DROP_UNTAGGED */ 69 | bool drop_tagged; /* SAI_PORT_ATTR_DROP_UNTAGGED */ 70 | bit<12> port_vlan_id; /* SAI_PORT_ATTR_VLAN_ID */ 71 | bit<16> mtu; /* SAI_PORT_ATTR_MTU */ 72 | bit<3> default_vlan_priority; /* SAI_PORT_ATTR_DEFAULT_VLAN_PRIORITY */ 73 | } 74 | 75 | struct tunnel_metadata_t { 76 | tunnel_id_t id; 77 | vni_id_t vni; 78 | bit<8> tun_type; 79 | bit<16> hash; 80 | } 81 | 82 | // Local metadata for each packet being processed. 83 | struct local_metadata_t { 84 | bit<8> exception_packet; 85 | bit<8> control_packet; 86 | bool admit_to_l3; 87 | vrf_id_t vrf_id; 88 | bit<16> ecmp_group_id; 89 | bit<16> ecmp_hash; 90 | bit<16> nexthop_id; 91 | router_interface_id_t rif_mod_map_id; 92 | bit<16> vlan_id; 93 | bit<8> is_tunnel; 94 | bit<16> hash; 95 | ipv4_addr_t ipv4_dst_match; 96 | // Tunnel metadata 97 | tunnel_metadata_t tunnel; 98 | bit<16> host_info_tx_extended_flex_0; 99 | bit<16> host_info_tx_extended_flex_1; 100 | bit<16> host_info_tx_extended_flex_2; 101 | bit<16> host_info_tx_extended_flex_3; 102 | bit<16> host_info_tx_extended_flex_4; 103 | bit<16> host_info_tx_extended_flex_5; 104 | } 105 | #endif // LINUX_NETWORKING_METADATA_P4_ 106 | -------------------------------------------------------------------------------- /examples/linux_networking/topology_linux_networking.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stolsma/go-p4pack/8fa78e2581bf4935184365ece1842a7de8760036/examples/linux_networking/topology_linux_networking.PNG -------------------------------------------------------------------------------- /examples/linux_networking/topology_linux_networking_with_ecmp.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stolsma/go-p4pack/8fa78e2581bf4935184365ece1842a7de8760036/examples/linux_networking/topology_linux_networking_with_ecmp.PNG -------------------------------------------------------------------------------- /examples/linux_networking/tunnel.p4: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Intel Corporation. 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at: 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | #ifndef LINUX_NETWORKING_TUNNEL_P4_ 20 | #define LINUX_NETWORKING_TUNNEL_P4_ 21 | 22 | #include "pna.p4" 23 | #include "headers.p4" 24 | #include "metadata.p4" 25 | 26 | /* Tunnel Decap */ 27 | control tunnel_decap(inout headers_t hdr, 28 | inout local_metadata_t local_metadata, 29 | inout vendor_metadata_t vendormeta) 30 | { 31 | /* TODO: decttl, copy (ecn_marker to inner) */ 32 | action decap_outer_ipv4(tunnel_id_t tunnel_id) { 33 | vendormeta.mod_data_ptr = tunnel_id; /* ptr can be tunnel_id */ 34 | vendormeta.mod_action_ref = VXLAN_DECAP_OUTER_IPV4; 35 | local_metadata.ipv4_dst_match = hdr.ipv4.dst_addr; 36 | } 37 | 38 | table ipv4_tunnel_term_table { 39 | key = { 40 | hdr.outer_ipv4.src_addr : exact @name("ipv4_src"); 41 | hdr.outer_ipv4.dst_addr : exact @name("ipv4_dst"); 42 | local_metadata.tunnel.tun_type : exact; /* base it on udp dst port - check parser for tunnel type */ 43 | } 44 | actions = { 45 | decap_outer_ipv4; 46 | @defaultonly NoAction; 47 | } 48 | const default_action = NoAction; 49 | } 50 | 51 | apply { 52 | ipv4_tunnel_term_table.apply(); 53 | } 54 | } // control tunnel_decap 55 | 56 | /* Tunnel Encap */ 57 | control tunnel_encap(inout headers_t hdr, 58 | inout local_metadata_t local_metadata, 59 | inout vendor_metadata_t vendormeta, 60 | in pna_main_input_metadata_t istd) 61 | { 62 | action set_tunnel(ModDataPtr_t tunnel_id, ipv4_addr_t dst_addr) { 63 | vendormeta.mod_action_ref = VXLAN_ENCAP; 64 | vendormeta.mod_data_ptr = tunnel_id; /* ptr can be tunnel_id */ 65 | local_metadata.ipv4_dst_match = dst_addr; 66 | } 67 | 68 | table set_tunnel_encap { 69 | key = { 70 | istd.input_port: exact; 71 | } 72 | actions = { 73 | set_tunnel; 74 | @defaultonly NoAction; 75 | } 76 | const default_action = NoAction; 77 | size = MIN_TABLE_SIZE; 78 | } 79 | 80 | apply { 81 | set_tunnel_encap.apply(); 82 | } 83 | } 84 | 85 | #endif // LINUX_NETWORKING_TUNNEL_P4_ 86 | -------------------------------------------------------------------------------- /examples/vxlan-dpdk/vxlan.cli: -------------------------------------------------------------------------------- 1 | ; SPDX-License-Identifier: BSD-3-Clause 2 | ; Copyright(c) 2020 Intel Corporation 3 | 4 | mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0 5 | 6 | link LINK0 dev 0000:18:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 7 | link LINK1 dev 0000:18:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 8 | link LINK2 dev 0000:3b:00.0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 9 | link LINK3 dev 0000:3b:00.1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 10 | 11 | pipeline PIPELINE0 create 0 12 | 13 | pipeline PIPELINE0 port in 0 link LINK0 rxq 0 bsz 32 14 | pipeline PIPELINE0 port in 1 link LINK1 rxq 0 bsz 32 15 | pipeline PIPELINE0 port in 2 link LINK2 rxq 0 bsz 32 16 | pipeline PIPELINE0 port in 3 link LINK3 rxq 0 bsz 32 17 | 18 | pipeline PIPELINE0 port out 0 link LINK0 txq 0 bsz 32 19 | pipeline PIPELINE0 port out 1 link LINK1 txq 0 bsz 32 20 | pipeline PIPELINE0 port out 2 link LINK2 txq 0 bsz 32 21 | pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32 22 | 23 | pipeline PIPELINE0 build ./examples/pipeline/examples/vxlan.spec 24 | pipeline PIPELINE0 table vxlan_table add ./examples/pipeline/examples/vxlan_table.txt 25 | pipeline PIPELINE0 commit 26 | 27 | thread 1 pipeline PIPELINE0 enable 28 | -------------------------------------------------------------------------------- /examples/vxlan-dpdk/vxlan_pcap.cli: -------------------------------------------------------------------------------- 1 | ; SPDX-License-Identifier: BSD-3-Clause 2 | ; Copyright(c) 2020 Intel Corporation 3 | 4 | mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0 5 | 6 | pipeline PIPELINE0 create 0 7 | 8 | pipeline PIPELINE0 port in 0 source MEMPOOL0 ./examples/pipeline/examples/packet.pcap loop 1 9 | pipeline PIPELINE0 port in 1 source MEMPOOL0 ./examples/pipeline/examples/packet.pcap loop 1 10 | pipeline PIPELINE0 port in 2 source MEMPOOL0 ./examples/pipeline/examples/packet.pcap loop 1 11 | pipeline PIPELINE0 port in 3 source MEMPOOL0 ./examples/pipeline/examples/packet.pcap loop 1 12 | 13 | pipeline PIPELINE0 port out 0 sink none 14 | pipeline PIPELINE0 port out 1 sink none 15 | pipeline PIPELINE0 port out 2 sink none 16 | pipeline PIPELINE0 port out 3 sink none 17 | 18 | pipeline PIPELINE0 build ./examples/pipeline/examples/vxlan.spec 19 | pipeline PIPELINE0 table vxlan_table add ./examples/pipeline/examples/vxlan_table.txt 20 | pipeline PIPELINE0 commit 21 | 22 | thread 1 pipeline PIPELINE0 enable 23 | -------------------------------------------------------------------------------- /examples/vxlan-dpdk/vxlan_table.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright(c) 2020 Intel Corporation 4 | # 5 | 6 | """ 7 | A Python program that generates the VXLAN tunnels for this example. 8 | """ 9 | 10 | import argparse 11 | 12 | DESCRIPTION = 'Table Generator' 13 | 14 | KEY = '0xaabbccdd{0:04x}' 15 | ACTION = 'vxlan_encap' 16 | ETHERNET_HEADER = 'ethernet_dst_addr 0xa0a1a2a3{0:04x} ' \ 17 | 'ethernet_src_addr 0xb0b1b2b3{0:04x} ' \ 18 | 'ethernet_ethertype 0x0800' 19 | IPV4_HEADER = 'ipv4_ver_ihl 0x45 ' \ 20 | 'ipv4_diffserv 0 ' \ 21 | 'ipv4_total_len 50 ' \ 22 | 'ipv4_identification 0 ' \ 23 | 'ipv4_flags_offset 0 ' \ 24 | 'ipv4_ttl 64 ' \ 25 | 'ipv4_protocol 17 ' \ 26 | 'ipv4_hdr_checksum 0x{1:04x} ' \ 27 | 'ipv4_src_addr 0xc0c1{0:04x} ' \ 28 | 'ipv4_dst_addr 0xd0d1{0:04x}' 29 | UDP_HEADER = 'udp_src_port 0xe0{0:02x} ' \ 30 | 'udp_dst_port 4789 ' \ 31 | 'udp_length 30 ' \ 32 | 'udp_checksum 0' 33 | VXLAN_HEADER = 'vxlan_flags 0 ' \ 34 | 'vxlan_reserved 0 ' \ 35 | 'vxlan_vni {0:d} ' \ 36 | 'vxlan_reserved2 0' 37 | PORT_OUT = 'port_out {0:d}' 38 | 39 | def ipv4_header_checksum(i): 40 | cksum = (0x4500 + 0x0032) + (0x0000 + 0x0000) + (0x4011 + 0x0000) + (0xc0c1 + i) + (0xd0d1 + i) 41 | cksum = (cksum & 0xFFFF) + (cksum >> 16) 42 | cksum = (cksum & 0xFFFF) + (cksum >> 16) 43 | cksum = ~cksum & 0xFFFF 44 | return cksum 45 | 46 | def table_generate(n, p): 47 | for i in range(0, n): 48 | print("match %s action %s %s %s %s %s %s" % 49 | (KEY.format(i), ACTION, 50 | ETHERNET_HEADER.format(i), 51 | IPV4_HEADER.format(i, ipv4_header_checksum(i)), 52 | UDP_HEADER.format(i % 256), 53 | VXLAN_HEADER.format(i), 54 | PORT_OUT.format(i % p))) 55 | 56 | if __name__ == '__main__': 57 | parser = argparse.ArgumentParser(description=DESCRIPTION) 58 | 59 | parser.add_argument( 60 | '-n', 61 | help='number of table entries (default: 65536)', 62 | required=False, 63 | default=65536) 64 | 65 | parser.add_argument( 66 | '-p', 67 | help='number of network ports (default: 4)', 68 | required=False, 69 | default=4) 70 | 71 | args = parser.parse_args() 72 | table_generate(int(args.n), int(args.p)) 73 | -------------------------------------------------------------------------------- /examples/vxlan/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2022 - Sander Tolsma. All rights reserved 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | docker run --rm -u 1000:1000 -v "${PWD}":/p4ccode -w /p4ccode stolsma/p4c-all:latest /bin/bash -c "p4c-dpdk -o vxlan.spec --p4runtime-files p4info.proto.txt vxlan.p4" -------------------------------------------------------------------------------- /examples/vxlan/old/p4info.proto.txt: -------------------------------------------------------------------------------- 1 | pkg_info { 2 | arch: "v1model" 3 | } 4 | tables { 5 | preamble { 6 | id: 33686666 7 | name: "ingress.vxlan" 8 | alias: "vxlan" 9 | } 10 | match_fields { 11 | id: 1 12 | name: "headers.ethernet.dst_addr" 13 | bitwidth: 48 14 | match_type: EXACT 15 | } 16 | action_refs { 17 | id: 32131245 18 | } 19 | action_refs { 20 | id: 33281717 21 | } 22 | const_default_action_id: 33281717 23 | size: 1048576 24 | } 25 | actions { 26 | preamble { 27 | id: 32131245 28 | name: "ingress.vxlan_encap" 29 | alias: "vxlan_encap" 30 | } 31 | params { 32 | id: 1 33 | name: "ethernet_dst_addr" 34 | bitwidth: 48 35 | } 36 | params { 37 | id: 2 38 | name: "ethernet_src_addr" 39 | bitwidth: 48 40 | } 41 | params { 42 | id: 3 43 | name: "ethernet_ether_type" 44 | bitwidth: 16 45 | } 46 | params { 47 | id: 4 48 | name: "ipv4_ver_ihl" 49 | bitwidth: 8 50 | } 51 | params { 52 | id: 5 53 | name: "ipv4_diffserv" 54 | bitwidth: 8 55 | } 56 | params { 57 | id: 6 58 | name: "ipv4_total_len" 59 | bitwidth: 16 60 | } 61 | params { 62 | id: 7 63 | name: "ipv4_identification" 64 | bitwidth: 16 65 | } 66 | params { 67 | id: 8 68 | name: "ipv4_flags_offset" 69 | bitwidth: 16 70 | } 71 | params { 72 | id: 9 73 | name: "ipv4_ttl" 74 | bitwidth: 8 75 | } 76 | params { 77 | id: 10 78 | name: "ipv4_protocol" 79 | bitwidth: 8 80 | } 81 | params { 82 | id: 11 83 | name: "ipv4_hdr_checksum" 84 | bitwidth: 16 85 | } 86 | params { 87 | id: 12 88 | name: "ipv4_src_addr" 89 | bitwidth: 32 90 | } 91 | params { 92 | id: 13 93 | name: "ipv4_dst_addr" 94 | bitwidth: 32 95 | } 96 | params { 97 | id: 14 98 | name: "udp_src_port" 99 | bitwidth: 16 100 | } 101 | params { 102 | id: 15 103 | name: "udp_dst_port" 104 | bitwidth: 16 105 | } 106 | params { 107 | id: 16 108 | name: "udp_length" 109 | bitwidth: 16 110 | } 111 | params { 112 | id: 17 113 | name: "udp_checksum" 114 | bitwidth: 16 115 | } 116 | params { 117 | id: 18 118 | name: "vxlan_flags" 119 | bitwidth: 8 120 | } 121 | params { 122 | id: 19 123 | name: "vxlan_reserved" 124 | bitwidth: 24 125 | } 126 | params { 127 | id: 20 128 | name: "vxlan_vni" 129 | bitwidth: 24 130 | } 131 | params { 132 | id: 21 133 | name: "vxlan_reserved2" 134 | bitwidth: 8 135 | } 136 | params { 137 | id: 22 138 | name: "port_out" 139 | bitwidth: 32 140 | } 141 | } 142 | actions { 143 | preamble { 144 | id: 33281717 145 | name: "ingress.drop" 146 | alias: "drop" 147 | } 148 | } 149 | type_info { 150 | } 151 | -------------------------------------------------------------------------------- /examples/vxlan/old/vxlan.cli: -------------------------------------------------------------------------------- 1 | ; SPDX-License-Identifier: BSD-3-Clause 2 | ; Copyright(c) 2020 Intel Corporation 3 | 4 | mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0 5 | 6 | tap sw0 7 | tap sw1 8 | tap sw2 9 | tap sw3 10 | tap sw10 11 | 12 | ; link LINK0 dev net_tap0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 13 | ; link LINK1 dev net_tap1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 14 | ; link LINK2 dev net_tap2 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 15 | ; link LINK3 dev net_tap3 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 16 | 17 | pipeline PIPELINE0 create 0 18 | 19 | ; pipeline PIPELINE0 port in 0 link LINK0 rxq 0 bsz 32 20 | ; pipeline PIPELINE0 port in 1 link LINK1 rxq 0 bsz 32 21 | ; pipeline PIPELINE0 port in 2 link LINK2 rxq 0 bsz 32 22 | ; pipeline PIPELINE0 port in 3 link LINK3 rxq 0 bsz 32 23 | pipeline PIPELINE0 port in 0 tap sw0 mempool MEMPOOL0 mtu 1200 bsz 32 24 | pipeline PIPELINE0 port in 1 tap sw1 mempool MEMPOOL0 mtu 1200 bsz 32 25 | pipeline PIPELINE0 port in 2 tap sw2 mempool MEMPOOL0 mtu 1200 bsz 32 26 | pipeline PIPELINE0 port in 3 tap sw3 mempool MEMPOOL0 mtu 1200 bsz 32 27 | pipeline PIPELINE0 port in 5 tap sw10 mempool MEMPOOL0 mtu 1200 bsz 32 28 | 29 | ; pipeline PIPELINE0 port out 0 link LINK0 txq 0 bsz 32 30 | ; pipeline PIPELINE0 port out 1 link LINK1 txq 0 bsz 32 31 | ; pipeline PIPELINE0 port out 2 link LINK2 txq 0 bsz 32 32 | ; pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32 33 | pipeline PIPELINE0 port out 0 tap sw0 bsz 32 34 | pipeline PIPELINE0 port out 1 tap sw1 bsz 32 35 | pipeline PIPELINE0 port out 2 tap sw2 bsz 32 36 | pipeline PIPELINE0 port out 3 tap sw3 bsz 32 37 | pipeline PIPELINE0 port out 4 sink none 38 | pipeline PIPELINE0 port out 5 tap sw10 bsz 32 39 | 40 | pipeline PIPELINE0 build ./examples/vxlan/vxlan.spec 41 | pipeline PIPELINE0 table vxlan add ./examples/vxlan/vxlan_table.txt 42 | ; pipeline PIPELINE0 table vxlan update ./vxlan_table.txt none none 43 | 44 | pipeline PIPELINE0 commit 45 | 46 | thread 1 pipeline PIPELINE0 enable 47 | -------------------------------------------------------------------------------- /examples/vxlan/p4info.proto.txt: -------------------------------------------------------------------------------- 1 | pkg_info { 2 | arch: "v1model" 3 | } 4 | tables { 5 | preamble { 6 | id: 33686666 7 | name: "ingress.vxlan" 8 | alias: "vxlan" 9 | } 10 | match_fields { 11 | id: 1 12 | name: "headers.ethernet.dst_addr" 13 | bitwidth: 48 14 | match_type: EXACT 15 | } 16 | action_refs { 17 | id: 32131245 18 | } 19 | action_refs { 20 | id: 33281717 21 | } 22 | const_default_action_id: 33281717 23 | size: 1048576 24 | } 25 | actions { 26 | preamble { 27 | id: 32131245 28 | name: "ingress.vxlan_encap" 29 | alias: "vxlan_encap" 30 | } 31 | params { 32 | id: 1 33 | name: "ethernet_dst_addr" 34 | bitwidth: 48 35 | } 36 | params { 37 | id: 2 38 | name: "ethernet_src_addr" 39 | bitwidth: 48 40 | } 41 | params { 42 | id: 3 43 | name: "ethernet_ether_type" 44 | bitwidth: 16 45 | } 46 | params { 47 | id: 4 48 | name: "ipv4_ver_ihl" 49 | bitwidth: 8 50 | } 51 | params { 52 | id: 5 53 | name: "ipv4_diffserv" 54 | bitwidth: 8 55 | } 56 | params { 57 | id: 6 58 | name: "ipv4_total_len" 59 | bitwidth: 16 60 | } 61 | params { 62 | id: 7 63 | name: "ipv4_identification" 64 | bitwidth: 16 65 | } 66 | params { 67 | id: 8 68 | name: "ipv4_flags_offset" 69 | bitwidth: 16 70 | } 71 | params { 72 | id: 9 73 | name: "ipv4_ttl" 74 | bitwidth: 8 75 | } 76 | params { 77 | id: 10 78 | name: "ipv4_protocol" 79 | bitwidth: 8 80 | } 81 | params { 82 | id: 11 83 | name: "ipv4_hdr_checksum" 84 | bitwidth: 16 85 | } 86 | params { 87 | id: 12 88 | name: "ipv4_src_addr" 89 | bitwidth: 32 90 | } 91 | params { 92 | id: 13 93 | name: "ipv4_dst_addr" 94 | bitwidth: 32 95 | } 96 | params { 97 | id: 14 98 | name: "udp_src_port" 99 | bitwidth: 16 100 | } 101 | params { 102 | id: 15 103 | name: "udp_dst_port" 104 | bitwidth: 16 105 | } 106 | params { 107 | id: 16 108 | name: "udp_length" 109 | bitwidth: 16 110 | } 111 | params { 112 | id: 17 113 | name: "udp_checksum" 114 | bitwidth: 16 115 | } 116 | params { 117 | id: 18 118 | name: "vxlan_flags" 119 | bitwidth: 8 120 | } 121 | params { 122 | id: 19 123 | name: "vxlan_reserved" 124 | bitwidth: 24 125 | } 126 | params { 127 | id: 20 128 | name: "vxlan_vni" 129 | bitwidth: 24 130 | } 131 | params { 132 | id: 21 133 | name: "vxlan_reserved2" 134 | bitwidth: 8 135 | } 136 | params { 137 | id: 22 138 | name: "port_out" 139 | bitwidth: 32 140 | } 141 | } 142 | actions { 143 | preamble { 144 | id: 33281717 145 | name: "ingress.drop" 146 | alias: "drop" 147 | } 148 | } 149 | type_info { 150 | } 151 | -------------------------------------------------------------------------------- /examples/vxlan/tools/packet.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stolsma/go-p4pack/8fa78e2581bf4935184365ece1842a7de8760036/examples/vxlan/tools/packet.pcap -------------------------------------------------------------------------------- /examples/vxlan/tools/vxlan_pcap.cli: -------------------------------------------------------------------------------- 1 | ; SPDX-License-Identifier: BSD-3-Clause 2 | ; Copyright(c) 2020 Intel Corporation 3 | 4 | mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0 5 | 6 | pipeline PIPELINE0 create 0 7 | 8 | pipeline PIPELINE0 port in 0 source MEMPOOL0 ./packet.pcap 9 | pipeline PIPELINE0 port in 1 source MEMPOOL0 ./packet.pcap 10 | pipeline PIPELINE0 port in 2 source MEMPOOL0 ./packet.pcap 11 | pipeline PIPELINE0 port in 3 source MEMPOOL0 ./packet.pcap 12 | 13 | pipeline PIPELINE0 port out 0 sink none 14 | pipeline PIPELINE0 port out 1 sink none 15 | pipeline PIPELINE0 port out 2 sink none 16 | pipeline PIPELINE0 port out 3 sink none 17 | pipeline PIPELINE0 port out 4 sink none 18 | 19 | pipeline PIPELINE0 build ./vxlan.spec 20 | pipeline PIPELINE0 table vxlan update ./vxlan_table.txt none none 21 | 22 | thread 1 pipeline PIPELINE0 enable 23 | -------------------------------------------------------------------------------- /examples/vxlan/tools/vxlan_table.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | # Copyright(c) 2020 Intel Corporation 4 | # 5 | 6 | """ 7 | A Python program that generates the VXLAN tunnels for this example. 8 | """ 9 | 10 | import argparse 11 | 12 | DESCRIPTION = 'Table Generator' 13 | 14 | KEY = '0xaabbccdd{0:04x}' 15 | ACTION = 'vxlan_encap' 16 | ETHERNET_HEADER = 'ethernet_dst_addr 0xa0a1a2a3{0:04x} ' \ 17 | 'ethernet_src_addr 0xb0b1b2b3{0:04x} ' \ 18 | 'ethernet_ether_type 0x0800' 19 | IPV4_HEADER = 'ipv4_ver_ihl 0x45 ' \ 20 | 'ipv4_diffserv 0 ' \ 21 | 'ipv4_total_len 50 ' \ 22 | 'ipv4_identification 0 ' \ 23 | 'ipv4_flags_offset 0 ' \ 24 | 'ipv4_ttl 64 ' \ 25 | 'ipv4_protocol 17 ' \ 26 | 'ipv4_hdr_checksum 0x{1:04x} ' \ 27 | 'ipv4_src_addr 0xc0c1{0:04x} ' \ 28 | 'ipv4_dst_addr 0xd0d1{0:04x}' 29 | UDP_HEADER = 'udp_src_port 0xe0{0:02x} ' \ 30 | 'udp_dst_port 4789 ' \ 31 | 'udp_length 30 ' \ 32 | 'udp_checksum 0' 33 | VXLAN_HEADER = 'vxlan_flags 0 ' \ 34 | 'vxlan_reserved 0 ' \ 35 | 'vxlan_vni {0:d} ' \ 36 | 'vxlan_reserved2 0' 37 | PORT_OUT = 'port_out {0:d}' 38 | 39 | def ipv4_header_checksum(i): 40 | cksum = (0x4500 + 0x0032) + (0x0000 + 0x0000) + (0x4011 + 0x0000) + (0xc0c1 + i) + (0xd0d1 + i) 41 | cksum = (cksum & 0xFFFF) + (cksum >> 16) 42 | cksum = (cksum & 0xFFFF) + (cksum >> 16) 43 | cksum = ~cksum & 0xFFFF 44 | return cksum 45 | 46 | def table_generate(n, p): 47 | for i in range(0, n): 48 | print("match %s action %s %s %s %s %s %s" % 49 | (KEY.format(i), ACTION, 50 | ETHERNET_HEADER.format(i), 51 | IPV4_HEADER.format(i, ipv4_header_checksum(i)), 52 | UDP_HEADER.format(i % 256), 53 | VXLAN_HEADER.format(i), 54 | PORT_OUT.format(i % p))) 55 | 56 | if __name__ == '__main__': 57 | parser = argparse.ArgumentParser(description=DESCRIPTION) 58 | 59 | parser.add_argument( 60 | '-n', 61 | help='number of table entries (default: 65536)', 62 | required=False, 63 | default=65536) 64 | 65 | parser.add_argument( 66 | '-p', 67 | help='number of network ports (default: 4)', 68 | required=False, 69 | default=4) 70 | 71 | args = parser.parse_args() 72 | table_generate(int(args.n), int(args.p)) 73 | -------------------------------------------------------------------------------- /examples/vxlan/vxlan.cli: -------------------------------------------------------------------------------- 1 | ; SPDX-License-Identifier: BSD-3-Clause 2 | ; Copyright(c) 2020 Intel Corporation 3 | 4 | mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0 5 | 6 | tap sw0 7 | tap sw1 8 | 9 | ; link LINK0 dev net_tap0 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 10 | ; link LINK1 dev net_tap1 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 11 | ; link LINK2 dev net_tap2 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 12 | ; link LINK3 dev net_tap3 rxq 1 128 MEMPOOL0 txq 1 512 promiscuous on 13 | 14 | pipeline PIPELINE0 create 0 15 | 16 | ; pipeline PIPELINE0 port in 0 link LINK0 rxq 0 bsz 32 17 | ; pipeline PIPELINE0 port in 1 link LINK1 rxq 0 bsz 32 18 | ; pipeline PIPELINE0 port in 2 link LINK2 rxq 0 bsz 32 19 | ; pipeline PIPELINE0 port in 3 link LINK3 rxq 0 bsz 32 20 | pipeline PIPELINE0 port in 0 tap sw0 mempool MEMPOOL0 mtu 1200 bsz 32 21 | pipeline PIPELINE0 port in 1 tap sw1 mempool MEMPOOL0 mtu 1200 bsz 32 22 | 23 | ; pipeline PIPELINE0 port out 0 link LINK0 txq 0 bsz 32 24 | ; pipeline PIPELINE0 port out 1 link LINK1 txq 0 bsz 32 25 | ; pipeline PIPELINE0 port out 2 link LINK2 txq 0 bsz 32 26 | ; pipeline PIPELINE0 port out 3 link LINK3 txq 0 bsz 32 27 | pipeline PIPELINE0 port out 0 tap sw0 bsz 32 28 | pipeline PIPELINE0 port out 1 tap sw1 bsz 32 29 | ; pipeline PIPELINE0 port out 4 sink none 30 | 31 | pipeline PIPELINE0 build ./examples/vxlan/vxlan.spec 32 | ; pipeline PIPELINE0 table vxlan add ./examples/vxlan/vxlan_table.txt 33 | ; pipeline PIPELINE0 table vxlan update ./vxlan_table.txt none none 34 | 35 | pipeline PIPELINE0 commit 36 | 37 | thread 1 pipeline PIPELINE0 enable 38 | -------------------------------------------------------------------------------- /go-p4pack.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright 2022 - Sander Tolsma. All rights reserved 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | sudo mkdir /mnt/huge 7 | sudo mount -t hugetlbfs nodev /mnt/huge 8 | sudo sysctl -w vm.nr_hugepages=256 9 | 10 | docker run \ 11 | --name go-p4pack \ 12 | --rm \ 13 | --cap-add ALL \ 14 | --privileged \ 15 | -v "${PWD}":/dummy \ 16 | -v /mnt/huge:/mnt/huge \ 17 | -v /sys/bus/pci/devices:/sys/bus/pci/devices \ 18 | -v /sys/devices/system/node:/sys/devices/system/node \ 19 | -v /dev:/dev \ 20 | -p 9339:9339 \ 21 | -p 9559:9559 \ 22 | -p 2222:2222 \ 23 | --entrypoint /bin/bash \ 24 | -it ghcr.io/stolsma/go-p4pack:latest 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stolsma/go-p4pack 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/golang/glog v1.0.0 7 | github.com/openconfig/gnmi v0.0.0-20220920173703-480bf53a74d2 8 | github.com/openconfig/goyang v1.2.0 //implicit 9 | github.com/openconfig/ygot v0.25.4 10 | github.com/p4lang/p4runtime v1.3.0 11 | github.com/vishvananda/netlink v1.1.0 12 | github.com/yerden/go-dpdk v0.0.8 13 | golang.org/x/sys v0.3.0 14 | google.golang.org/grpc v1.51.0 15 | google.golang.org/protobuf v1.28.1 16 | ) 17 | 18 | require ( 19 | github.com/gliderlabs/ssh v0.3.5 20 | github.com/golang/protobuf v1.5.2 // indirect 21 | github.com/google/go-cmp v0.5.9 // indirect 22 | github.com/kylelemons/godebug v1.1.0 // indirect 23 | github.com/vishvananda/netns v0.0.1 // indirect 24 | golang.org/x/net v0.3.0 25 | golang.org/x/text v0.5.0 // indirect 26 | google.golang.org/genproto v0.0.0-20221205194025-8222ab48f5fc // indirect 27 | ) 28 | 29 | require ( 30 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect 31 | github.com/spf13/cobra v1.6.1 32 | golang.org/x/crypto v0.3.0 33 | ) 34 | 35 | require ( 36 | github.com/mdlayher/packet v1.1.0 37 | github.com/openconfig/ygnmi v0.7.0 38 | github.com/spf13/pflag v1.0.5 39 | ) 40 | 41 | require ( 42 | github.com/davecgh/go-spew v1.1.1 // indirect 43 | github.com/eapache/go-resiliency v1.3.0 // indirect 44 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect 45 | github.com/eapache/queue v1.1.0 // indirect 46 | github.com/golang/snappy v0.0.4 // indirect 47 | github.com/hashicorp/errwrap v1.1.0 // indirect 48 | github.com/hashicorp/go-multierror v1.1.1 // indirect 49 | github.com/hashicorp/go-uuid v1.0.3 // indirect 50 | github.com/jcmturner/aescts/v2 v2.0.0 // indirect 51 | github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect 52 | github.com/jcmturner/gofork v1.7.6 // indirect 53 | github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect 54 | github.com/jcmturner/rpc/v2 v2.0.3 // indirect 55 | github.com/josharian/native v1.0.0 // indirect 56 | github.com/klauspost/compress v1.15.12 // indirect 57 | github.com/mdlayher/socket v0.4.0 // indirect 58 | github.com/pierrec/lz4/v4 v4.1.17 // indirect 59 | github.com/pmezard/go-difflib v1.0.0 // indirect 60 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect 61 | github.com/rogpeppe/go-internal v1.9.0 // indirect 62 | go.uber.org/atomic v1.10.0 // indirect 63 | go.uber.org/multierr v1.8.0 // indirect 64 | golang.org/x/sync v0.1.0 // indirect 65 | ) 66 | 67 | require ( 68 | github.com/Shopify/sarama v1.37.2 69 | github.com/cenkalti/backoff/v4 v4.2.0 // indirect 70 | github.com/fsnotify/fsnotify v1.6.0 // indirect 71 | github.com/hashicorp/hcl v1.0.0 // indirect 72 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 73 | github.com/magiconair/properties v1.8.6 // indirect 74 | github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 75 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 76 | github.com/mitchellh/mapstructure v1.5.0 // indirect 77 | github.com/openconfig/gocloser v0.0.0-20220310182203-c6c950ed3b0b // indirect 78 | github.com/openconfig/grpctunnel v0.0.0-20220819142823-6f5422b8ca70 // indirect 79 | github.com/pelletier/go-toml v1.9.5 // indirect 80 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 81 | github.com/pkg/errors v0.9.1 // indirect 82 | github.com/protocolbuffers/txtpbfmt v0.0.0-20221206070812-31e4035b9046 // indirect 83 | github.com/spf13/afero v1.9.3 // indirect 84 | github.com/spf13/cast v1.5.0 // indirect 85 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 86 | github.com/spf13/viper v1.14.0 // indirect 87 | github.com/stretchr/testify v1.8.1 88 | github.com/subosito/gotenv v1.4.1 // indirect 89 | go.uber.org/zap v1.24.0 90 | golang.org/x/exp v0.0.0-20221205204356-47842c84f3db 91 | golang.org/x/term v0.3.0 92 | gopkg.in/ini.v1 v1.67.0 // indirect 93 | gopkg.in/yaml.v2 v2.4.0 94 | gopkg.in/yaml.v3 v3.0.1 // indirect 95 | ) 96 | -------------------------------------------------------------------------------- /pkg/cli/cli.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "io" 8 | "strings" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | const ( 14 | ETX = 0x3 // control-C 15 | ) 16 | 17 | // wait for CTRL-C 18 | func WaitForCtrlC(input io.Reader) { 19 | buf := make([]byte, 1) 20 | for { 21 | amount, err := input.Read(buf) 22 | if err != nil { 23 | break 24 | } 25 | 26 | if amount > 0 { 27 | ch := buf[0] 28 | if ch == ETX { 29 | break 30 | } 31 | } 32 | } 33 | } 34 | 35 | // add cobra command to given parent(s) and returns given command 36 | func AddCommand(parents []*cobra.Command, command *cobra.Command) *cobra.Command { 37 | if len(parents) > 0 { 38 | parents[0].AddCommand(command) 39 | } 40 | 41 | return command 42 | } 43 | 44 | type ValidateFn = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) 45 | 46 | // execute completion commands per argument 47 | func ValidateArguments(fns ...ValidateFn) ValidateFn { 48 | return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 49 | var completions []string 50 | var directive cobra.ShellCompDirective 51 | var fnLen = len(fns) 52 | var argLen = len(args) 53 | 54 | // call completion function related to the current argument position 55 | if argLen <= fnLen-2 { 56 | completions, directive = fns[argLen](cmd, args, toComplete) 57 | } 58 | 59 | // if last real argument has no completion or if pos of current argument > max arguments call catch-all function 60 | if argLen >= fnLen-2 && len(completions) == 0 { 61 | comp, direct := fns[fnLen-1](cmd, args, toComplete) 62 | if len(comp) > 0 { 63 | completions = comp 64 | directive = direct 65 | } 66 | } 67 | 68 | return completions, directive 69 | } 70 | } 71 | 72 | // add active help text for argument, and show if argument is empty 73 | func AppendHelp(helpTxt string) ValidateFn { 74 | return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 75 | var completions []string 76 | directive := cobra.ShellCompDirectiveNoFileComp 77 | 78 | // help needed 79 | if toComplete == "" { 80 | completions = cobra.AppendActiveHelp(completions, helpTxt) 81 | directive |= cobra.ShellCompDirectiveNoSpace 82 | } 83 | 84 | return completions, directive 85 | } 86 | } 87 | 88 | // add active help text shown when last valid argument is already entered 89 | func AppendLastHelp(total int, helpTxt string) ValidateFn { 90 | return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 91 | var completions []string 92 | directive := cobra.ShellCompDirectiveNoFileComp 93 | 94 | // help needed 95 | if len(args) == total-1 && toComplete != "" || len(args) > total-1 { 96 | completions = cobra.AppendActiveHelp(completions, helpTxt) 97 | directive |= cobra.ShellCompDirectiveNoSpace 98 | } 99 | 100 | return completions, directive 101 | } 102 | } 103 | 104 | // filter all strings starting with string to complete 105 | func FilterCompletions(list []string, toComplete string, directive *cobra.ShellCompDirective, help string) []string { 106 | var completions []string 107 | 108 | // always no space 109 | *directive |= cobra.ShellCompDirectiveNoSpace 110 | 111 | // filter list 112 | for _, name := range list { 113 | if strings.HasPrefix(name, toComplete) { 114 | completions = append(completions, name) 115 | } 116 | } 117 | 118 | // if none filtered, select all strings 119 | if len(completions) == 0 { 120 | completions = list 121 | } 122 | 123 | // if only one answer left and that is same as toComplete return nothing 124 | if len(completions) == 1 && completions[0] == toComplete { 125 | completions = []string{} 126 | *directive ^= cobra.ShellCompDirectiveNoSpace 127 | } else if len(completions) == 0 { 128 | completions = cobra.AppendActiveHelp(completions, help) 129 | } 130 | 131 | return completions 132 | } 133 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package config 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | ) 12 | 13 | type Type interface { 14 | SetBasePath(bp string) 15 | GetBasePath() string 16 | } 17 | 18 | type Base struct { 19 | basePath string 20 | } 21 | 22 | func (b *Base) SetBasePath(bp string) { 23 | b.basePath = bp 24 | } 25 | 26 | func (b *Base) GetBasePath() string { 27 | return b.basePath 28 | } 29 | 30 | func LoadConfig(filename string, c Type) error { 31 | data, err := os.ReadFile(filename) 32 | if err != nil { 33 | return fmt.Errorf("error when opening file: %s", err) 34 | } 35 | 36 | if err := json.Unmarshal(data, c); err != nil { 37 | return fmt.Errorf("JSON unmarshaling failed: %s", err) 38 | } 39 | 40 | // save the basepath of the config file read 41 | c.SetBasePath(filepath.Dir(filename)) 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /pkg/config/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package config 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stolsma/go-p4pack/pkg/flowtest" 10 | "github.com/stolsma/go-p4pack/pkg/logging" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | type empty struct{} 15 | 16 | var filename = "../../examples/default/config.json" 17 | 18 | type Config struct { 19 | *Base 20 | FlowTest *flowtest.Config `json:"flowtest"` 21 | Logging *logging.Config `json:"logging"` 22 | } 23 | 24 | func TestLoad(t *testing.T) { 25 | var c = Config{Base: &Base{}} 26 | err := LoadConfig(filename, &c) 27 | if err != nil { 28 | return 29 | } 30 | assert.Equal(t, "../../examples/default", c.GetBasePath()) 31 | assert.NotEqual(t, empty{}, c) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/cli/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | "github.com/stolsma/go-p4pack/pkg/cli" 9 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra" 10 | ) 11 | 12 | func InterfaceCmd(parents ...*cobra.Command) *cobra.Command { 13 | interfaceCmd := &cobra.Command{ 14 | Use: "interface", 15 | Short: "Base command for all interface actions", 16 | Aliases: []string{"int"}, 17 | } 18 | 19 | InterfaceDeviceCmd(interfaceCmd) 20 | InterfaceCreateCmd(interfaceCmd) 21 | InterfaceShowCmd(interfaceCmd) 22 | InterfaceStatsCmd(interfaceCmd) 23 | InterfaceLinkUpDownCmd(interfaceCmd) 24 | return cli.AddCommand(parents, interfaceCmd) 25 | } 26 | 27 | func InterfaceLinkUpDownCmd(parents ...*cobra.Command) *cobra.Command { 28 | ludCmd := &cobra.Command{ 29 | Use: "link [name] [up/down]", 30 | Short: "Set the interface up or down", 31 | Aliases: []string{"set"}, 32 | Args: cobra.MaximumNArgs(2), 33 | ValidArgsFunction: cli.ValidateArguments( 34 | completePortList, 35 | cli.AppendHelp("Set the interface state to up or down (up/down)"), 36 | cli.AppendLastHelp(2, "This command does not take any more arguments"), 37 | ), 38 | Run: func(cmd *cobra.Command, args []string) { 39 | dpdki := dpdkinfra.Get() 40 | var err error 41 | ud := "" 42 | t := "" 43 | if len(args) == 2 { 44 | t = args[0] 45 | ud = args[1] 46 | } 47 | 48 | switch ud { 49 | case "up": 50 | err = dpdki.LinkUp(t) 51 | case "down": 52 | err = dpdki.LinkDown(t) 53 | default: 54 | cmd.PrintErrf("Use up or down and not %v !\n", ud) 55 | return 56 | } 57 | 58 | if err != nil { 59 | cmd.PrintErrf("Interface %v set up/down err: %v\n", t, err) 60 | return 61 | } 62 | cmd.Printf("Interface link state changed to: %v\n", ud) 63 | }, 64 | } 65 | 66 | return cli.AddCommand(parents, ludCmd) 67 | } 68 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/cli/interfacedevice.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | "github.com/stolsma/go-p4pack/pkg/cli" 9 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra" 10 | ) 11 | 12 | func InterfaceDeviceCmd(parents ...*cobra.Command) *cobra.Command { 13 | deviceCmd := &cobra.Command{ 14 | Use: "device", 15 | Short: "Base command for all device actions", 16 | Aliases: []string{"dev"}, 17 | } 18 | 19 | InterfaceDeviceListCmd(deviceCmd) 20 | InterfaceDeviceAttachCmd(deviceCmd) 21 | InterfaceDeviceDetachCmd(deviceCmd) 22 | return cli.AddCommand(parents, deviceCmd) 23 | } 24 | 25 | func InterfaceDeviceListCmd(parents ...*cobra.Command) *cobra.Command { 26 | var used, notused bool 27 | listCmd := &cobra.Command{ 28 | Use: "list", 29 | Short: "List all attached (hotplug) devices on the system", 30 | Aliases: []string{"l"}, 31 | ValidArgsFunction: cli.ValidateArguments( 32 | cli.AppendLastHelp(0, "This command does not take any more arguments"), 33 | ), 34 | Run: func(cmd *cobra.Command, args []string) { 35 | var devices []string 36 | var hs string 37 | 38 | switch { 39 | case used: 40 | devices = deviceList(UsedDevices) 41 | hs = "Used" 42 | case notused: 43 | devices = deviceList(UnusedDevices) 44 | hs = "Not used" 45 | default: 46 | devices = deviceList(AllDevices) 47 | hs = "All" 48 | } 49 | 50 | cmd.Printf("%s attached DPDK devices:\n", hs) 51 | for _, device := range devices { 52 | cmd.Printf(" %s\n", device) 53 | } 54 | }, 55 | } 56 | listCmd.Flags().BoolVarP(&used, "used", "u", false, "Show all used DPDK devices.") 57 | listCmd.Flags().BoolVarP(¬used, "notused", "n", false, "Show all not used DPDK devices.") 58 | listCmd.MarkFlagsMutuallyExclusive("used", "notused") 59 | return cli.AddCommand(parents, listCmd) 60 | } 61 | 62 | func InterfaceDeviceAttachCmd(parents ...*cobra.Command) *cobra.Command { 63 | attachCmd := &cobra.Command{ 64 | Use: "attach [device argument string]", 65 | Short: "Attach a DPDK device on the system via hotplug procedure", 66 | Aliases: []string{"a"}, 67 | Args: cobra.ExactArgs(1), 68 | ValidArgsFunction: cli.ValidateArguments( 69 | cli.AppendHelp("You must specify the DPDK device argument string for the interface you are attaching"), 70 | cli.AppendLastHelp(1, "This command does not take any more arguments"), 71 | ), 72 | Run: func(cmd *cobra.Command, args []string) { 73 | dpdki := dpdkinfra.Get() 74 | 75 | devArgs, err := dpdki.AttachDevice(args[0]) 76 | if err != nil { 77 | cmd.PrintErrf("Error creating device: %v", err) 78 | return 79 | } 80 | 81 | cmd.Printf("Requested device (%s) created via hotplug on bus %s!\n", devArgs.Name(), devArgs.Bus()) 82 | }, 83 | } 84 | 85 | return cli.AddCommand(parents, attachCmd) 86 | } 87 | 88 | func InterfaceDeviceDetachCmd(parents ...*cobra.Command) *cobra.Command { 89 | detachCmd := &cobra.Command{ 90 | Use: "detach [device name]", 91 | Short: "Detach a DPDK device from the system via hotplug procedure", 92 | Aliases: []string{"a"}, 93 | Args: cobra.ExactArgs(1), 94 | ValidArgsFunction: cli.ValidateArguments( 95 | completeUnusedDeviceList, 96 | cli.AppendLastHelp(1, "This command does not take any more arguments"), 97 | ), 98 | Run: func(cmd *cobra.Command, args []string) { 99 | dpdki := dpdkinfra.Get() 100 | 101 | _, err := dpdki.DetachDevice(args[0]) 102 | if err != nil { 103 | cmd.PrintErrf("Error detaching device: %v\n", err) 104 | return 105 | } 106 | 107 | cmd.Printf("Device %s is detached!\n", args[0]) 108 | }, 109 | } 110 | 111 | return cli.AddCommand(parents, detachCmd) 112 | } 113 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/cli/interfaceshow.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "sort" 8 | 9 | "github.com/spf13/cobra" 10 | "github.com/stolsma/go-p4pack/pkg/cli" 11 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra" 12 | ) 13 | 14 | func InterfaceShowCmd(parents ...*cobra.Command) *cobra.Command { 15 | showCmd := &cobra.Command{ 16 | Use: "show [portname]", 17 | Short: "Show information of all (or one given) interface(s)", 18 | Aliases: []string{"sh"}, 19 | Args: cobra.MaximumNArgs(1), 20 | ValidArgsFunction: cli.ValidateArguments( 21 | completePortList, 22 | cli.AppendLastHelp(1, "This command does not take any more arguments"), 23 | ), 24 | Run: func(cmd *cobra.Command, args []string) { 25 | dpdki := dpdkinfra.Get() 26 | t := "" 27 | if len(args) == 1 { 28 | t = args[0] 29 | } 30 | 31 | info, err := dpdki.GetPortInfo(t) 32 | if err != nil { 33 | cmd.PrintErrf("Interface %v info err: %v\n", t, err) 34 | } 35 | 36 | var names []string 37 | for name := range info { 38 | names = append(names, name) 39 | } 40 | sort.Strings(names) 41 | 42 | for _, name := range names { 43 | hd := info[name]["header"] 44 | cmd.Printf("\nInterface: %v <%v rx: %v tx: %v>\n", name, hd["type"], hd["rxqueuebound"], hd["txqueuebound"]) 45 | 46 | if _, ok := info[name]["err"]; ok { 47 | cmd.Printf(" Information: %v\n", info[name]["err"]["err"]) 48 | } else { 49 | st := info[name]["info"] 50 | cmd.Print("\n") 51 | 52 | switch hd["type"] { 53 | case "PMD": 54 | cmd.Printf(" Status : %s\n", st["status"]) 55 | cmd.Printf(" Autonegotiation : %s\n", st["autoneg"]) 56 | cmd.Printf(" Duplex : %s\n", st["duplex"]) 57 | cmd.Printf(" Link speed : %s\n", st["speed"]) 58 | cmd.Printf(" Promiscuous mode : %s\n", st["promiscuous"]) 59 | cmd.Printf(" MAC address : %s\n", st["macaddr"]) 60 | cmd.Print("\n") 61 | cmd.Print(" Port specific items:\n") 62 | cmd.Printf("%s\n", st["portinfo"]) 63 | case "TAP": 64 | cmd.Print("\tno stats\n") 65 | } 66 | } 67 | } 68 | }, 69 | } 70 | 71 | return cli.AddCommand(parents, showCmd) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/cli/interfacestats.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "sort" 8 | 9 | "github.com/spf13/cobra" 10 | "github.com/stolsma/go-p4pack/pkg/cli" 11 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra" 12 | ) 13 | 14 | func InterfaceStatsCmd(parents ...*cobra.Command) *cobra.Command { 15 | var re bool 16 | statsCmd := &cobra.Command{ 17 | Use: "stats [name]", 18 | Short: "Show statistics of all (or one given) interface(s)", 19 | Aliases: []string{"st"}, 20 | Args: cobra.MaximumNArgs(1), 21 | ValidArgsFunction: cli.ValidateArguments( 22 | completePortList, 23 | cli.AppendLastHelp(1, "This command does not take any more arguments"), 24 | ), 25 | Run: func(cmd *cobra.Command, args []string) { 26 | dpdki := dpdkinfra.Get() 27 | t := "" 28 | if len(args) == 1 { 29 | t = args[0] 30 | } 31 | 32 | stats, err := dpdki.GetPortStats(t) 33 | if err != nil { 34 | cmd.PrintErrf("Interface %v stats err: %v\n", t, err) 35 | } 36 | 37 | var names []string 38 | for name := range stats { 39 | names = append(names, name) 40 | } 41 | sort.Strings(names) 42 | 43 | for _, name := range names { 44 | hd := stats[name]["header"] 45 | cmd.Printf("\nInterface: %v <%v rx: %v tx: %v>\n", name, hd["type"], hd["rxqueuebound"], hd["txqueuebound"]) 46 | 47 | if _, ok := stats[name]["err"]; ok { 48 | cmd.Printf(" Statistics: %v\n", stats[name]["err"]["err"]) 49 | } else { 50 | st := stats[name]["stats"] 51 | cmd.Print(" Statistics:\n") 52 | 53 | switch hd["type"] { 54 | case "PMD": 55 | cmd.Printf("\tRX packets: %s bytes : %s\n", st["ipackets"], st["ibytes"]) 56 | cmd.Printf("\tRX errors : %s missed: %s RX no mbuf: %s\n", st["ierrors"], st["imissed"], st["rxnombuf"]) 57 | cmd.Printf("\tTX packets: %s bytes : %s\n", st["opackets"], st["obytes"]) 58 | cmd.Printf("\tTX errors : %s\n", st["oerrors"]) 59 | case "TAP": 60 | cmd.Print("\tno stats\n") 61 | } 62 | } 63 | } 64 | }, 65 | } 66 | statsCmd.Flags().BoolVarP(&re, "repeat", "r", false, "Continuously update statistics (every second), use CTRL-C to stop.") 67 | 68 | return cli.AddCommand(parents, statsCmd) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/cli/pktmbuf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "strconv" 8 | 9 | "github.com/spf13/cobra" 10 | "github.com/stolsma/go-p4pack/pkg/cli" 11 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra" 12 | "github.com/stolsma/go-p4pack/pkg/dpdkswx/pktmbuf" 13 | ) 14 | 15 | func PktmbufCmd(parents ...*cobra.Command) *cobra.Command { 16 | var pktmbufCmd = &cobra.Command{ 17 | Use: "pktmbuf", 18 | Short: "Base command for all pktmbuf actions", 19 | } 20 | 21 | PktmbufCreateCmd(pktmbufCmd) 22 | PktmbufListCmd(pktmbufCmd) 23 | return cli.AddCommand(parents, pktmbufCmd) 24 | } 25 | 26 | // implements mempool MEMPOOL0 buffer 2304 pool 32K cache 256 cpu 0 27 | func PktmbufCreateCmd(parents ...*cobra.Command) *cobra.Command { 28 | createCmd := &cobra.Command{ 29 | Use: "create [name] [buffersize] [poolsize] [cachesize] [numaid]", 30 | Example: "Example: pktmbuf create [name] [buffersize] [poolsize] [cachesize] [numaid]", 31 | Short: "Create a pktmbuf", 32 | Long: `Create a pktmbuf with name, buffersize, poolsize, cachesize and numa-id`, 33 | Args: cobra.ExactArgs(5), 34 | ValidArgsFunction: cli.ValidateArguments( 35 | cli.AppendHelp("You must choose a name for the pktmbuf you are adding"), 36 | cli.AppendHelp("You must specify the buffersize for the pktmbuf you are adding"), 37 | cli.AppendHelp("You must specify the poolsize for the pktmbuf you are adding"), 38 | cli.AppendHelp("You must specify the cachesize for the pktmbuf you are adding"), 39 | cli.AppendHelp("You must specify the numa id for the pktmbuf you are adding"), 40 | cli.AppendLastHelp(5, "This command does not take any more arguments"), 41 | ), 42 | Run: func(cmd *cobra.Command, args []string) { 43 | dpdki := dpdkinfra.Get() 44 | 45 | // parse arguments 46 | var name = args[0] 47 | bufferSize, err := strconv.ParseUint(args[1], 10, 32) 48 | if err != nil { 49 | cmd.PrintErrf("Buffersize parse err: %v\n", err) 50 | return 51 | } 52 | poolSize, err := strconv.ParseUint(args[2], 10, 32) 53 | if err != nil { 54 | cmd.PrintErrf("Poolsize parse err: %v\n", err) 55 | return 56 | } 57 | cacheSize, err := strconv.ParseUint(args[3], 10, 32) 58 | if err != nil { 59 | cmd.PrintErrf("Cachesize parse err: %v\n", err) 60 | return 61 | } 62 | numaID, err := strconv.ParseInt(args[4], 10, 32) 63 | if err != nil { 64 | cmd.PrintErrf("NumaID parse err: %v\n", err) 65 | return 66 | } 67 | 68 | // create 69 | _, err = dpdki.PktmbufCreate(name, uint(bufferSize), uint32(poolSize), uint32(cacheSize), int(numaID)) 70 | if err != nil { 71 | cmd.PrintErrf("Pktmbuf create err: %v\n", err) 72 | } else { 73 | cmd.Printf("Pktmbuf %s created!\n", name) 74 | } 75 | }, 76 | } 77 | 78 | return cli.AddCommand(parents, createCmd) 79 | } 80 | 81 | func PktmbufListCmd(parents ...*cobra.Command) *cobra.Command { 82 | listCmd := &cobra.Command{ 83 | Use: "list", 84 | Short: "List all created Pktmbuf", 85 | Run: func(cmd *cobra.Command, args []string) { 86 | dpdki := dpdkinfra.Get() 87 | 88 | err := dpdki.PktmbufStore.Iterate(func(key string, value *pktmbuf.Pktmbuf) error { 89 | cmd.Println(key) 90 | return nil 91 | }) 92 | 93 | if err != nil { 94 | cmd.PrintErrf("List err: %v\n", err) 95 | } 96 | }, 97 | } 98 | 99 | return cli.AddCommand(parents, listCmd) 100 | } 101 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/cli/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | "github.com/stolsma/go-p4pack/pkg/logging" 9 | ) 10 | 11 | var log logging.Logger 12 | 13 | func init() { 14 | // keep the logger up to date, also after new log config 15 | logging.Register("dpdkinfra/cli", func(logger logging.Logger) { 16 | log = logger 17 | }) 18 | } 19 | 20 | // GetCommand returns the given parent (root) command with all dpdkinfra sub commands added 21 | func GetCommand(parent *cobra.Command) *cobra.Command { 22 | log.Info("Adding dpdkinfra cli commands") 23 | 24 | // add all dpdkinfra cli commands 25 | PktmbufCmd(parent) 26 | InterfaceCmd(parent) 27 | PipelineCmd(parent) 28 | 29 | return parent 30 | } 31 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package config 5 | 6 | import ( 7 | "github.com/stolsma/go-p4pack/pkg/config" 8 | "github.com/stolsma/go-p4pack/pkg/logging" 9 | ) 10 | 11 | var log logging.Logger 12 | 13 | func init() { 14 | // keep the logger up to date, also after new log config 15 | logging.Register("dpdkinfra/config", func(logger logging.Logger) { 16 | log = logger 17 | }) 18 | } 19 | 20 | type Config struct { 21 | *config.Base 22 | Pktmbufs PktmbufsConfig `json:"pktmbufs"` 23 | Devices DevicesConfig `json:"devices"` 24 | Interfaces InterfacesConfig `json:"interfaces"` 25 | Pipelines PipelinesConfig `json:"pipelines"` 26 | } 27 | 28 | // Process everything in this config structure 29 | func (c *Config) Apply() error { 30 | // Order of processing is important because Pipeline needs Interface and Interface needs Pktmbuf!! 31 | if err := c.Pktmbufs.Apply(); err != nil { 32 | return err 33 | } 34 | 35 | if err := c.Devices.Apply(); err != nil { 36 | return err 37 | } 38 | 39 | if err := c.Interfaces.Apply(); err != nil { 40 | return err 41 | } 42 | 43 | if err := c.Pipelines.Apply(c.GetBasePath()); err != nil { 44 | return err 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func Create() *Config { 51 | return &Config{Base: &config.Base{}} 52 | } 53 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/config/deviceconfig.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package config 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | 10 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra" 11 | ) 12 | 13 | type DevicesConfig []string 14 | 15 | // Create hotplug ethdev interfaces with given DPDK device argument string 16 | func (c DevicesConfig) Apply() error { 17 | dpdki := dpdkinfra.Get() 18 | if dpdki == nil { 19 | return errors.New("dpdkinfra module is not initialized") 20 | } 21 | 22 | // hotplug devices 23 | for _, devArgString := range c { 24 | devArgs, err := dpdki.AttachDevice(devArgString) 25 | if err != nil { 26 | log.Infof("Hotplug (devargs: %s) error: %v", devArgString, err) 27 | return fmt.Errorf("error creating hotplug device: %v", err) 28 | } 29 | log.Infof("Device (%s) created via hotplug on bus %s!", devArgs.Name(), devArgs.Bus()) 30 | } 31 | 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/config/pktmbufconfig.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package config 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | 10 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra" 11 | "github.com/stolsma/go-p4pack/pkg/dpdkswx/pktmbuf" 12 | ) 13 | 14 | type PktmbufsConfig []*PktmbufConfig 15 | 16 | type PktmbufConfig struct { 17 | Name string `json:"name"` 18 | BufferSize uint `json:"buffersize"` 19 | PoolSize uint32 `json:"poolsize"` 20 | CacheSize uint32 `json:"cachesize"` 21 | CPUID int `json:"cpuid"` 22 | } 23 | 24 | func (mpc *PktmbufConfig) GetName() string { 25 | return mpc.Name 26 | } 27 | 28 | func (mpc *PktmbufConfig) GetBufferSize() uint { 29 | if mpc.BufferSize == 0 { 30 | mpc.BufferSize = pktmbuf.RteMbufDefaultBufSize 31 | } 32 | return mpc.BufferSize 33 | } 34 | 35 | func (mpc *PktmbufConfig) GetPoolSize() uint32 { 36 | return mpc.PoolSize 37 | } 38 | 39 | func (mpc *PktmbufConfig) GetCacheSize() uint32 { 40 | return mpc.CacheSize 41 | } 42 | 43 | func (mpc *PktmbufConfig) GetCPUID() int { 44 | return mpc.CPUID 45 | } 46 | 47 | // Create pktmbufs through the DpdkInfra API 48 | func (c PktmbufsConfig) Apply() error { 49 | dpdki := dpdkinfra.Get() 50 | if dpdki == nil { 51 | return errors.New("dpdkinfra module is not initialized") 52 | } 53 | 54 | // Create PktMbuf memory pool 55 | for _, m := range c { 56 | name := m.GetName() 57 | _, err := dpdki.PktmbufCreate(name, m.GetBufferSize(), m.GetPoolSize(), m.GetCacheSize(), m.GetCPUID()) 58 | if err != nil { 59 | return fmt.Errorf("pktmbuf %s create err: %d", name, err) 60 | } 61 | log.Infof("Pktmbuf Mempool %s ready!", name) 62 | } 63 | 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/dpdkinfra.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package dpdkinfra 5 | 6 | import ( 7 | "errors" 8 | 9 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra/pipemngr" 10 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra/portmngr" 11 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra/store" 12 | "github.com/stolsma/go-p4pack/pkg/dpdkswx" 13 | "github.com/stolsma/go-p4pack/pkg/dpdkswx/pktmbuf" 14 | "github.com/stolsma/go-p4pack/pkg/logging" 15 | ) 16 | 17 | var dpdki *DpdkInfra 18 | var log logging.Logger 19 | 20 | func init() { 21 | // keep the logger up to date, also after new log config 22 | logging.Register("dpdkinfra", func(logger logging.Logger) { 23 | log = logger 24 | }) 25 | } 26 | 27 | type DpdkInfra struct { 28 | args []string 29 | numArgs int 30 | *portmngr.PortMngr 31 | *pipemngr.PipeMngr 32 | PktmbufStore *store.Store[*pktmbuf.Pktmbuf] 33 | } 34 | 35 | // return a pointer to the (initialized) dpdkinfra singleton 36 | func Get() *DpdkInfra { 37 | return dpdki 38 | } 39 | 40 | // create and initialize the dpdkinfra singleton, return the current dpdkinfra singleton with error if it already exists 41 | func CreateAndInit(dpdkArgs []string) (*DpdkInfra, error) { 42 | if dpdki != nil { 43 | return dpdki, errors.New("dpdki already initialized") 44 | } 45 | 46 | // create & initialize the dpdkinfra singleton 47 | dpdki = &DpdkInfra{} 48 | if err := dpdki.init(dpdkArgs); err != nil { 49 | return nil, err 50 | } 51 | 52 | return dpdki, nil 53 | } 54 | 55 | // Initialize the non system intrusive dpdkinfra singleton parts (i.e. excluding the dpdkswx runtime parts!) 56 | func (di *DpdkInfra) init(dpdkArgs []string) error { 57 | // initialize the dpdkswx runtime 58 | dpdki.args = dpdkArgs 59 | nArgs, err := dpdkswx.Runtime.Start(dpdkArgs) 60 | dpdki.numArgs = nArgs 61 | if err != nil { 62 | return err 63 | } 64 | 65 | // create store and initialize PortMngr and PipeMngr 66 | log.Info("Create Pktmbuf store...") 67 | di.PktmbufStore = store.NewStore[*pktmbuf.Pktmbuf]() 68 | 69 | log.Info("Initialize PortMngr...") 70 | di.PortMngr = &portmngr.PortMngr{} 71 | di.PortMngr.Init() 72 | 73 | log.Info("Initialize PipeMngr...") 74 | di.PipeMngr = &pipemngr.PipeMngr{} 75 | di.PipeMngr.Init() 76 | 77 | log.Info("Dpdkinfra initialization ready!") 78 | 79 | return nil 80 | } 81 | 82 | // empty & remove stores and cleanup initialized managers 83 | func (di *DpdkInfra) Cleanup() error { 84 | di.PipeMngr.Cleanup() 85 | di.PortMngr.Cleanup() 86 | di.PktmbufStore.Clear() 87 | return dpdkswx.Runtime.Stop() 88 | } 89 | 90 | // PktmbufCreate creates a pktmuf and stores it in the dpdkinfra pktmbuf store 91 | func (di *DpdkInfra) PktmbufCreate( 92 | name string, bufferSize uint, poolSize uint32, cacheSize uint32, cpuID int, 93 | ) (*pktmbuf.Pktmbuf, error) { 94 | var pm pktmbuf.Pktmbuf 95 | if di.PktmbufStore.Contains(name) { 96 | return nil, errors.New("pktmbuf mempool with this name exists") 97 | } 98 | 99 | // initialize 100 | if err := pm.Init(name, bufferSize, poolSize, cacheSize, cpuID, func() { 101 | di.PktmbufStore.Delete(name) 102 | }); err != nil { 103 | return nil, err 104 | } 105 | 106 | // add node to list 107 | di.PktmbufStore.Set(name, &pm) 108 | log.Infof("pktmbuf %s created", name) 109 | return &pm, nil 110 | } 111 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/portmngr/cgo-shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package portmngr 5 | 6 | /* 7 | #cgo pkg-config: libdpdk 8 | */ 9 | import "C" 10 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/store/kvstore/kvstore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package kvstore 5 | 6 | import ( 7 | "errors" 8 | "sort" 9 | "sync" 10 | 11 | "golang.org/x/exp/constraints" 12 | ) 13 | 14 | // ValueInterface is a common value interface. 15 | type ValueInterface interface { 16 | } 17 | 18 | // Interface is a common key-value store interface. 19 | type Interface[K constraints.Ordered, V ValueInterface] interface { 20 | // Get looks up a key's value from the key-value store. Ok is false if not found. 21 | Get(key K) (value *V, ok bool) 22 | // Set sets a value to the key-value store with key, replacing any existing value. 23 | Set(key K, val *V) 24 | // Keys returns the keys of the key-value store. The order is relied on algorithms. 25 | Keys() []K 26 | // Delete deletes the item with provided key from the key-value store. 27 | Delete(key K) 28 | // Contains reports whether the given key is stored within the key-value store. 29 | Contains(key K) bool 30 | // Iterate iterates over all the items in the key-value store and calls given function with key, value and options. 31 | Iterate(fn func(key K, value *V) error) error 32 | } 33 | 34 | // Item is an kvstore item 35 | type Item[K constraints.Ordered, V ValueInterface] struct { 36 | Key K 37 | Value V 38 | } 39 | 40 | // newItem creates a new item with specified options. 41 | func newItem[K constraints.Ordered, V ValueInterface](key K, val V) *Item[K, V] { 42 | return &Item[K, V]{ 43 | Key: key, 44 | Value: val, 45 | } 46 | } 47 | 48 | // KVStore is a thread safe key-value store. 49 | type KVStore[K constraints.Ordered, V ValueInterface] struct { 50 | s map[K]*Item[K, V] 51 | // mu is used to do lock in some method process. 52 | mu sync.Mutex 53 | } 54 | 55 | // New creates a new thread safe key-value store. 56 | func New[K constraints.Ordered, V ValueInterface]() *KVStore[K, V] { 57 | cache := &KVStore[K, V]{ 58 | s: make(map[K]*Item[K, V], 0), 59 | } 60 | return cache 61 | } 62 | 63 | // Get looks up a key's value from the key-value store. 64 | func (c *KVStore[K, V]) Get(key K) (value V, ok bool) { 65 | c.mu.Lock() 66 | defer c.mu.Unlock() 67 | item, ok := c.s[key] 68 | 69 | if !ok { 70 | return 71 | } 72 | 73 | return item.Value, true 74 | } 75 | 76 | // Set sets a value to the key-value store with key. Replacing any existing value. 77 | func (c *KVStore[K, V]) Set(key K, val V) { 78 | c.mu.Lock() 79 | defer c.mu.Unlock() 80 | 81 | item := newItem(key, val) 82 | c.s[key] = item 83 | } 84 | 85 | // Keys returns the keys of the key-value store. The order is sorted from low to high (i.e. a-Z). 86 | func (c *KVStore[K, V]) Keys() []K { 87 | c.mu.Lock() 88 | defer c.mu.Unlock() 89 | 90 | ret := make([]K, 0, len(c.s)) 91 | for key := range c.s { 92 | ret = append(ret, key) 93 | } 94 | sort.Slice(ret, func(i, j int) bool { 95 | return c.s[ret[i]].Key == c.s[ret[j]].Key 96 | }) 97 | 98 | return ret 99 | } 100 | 101 | // Delete deletes the item identified with the given key from the key-value store. 102 | func (c *KVStore[K, V]) Delete(key K) { 103 | c.mu.Lock() 104 | defer c.mu.Unlock() 105 | delete(c.s, key) 106 | } 107 | 108 | // Contains reports whether the given key is stored within the key-value store. 109 | func (c *KVStore[K, V]) Contains(key K) bool { 110 | c.mu.Lock() 111 | defer c.mu.Unlock() 112 | _, ok := c.s[key] 113 | return ok 114 | } 115 | 116 | // Iterate iterates over all the items in the key-value store and calls given function with key, value and options. 117 | func (c *KVStore[K, V]) Iterate(fn func(key K, value V) error) error { 118 | if fn != nil { 119 | for k, v := range c.s { 120 | if err := fn(k, v.Value); err != nil { 121 | return err 122 | } 123 | } 124 | } else { 125 | return errors.New("no function to call") 126 | } 127 | return nil 128 | } 129 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/store/kvstore/kvstore_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package kvstore 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | type TestStruct struct { 13 | clean func() 14 | name string 15 | } 16 | 17 | func (ts *TestStruct) Init(key string, clean func()) error { 18 | ts.clean = clean 19 | ts.name = key 20 | return nil 21 | } 22 | 23 | func (ts *TestStruct) Free() { 24 | 25 | } 26 | 27 | func TestKVStore(t *testing.T) { 28 | store := New[string, *TestStruct]() 29 | ts := &TestStruct{} 30 | store.Set("eerste", ts) 31 | tsGet, ok := store.Get("eerste") 32 | assert.Equal(t, ok, true) 33 | assert.Equal(t, ts, tsGet) 34 | } 35 | -------------------------------------------------------------------------------- /pkg/dpdkinfra/store/store.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package store 5 | 6 | import ( 7 | "github.com/stolsma/go-p4pack/pkg/dpdkinfra/store/kvstore" 8 | ) 9 | 10 | type ValueInterface interface { 11 | kvstore.ValueInterface 12 | Free() error 13 | } 14 | 15 | type Store[V ValueInterface] struct { 16 | *kvstore.KVStore[string, V] 17 | } 18 | 19 | // NewStore creates a new thread safe key-value store specifically tailored for the dpdkinfra/portmngr package 20 | func NewStore[V ValueInterface]() *Store[V] { 21 | s := &Store[V]{ 22 | KVStore: kvstore.New[string, V](), 23 | } 24 | return s 25 | } 26 | 27 | // Get looks up a key's value from the store and returns nil if it doesn't exist. 28 | func (s *Store[V]) Get(key string) (value V) { 29 | item, ok := s.KVStore.Get(key) 30 | 31 | if !ok { 32 | return // null value for type V (with pointer type it will be nil) 33 | } 34 | 35 | return item 36 | } 37 | 38 | // Clear deletes all key-value pairs from the key-value store and calls the Free method on all Values. 39 | func (s *Store[V]) Clear() { 40 | s.Iterate(func(key string, value V) error { 41 | err := value.Free() 42 | s.Delete(key) 43 | return err 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/dpdkswx/README.md: -------------------------------------------------------------------------------- 1 | # Go-P4Pack DPDK SWX Package 2 | 3 | This package supports the DPDK SWX P4 Spec programmable dataplane. 4 | 5 | ## Supported DPDK SWX C functions 6 | 7 | Crossed functions are supported by this package. All functions are tagged 'experimental' in DPDK at the moment. 8 | 9 | ### added in DPDK 20.11 10 | 11 | - [x] rte_swx_ctl_action_arg_info_get; 12 | - [x] rte_swx_ctl_action_info_get; 13 | - [x] rte_swx_ctl_pipeline_abort; 14 | - [x] rte_swx_ctl_pipeline_commit; 15 | - [x] rte_swx_ctl_pipeline_create; 16 | - [x] rte_swx_ctl_pipeline_free; 17 | - [x] rte_swx_ctl_pipeline_info_get; 18 | - [ ] rte_swx_ctl_pipeline_mirroring_session_set; 19 | - [x] rte_swx_ctl_pipeline_numa_node_get; 20 | - [x] rte_swx_ctl_pipeline_port_in_stats_read; 21 | - [x] rte_swx_ctl_pipeline_port_out_stats_read; 22 | - [x] rte_swx_ctl_pipeline_table_default_entry_add; 23 | - [x] rte_swx_ctl_pipeline_table_entry_add; 24 | - [x] rte_swx_ctl_pipeline_table_entry_delete; 25 | - [x] rte_swx_ctl_pipeline_table_entry_read; 26 | - [ ] rte_swx_ctl_pipeline_table_fprintf; 27 | - [x] rte_swx_ctl_table_action_info_get; 28 | - [x] rte_swx_ctl_table_info_get; 29 | - [x] rte_swx_ctl_table_match_field_info_get; 30 | - [ ] rte_swx_ctl_table_ops_get; 31 | - [ ] rte_swx_pipeline_action_config; 32 | - [ ] rte_swx_pipeline_build; 33 | - [x] rte_swx_pipeline_build_from_spec; 34 | - [x] rte_swx_pipeline_config; (implemented in pipeline.Init()) 35 | - [ ] rte_swx_pipeline_extern_func_register; 36 | - [ ] rte_swx_pipeline_extern_object_config; 37 | - [ ] rte_swx_pipeline_extern_type_member_func_register; 38 | - [ ] rte_swx_pipeline_extern_type_register; 39 | - [ ] rte_swx_pipeline_flush; 40 | - [x] rte_swx_pipeline_free; 41 | - [ ] rte_swx_pipeline_instructions_config; 42 | - [ ] rte_swx_pipeline_mirroring_config; 43 | - [ ] rte_swx_pipeline_packet_header_register; 44 | - [ ] rte_swx_pipeline_packet_metadata_register; 45 | - [x] rte_swx_pipeline_port_in_config; 46 | - [ ] rte_swx_pipeline_port_in_type_register; 47 | - [x] rte_swx_pipeline_port_out_config; 48 | - [ ] rte_swx_pipeline_port_out_type_register; 49 | - [x] rte_swx_pipeline_run; (implemented in Thread C code) 50 | - [ ] rte_swx_pipeline_struct_type_register; 51 | - [ ] rte_swx_pipeline_table_config; 52 | - [ ] rte_swx_pipeline_table_state_get; 53 | - [ ] rte_swx_pipeline_table_state_set; 54 | - [ ] rte_swx_pipeline_table_type_register; 55 | 56 | ### added in DPDK 21.05 57 | 58 | - [x] rte_swx_ctl_metarray_info_get; 59 | - [x] rte_swx_ctl_meter_profile_delete; 60 | - [x] rte_swx_ctl_meter_reset; 61 | - [x] rte_swx_ctl_meter_set; 62 | - [x] rte_swx_ctl_meter_stats_read; 63 | - [x] rte_swx_ctl_pipeline_regarray_read; 64 | - [x] rte_swx_ctl_pipeline_regarray_write; 65 | - [x] rte_swx_ctl_pipeline_table_stats_read; 66 | - [x] rte_swx_ctl_regarray_info_get; 67 | - [ ] rte_swx_pipeline_metarray_config; 68 | - [ ] rte_swx_pipeline_regarray_config; 69 | 70 | ### added in DPDK 21.08 71 | 72 | - [ ] rte_swx_pipeline_selector_config; 73 | - [ ] rte_swx_ctl_pipeline_selector_fprintf; 74 | - [x] rte_swx_ctl_pipeline_selector_group_add; 75 | - [x] rte_swx_ctl_pipeline_selector_group_delete; 76 | - [x] rte_swx_ctl_pipeline_selector_group_member_add; 77 | - [x] rte_swx_ctl_pipeline_selector_group_member_delete; 78 | - [ ] rte_swx_ctl_pipeline_selector_stats_read; 79 | - [ ] rte_swx_ctl_selector_info_get; 80 | - [ ] rte_swx_ctl_selector_field_info_get; 81 | - [ ] rte_swx_ctl_selector_group_id_field_info_get; 82 | - [ ] rte_swx_ctl_selector_member_id_field_info_get; 83 | 84 | ### added in DPDK 21.11 85 | 86 | - [x] rte_swx_ctl_pipeline_learner_default_entry_add; 87 | - [x] rte_swx_ctl_pipeline_learner_default_entry_read; 88 | - [x] rte_swx_ctl_pipeline_learner_stats_read; 89 | - [ ] rte_swx_ctl_learner_action_info_get; 90 | - [x] rte_swx_ctl_learner_info_get; 91 | - [ ] rte_swx_ctl_learner_match_field_info_get; 92 | - [ ] rte_swx_pipeline_learner_config; 93 | 94 | ### added in DPDK 22.07 95 | 96 | - [ ] rte_swx_ctl_pipeline_learner_timeout_get; 97 | - [ ] rte_swx_ctl_pipeline_learner_timeout_set; 98 | - [ ] rte_swx_pipeline_hash_func_register; 99 | -------------------------------------------------------------------------------- /pkg/dpdkswx/common/cgo-shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package common 5 | 6 | /* 7 | #cgo pkg-config: libdpdk 8 | */ 9 | import "C" 10 | -------------------------------------------------------------------------------- /pkg/dpdkswx/common/common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package common 5 | 6 | /* 7 | #include 8 | #include 9 | 10 | static int rteErrno() { 11 | return rte_errno; 12 | } 13 | 14 | */ 15 | import "C" 16 | 17 | import ( 18 | "errors" 19 | "reflect" 20 | "syscall" 21 | ) 22 | 23 | // Custom RTE induced errors. 24 | var ( 25 | ErrNoConfig = errors.New("missing rte_config") 26 | ErrSecondary = errors.New("operation not allowed in secondary processes") 27 | ) 28 | 29 | func errno(n int64) error { 30 | if n == 0 { 31 | return nil 32 | } else if n < 0 { 33 | n = -n 34 | } 35 | 36 | if n == int64(C.E_RTE_NO_CONFIG) { 37 | return ErrNoConfig 38 | } 39 | 40 | if n == int64(C.E_RTE_SECONDARY) { 41 | return ErrSecondary 42 | } 43 | 44 | return syscall.Errno(int(n)) 45 | } 46 | 47 | // RteErrno returns rte_errno variable. 48 | func rteErrno() error { 49 | return errno(int64(C.rteErrno())) 50 | } 51 | 52 | // IntToErr converts n into an 'errno' error. If n is not a signed 53 | // integer it will panic. 54 | func intToErr(n interface{}) error { 55 | x := reflect.ValueOf(n).Int() 56 | return errno(x) 57 | } 58 | 59 | func Err(n ...interface{}) error { 60 | if len(n) == 0 { 61 | return rteErrno() 62 | } 63 | return intToErr(n[0]) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/dpdkswx/dpdkswx.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package dpdkswx 5 | 6 | import ( 7 | "github.com/stolsma/go-p4pack/pkg/dpdkswx/swxruntime" 8 | ) 9 | 10 | // The standard DPDK SWX Pipeline runtime instance 11 | var Runtime = swxruntime.Create() 12 | -------------------------------------------------------------------------------- /pkg/dpdkswx/eal/cgo-shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package eal 5 | 6 | /* 7 | #cgo pkg-config: libdpdk 8 | */ 9 | import "C" 10 | -------------------------------------------------------------------------------- /pkg/dpdkswx/ethdev/cgo-shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package ethdev 5 | 6 | /* 7 | #cgo pkg-config: libdpdk 8 | */ 9 | import "C" 10 | -------------------------------------------------------------------------------- /pkg/dpdkswx/netlink/netlink.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package netlink 5 | 6 | import ( 7 | "github.com/stolsma/go-p4pack/pkg/logging" 8 | "github.com/vishvananda/netlink" 9 | ) 10 | 11 | var log logging.Logger 12 | 13 | func init() { 14 | // keep the logger up to date, also after new log config 15 | logging.Register("dpdkswx/netlink", func(logger logging.Logger) { 16 | log = logger 17 | }) 18 | } 19 | 20 | // set interface up if not already up 21 | func InterfaceUp(name string) error { 22 | localInterface, err := netlink.LinkByName(name) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | if err = netlink.LinkSetUp(localInterface); err != nil { 28 | return err 29 | } 30 | 31 | return nil 32 | } 33 | 34 | // Remove all addresses from interface 35 | func RemoveAllAddr(name string) error { 36 | localInterface, err := netlink.LinkByName(name) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | list, _ := netlink.AddrList(localInterface, netlink.FAMILY_ALL) 42 | for _, addr := range list { 43 | a := addr 44 | err := netlink.AddrDel(localInterface, &a) 45 | if err != nil { 46 | log.Infof("Couldnt remove address %s from interface %s", addr.String(), name) 47 | } 48 | } 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/dpdkswx/pipeline/actions.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package pipeline 5 | 6 | import "fmt" 7 | 8 | // represent an action argument description 9 | type ActionArg struct { 10 | index uint 11 | ActionArgInfo // TODO implement native Go struct instead of C struct 12 | } 13 | 14 | func (aa *ActionArg) GetIndex() uint { 15 | return aa.index 16 | } 17 | 18 | type ActionArgStore map[string]*ActionArg 19 | 20 | type Action struct { 21 | index uint // index 22 | name string // action name. 23 | actionArgs ActionArgStore // action args 24 | } 25 | 26 | // Initialize Action record after creation. Returns nil on success or the following error codes otherwise: 27 | // 28 | // -EINVAL = Invalid argument 29 | func (a *Action) Init(p *Pipeline, index uint) error { 30 | actionInfo, err := p.ActionInfoGet(index) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | // initalize generic table attibutes 36 | a.index = index 37 | a.name = actionInfo.GetName() 38 | a.actionArgs = make(ActionArgStore) 39 | 40 | // get action arg information 41 | for i := uint(0); i < actionInfo.GetNArgs(); i++ { 42 | actionArgInfo, err := p.ActionArgInfoGet(index, i) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | var actionArg = &ActionArg{i, *actionArgInfo} 48 | a.actionArgs[actionArg.GetName()] = actionArg 49 | } 50 | 51 | return nil 52 | } 53 | 54 | func (a *Action) GetIndex() uint { 55 | return a.index 56 | } 57 | 58 | func (a *Action) GetName() string { 59 | return a.name 60 | } 61 | 62 | // represents a store of action records 63 | type ActionStore map[string]*Action 64 | 65 | func CreateActionStore() ActionStore { 66 | return make(ActionStore) 67 | } 68 | 69 | func (as ActionStore) FindName(name string) *Action { 70 | if name == "" { 71 | return nil 72 | } 73 | 74 | return as[name] 75 | } 76 | 77 | func (as ActionStore) FindIndex(index uint) *Action { 78 | for _, action := range as { 79 | if action.GetIndex() == index { 80 | return action 81 | } 82 | } 83 | return nil 84 | } 85 | 86 | func (as ActionStore) CreateFromPipeline(p *Pipeline) error { 87 | pipelineInfo, err := p.PipelineInfoGet() 88 | if err != nil { 89 | return err 90 | } 91 | 92 | for i := uint(0); i < pipelineInfo.GetNActions(); i++ { 93 | var action Action 94 | 95 | err := action.Init(p, i) 96 | if err != nil { 97 | return fmt.Errorf("Actionstore.CreateFromPipeline error: %d", err) 98 | } 99 | as.Add(&action) 100 | } 101 | 102 | return nil 103 | } 104 | 105 | func (as ActionStore) Add(action *Action) { 106 | as[action.GetName()] = action 107 | } 108 | 109 | // Delete all Action records and free corresponding memory if required 110 | func (as ActionStore) Clear() { 111 | for _, action := range as { 112 | delete(as, action.GetName()) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /pkg/dpdkswx/pipeline/cgo-shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package pipeline 5 | 6 | /* 7 | #cgo pkg-config: libdpdk 8 | */ 9 | import "C" 10 | -------------------------------------------------------------------------------- /pkg/dpdkswx/pktmbuf/cgo-shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package pktmbuf 5 | 6 | /* 7 | #cgo pkg-config: libdpdk 8 | */ 9 | import "C" 10 | -------------------------------------------------------------------------------- /pkg/dpdkswx/pktmbuf/pktmbuf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package pktmbuf 5 | 6 | /* 7 | #include 8 | #include 9 | 10 | */ 11 | import "C" 12 | import ( 13 | "errors" 14 | 15 | "github.com/stolsma/go-p4pack/pkg/dpdkswx" 16 | "github.com/stolsma/go-p4pack/pkg/dpdkswx/swxruntime" 17 | "github.com/stolsma/go-p4pack/pkg/logging" 18 | "github.com/yerden/go-dpdk/mempool" 19 | ) 20 | 21 | var log logging.Logger 22 | 23 | func init() { 24 | // keep the logger up to date, also after new log config 25 | logging.Register("dpdkswx/pktmbuf", func(logger logging.Logger) { 26 | log = logger 27 | }) 28 | } 29 | 30 | const RteMbufDefaultBufSize uint = C.RTE_MBUF_DEFAULT_BUF_SIZE 31 | const RteMbufDefaultDataroom uint = C.RTE_MBUF_DEFAULT_DATAROOM 32 | const RtePktmbufHeadroom uint = C.RTE_PKTMBUF_HEADROOM 33 | const SizeofRteMbuf uint = C.sizeof_struct_rte_mbuf 34 | 35 | const BufferSizeMin uint = SizeofRteMbuf + RtePktmbufHeadroom 36 | 37 | // Pktmbuf represents a DPDK Packet memory buffer (pktmbuf) initialized memory pool (mempool) 38 | type Pktmbuf struct { 39 | name string 40 | m *mempool.Mempool 41 | bufferSize uint 42 | clean func() 43 | } 44 | 45 | // Create pktmbuf with corresponding pktmbuf mempool memory. Returns a pointer to a Pktmbuf 46 | // structure or nil with error. 47 | func (pm *Pktmbuf) Init( 48 | name string, 49 | bufferSize uint, 50 | poolSize uint32, 51 | cacheSize uint32, 52 | cpuSocketID int, 53 | clean func(), 54 | ) error { 55 | // Check input params 56 | if name == "" { 57 | return errors.New("no name given") 58 | } 59 | 60 | if bufferSize < BufferSizeMin { 61 | return errors.New("buffer size to small") 62 | } 63 | 64 | if poolSize == 0 { 65 | return errors.New("pool size is 0") 66 | } 67 | 68 | // create PktMbufpool on main DPDK Lcore to prevent problems 69 | var md *mempool.Mempool 70 | err := dpdkswx.Runtime.ExecOnMain(func(*swxruntime.MainCtx) (err error) { 71 | md, err = mempool.CreateMbufPool( 72 | name, 73 | poolSize, 74 | uint16(bufferSize), 75 | mempool.OptSocket(cpuSocketID), 76 | mempool.OptCacheSize(cacheSize), 77 | mempool.OptPrivateDataSize(0), // for each Mbuf 78 | ) 79 | return err 80 | }) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | // Node fill in 86 | pm.name = name 87 | pm.m = md 88 | pm.bufferSize = bufferSize 89 | pm.clean = clean 90 | 91 | return nil 92 | } 93 | 94 | func (pm *Pktmbuf) Name() string { 95 | return pm.name 96 | } 97 | 98 | func (pm *Pktmbuf) Mempool() *mempool.Mempool { 99 | return pm.m 100 | } 101 | 102 | func (pm *Pktmbuf) Free() error { 103 | if pm.m != nil { 104 | pm.m.Free() 105 | pm.m = nil 106 | } 107 | 108 | // call given clean callback function if given during init 109 | if pm.clean != nil { 110 | pm.clean() 111 | } 112 | 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /pkg/dpdkswx/ring/cgo-shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package ring 5 | 6 | /* 7 | #cgo pkg-config: libdpdk 8 | */ 9 | import "C" 10 | -------------------------------------------------------------------------------- /pkg/dpdkswx/sourcesink/cgo-shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package sourcesink 5 | 6 | /* 7 | #cgo pkg-config: libdpdk 8 | */ 9 | import "C" 10 | -------------------------------------------------------------------------------- /pkg/dpdkswx/sourcesink/sink.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package sourcesink 5 | 6 | /* 7 | #include 8 | 9 | #include 10 | 11 | */ 12 | import "C" 13 | import ( 14 | "errors" 15 | "unsafe" 16 | 17 | "github.com/stolsma/go-p4pack/pkg/dpdkswx/device" 18 | "github.com/stolsma/go-p4pack/pkg/dpdkswx/pipeline" 19 | ) 20 | 21 | type SinkParams struct { 22 | // Name of a valid PCAP file to write the output packets to. When NULL, all the output packets are dropped instead 23 | // of being saved to a PCAP file. 24 | FileName string 25 | } 26 | 27 | // Sink represents a Sink device 28 | type Sink struct { 29 | *device.Device 30 | fileName string 31 | } 32 | 33 | // Create and initialize Sink device 34 | func (s *Sink) Init(name string, params *SinkParams, clean func()) error { 35 | // Node fill in 36 | s.Device = &device.Device{} 37 | s.SetType("SINK") 38 | s.SetName(name) 39 | s.fileName = params.FileName 40 | s.InitializeQueues(0, 1) // initialize queue setup for pipeline bind use 41 | s.SetClean(clean) 42 | 43 | return nil 44 | } 45 | 46 | // Free deletes the current Sink record and calls the clean callback function given at init 47 | func (s *Sink) Free() error { 48 | // call given clean callback function if given during init 49 | if s.Clean() != nil { 50 | s.Clean()() 51 | } 52 | 53 | return nil 54 | } 55 | 56 | type SwxPortSinkParams struct { 57 | txParams *C.struct_rte_swx_port_sink_params 58 | paramsSet bool 59 | name string 60 | fileName string 61 | } 62 | 63 | func (e *SwxPortSinkParams) PortName() string { 64 | return e.name 65 | } 66 | 67 | func (e *SwxPortSinkParams) PortType() string { 68 | return "sink" 69 | } 70 | 71 | func (e *SwxPortSinkParams) GetReaderParams() unsafe.Pointer { 72 | return nil 73 | } 74 | 75 | func (e *SwxPortSinkParams) GetWriterParams() unsafe.Pointer { 76 | e.createCParams() 77 | return unsafe.Pointer(e.txParams) 78 | } 79 | 80 | func (e *SwxPortSinkParams) FreeParams() { 81 | e.freeCParams() 82 | } 83 | 84 | func (e *SwxPortSinkParams) createCParams() { 85 | if e.paramsSet { 86 | return 87 | } 88 | 89 | e.paramsSet = true 90 | e.txParams = &C.struct_rte_swx_port_sink_params{ 91 | file_name: C.CString(e.fileName), 92 | } 93 | } 94 | 95 | func (e *SwxPortSinkParams) freeCParams() { 96 | if !e.paramsSet { 97 | return 98 | } 99 | 100 | e.paramsSet = false 101 | C.free(unsafe.Pointer(e.txParams.file_name)) 102 | e.txParams = nil 103 | } 104 | 105 | // bind to given pipeline input port 106 | func (s *Sink) BindToPipelineInputPort(pl *pipeline.Pipeline, portID int, rxq uint16, bsz uint) error { 107 | return errors.New("can't bind sink device to pipeline input port") 108 | } 109 | 110 | // bind to given pipeline output port 111 | func (s *Sink) BindToPipelineOutputPort(pl *pipeline.Pipeline, portID int, txq uint16, bsz uint) error { 112 | if _, plp, err := s.GetTxQueue(txq); err != nil { 113 | return err 114 | } else if plp != device.NotBound { 115 | return errors.New("port already bound") 116 | } 117 | 118 | params := &SwxPortSinkParams{ 119 | name: s.Name(), 120 | fileName: s.fileName, 121 | } 122 | if err := pl.PortOutConfig(portID, params); err != nil { 123 | return err 124 | } 125 | 126 | return s.SetTxQueue(txq, pl.GetName(), portID) 127 | } 128 | -------------------------------------------------------------------------------- /pkg/dpdkswx/swxruntime/cgo-shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package swxruntime 5 | 6 | /* 7 | #cgo pkg-config: libdpdk 8 | */ 9 | import "C" 10 | -------------------------------------------------------------------------------- /pkg/dpdkswx/swxruntime/thread.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package swxruntime 5 | 6 | /* 7 | #include 8 | #include 9 | 10 | #include "thread.h" 11 | 12 | */ 13 | import "C" 14 | import ( 15 | "unsafe" 16 | 17 | "github.com/stolsma/go-p4pack/pkg/dpdkswx/common" 18 | ) 19 | 20 | // 21 | // Pipeline functions 22 | // 23 | 24 | func EnablePipeline(pl unsafe.Pointer, threadID uint) error { 25 | res := C.pipeline_enable((*C.struct_rte_swx_pipeline)(pl), C.uint(threadID)) 26 | return common.Err(res) 27 | } 28 | 29 | func DisablePipeline(pl unsafe.Pointer) { 30 | C.pipeline_disable((*C.struct_rte_swx_pipeline)(pl)) 31 | } 32 | 33 | // 34 | // Block functions 35 | // 36 | 37 | func EnableBlock(fn unsafe.Pointer, block unsafe.Pointer, threadID uint) error { 38 | res := C.block_enable((C.block_run_f)(fn), block, C.uint(threadID)) 39 | return common.Err(res) 40 | } 41 | 42 | func DisableBlock(block unsafe.Pointer) { 43 | C.block_disable(block) 44 | } 45 | 46 | // 47 | // Thread functions 48 | // 49 | 50 | func ThreadsInit() error { 51 | res := C.thread_init() 52 | return common.Err(res) 53 | } 54 | 55 | func ThreadsStart() error { 56 | res := C.thread_start() 57 | return common.Err(res) 58 | } 59 | 60 | func ThreadsStop() error { 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /pkg/dpdkswx/swxruntime/thread.h: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2020 Intel Corporation 2 | // Copyright 2022 - Sander Tolsma. All rights reserved 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | #ifndef _INCLUDE_THREAD_H_ 6 | #define _INCLUDE_THREAD_H_ 7 | 8 | #include 9 | #include 10 | 11 | /** 12 | * Control plane (CP) thread. 13 | */ 14 | 15 | int thread_init(void); 16 | 17 | // pipeline 18 | 19 | int pipeline_enable(struct rte_swx_pipeline *p, uint32_t thread_id); 20 | void pipeline_disable(struct rte_swx_pipeline *p); 21 | 22 | // block 23 | 24 | typedef void (*block_run_f)(void *block); 25 | int block_enable(block_run_f block_func, void *block, uint32_t thread_id); 26 | void block_disable(void *block); 27 | 28 | /** 29 | * Data plane (DP) threads. 30 | */ 31 | 32 | int thread_main(void *arg); 33 | 34 | /* 35 | * Check that all configured WORKER lcores are in WAIT state, then run the thread_main function on all of them. 36 | * 37 | * Returns: 38 | * - 0: Success. Execution of thread_main function is started on all WORKER lcores. 39 | * - (-EBUSY): At least one WORKER lcore is not in a WAIT state. In this case, thread_main is not started on any of 40 | * the WORKER lcores. 41 | */ 42 | int thread_start(void); 43 | 44 | #endif /* _INCLUDE_THREAD_H_ */ 45 | -------------------------------------------------------------------------------- /pkg/dpdkswx/tap/cgo-shared.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package tap 5 | 6 | /* 7 | #cgo pkg-config: libdpdk 8 | */ 9 | import "C" 10 | -------------------------------------------------------------------------------- /pkg/flowtest/README.md: -------------------------------------------------------------------------------- 1 | # This package is under construction -------------------------------------------------------------------------------- /pkg/flowtest/cli/root.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | "github.com/stolsma/go-p4pack/pkg/cli" 9 | ) 10 | 11 | // GetCommand returns the root command after adding the flowtest service commands 12 | func GetCommand(parents ...*cobra.Command) *cobra.Command { 13 | flowtestCmd := &cobra.Command{ 14 | Use: "flowtest {start/stop} test [args]", 15 | Short: "flowtest commands", 16 | } 17 | 18 | FlowtestStartCmd(flowtestCmd) 19 | FlowtestStopCmd(flowtestCmd) 20 | 21 | return cli.AddCommand(parents, flowtestCmd) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/flowtest/cli/start.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | "github.com/stolsma/go-p4pack/pkg/cli" 9 | "github.com/stolsma/go-p4pack/pkg/flowtest" 10 | ) 11 | 12 | func FlowtestStartCmd(parents ...*cobra.Command) *cobra.Command { 13 | startCmd := &cobra.Command{ 14 | Use: "start [flowtest name]", 15 | Short: "Starts a defined flowtest or all if name omitted", 16 | Args: cobra.MaximumNArgs(1), 17 | Run: runStartFTCommand, 18 | } 19 | 20 | return cli.AddCommand(parents, startCmd) 21 | } 22 | 23 | func runStartFTCommand(cmd *cobra.Command, args []string) { 24 | var name string 25 | 26 | // get flowtest singleton 27 | ft := flowtest.Get() 28 | if ft == nil { 29 | cmd.PrintErrf("The flowtest module is not initialized yet!\n") 30 | return 31 | } 32 | 33 | // get name argument 34 | if len(args) == 1 { 35 | name = args[0] 36 | } 37 | 38 | // execute 39 | if name == "" { 40 | err := ft.StartAll() 41 | if err != nil { 42 | cmd.PrintErrf("Something went wrong starting all the flowtests: %d \n", err) 43 | return 44 | } 45 | cmd.Println("All defined flowtests are started!") 46 | } else { 47 | cmd.PrintErrf("The start [flowtest name] command is not implemented yet!\n") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/flowtest/cli/stop.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | "github.com/stolsma/go-p4pack/pkg/cli" 9 | "github.com/stolsma/go-p4pack/pkg/flowtest" 10 | ) 11 | 12 | func FlowtestStopCmd(parents ...*cobra.Command) *cobra.Command { 13 | stopCmd := &cobra.Command{ 14 | Use: "stop [flowtest name]", 15 | Short: "Stops a running flowtest or all if name omitted", 16 | Args: cobra.MaximumNArgs(1), 17 | Run: runStopFTCommand, 18 | } 19 | 20 | return cli.AddCommand(parents, stopCmd) 21 | } 22 | 23 | func runStopFTCommand(cmd *cobra.Command, args []string) { 24 | var name string 25 | 26 | // get flowtest singleton 27 | ft := flowtest.Get() 28 | if ft == nil { 29 | cmd.PrintErrf("The flowtest module is not initialized yet!\n") 30 | return 31 | } 32 | 33 | // get name argument 34 | if len(args) == 1 { 35 | name = args[0] 36 | } 37 | 38 | // execute 39 | if name == "" { 40 | err := ft.StopAll() 41 | if err != nil { 42 | cmd.PrintErrf("Something went wrong stopping all the flowtests: %d \n", err) 43 | return 44 | } 45 | cmd.Println("All defined flowtests are stopped!") 46 | } else { 47 | cmd.PrintErrf("The stop [flowtest name] command is not implemented yet!\n") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/flowtest/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package flowtest 5 | 6 | import ( 7 | "bytes" 8 | "testing" 9 | ) 10 | 11 | func TestDecode(t *testing.T) { 12 | // check odd number of hex characters 13 | value, err := decode("0xA00") 14 | if err != nil { 15 | t.Fatalf("Decode failed: %v", err) 16 | } 17 | if !bytes.Equal(value, []byte{0xA, 0x0}) { 18 | t.Fatalf("Decoded to wrong result (0xA00 <> [10,0]): %v", value) 19 | } 20 | 21 | // check correct hex number 22 | value, err = decode("0x0800") 23 | if err != nil { 24 | t.Fatalf("Decode failed: %v", err) 25 | } 26 | if !bytes.Equal(value, []byte{0x8, 0x0}) { 27 | t.Fatalf("Decoded to wrong result (0x0800 <> [8,0]): %v", value) 28 | } 29 | 30 | // check odd number of hex characters 31 | _, err = decode("0xJ00") 32 | if err == nil { 33 | t.Fatalf("Decoded with wrong hex number (0xJ00)") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/flowtest/flowtest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package flowtest 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "fmt" 10 | 11 | "github.com/stolsma/go-p4pack/pkg/logging" 12 | ) 13 | 14 | var flowTest *FlowTest 15 | var log logging.Logger 16 | 17 | func init() { 18 | // keep the logger up to date, also after new log config 19 | logging.Register("flowtest", func(logger logging.Logger) { 20 | log = logger 21 | }) 22 | } 23 | 24 | type Iface struct { 25 | Name string 26 | MAC HexArray 27 | IP HexArray 28 | } 29 | 30 | func (i *Iface) GetName() string { 31 | return i.Name 32 | } 33 | 34 | func (i *Iface) GetMAC() HexArray { 35 | return i.MAC 36 | } 37 | 38 | func (i *Iface) GetIP() HexArray { 39 | return i.IP 40 | } 41 | 42 | type IfaceMap map[string]*Iface 43 | 44 | type FlowTest struct { 45 | ctx context.Context 46 | cancelFn context.CancelFunc 47 | ifaces map[string]*Iface 48 | flowSets []*FlowSet 49 | } 50 | 51 | // return a pointer to the (initialized) flowtest singleton 52 | func Get() *FlowTest { 53 | return flowTest 54 | } 55 | 56 | // create and initialize the flowtest singleton, return the current flowtest singleton with error if it already exists 57 | func CreateAndInit(ctx context.Context) (*FlowTest, error) { 58 | if flowTest != nil { 59 | return flowTest, errors.New("flowtest already initialized") 60 | } 61 | 62 | flowTest = &FlowTest{ 63 | ifaces: make(map[string]*Iface), 64 | } 65 | 66 | flowTest.Init(ctx) 67 | 68 | return flowTest, nil 69 | } 70 | 71 | func (t *FlowTest) Init(ctx context.Context) { 72 | ctx, cancelFn := context.WithCancel(ctx) 73 | t.ctx = ctx 74 | t.cancelFn = cancelFn 75 | } 76 | 77 | func (t *FlowTest) AddInterface(ifName string, mac HexArray, ip HexArray) error { 78 | if t.ifaces[ifName] != nil { 79 | return fmt.Errorf("interface %s already exists", ifName) 80 | } 81 | 82 | t.ifaces[ifName] = &Iface{ // interface configuration 83 | Name: ifName, 84 | MAC: mac, 85 | IP: ip, 86 | } 87 | 88 | return nil 89 | } 90 | 91 | func (t *FlowTest) AddFlowSet(config FlowSetConfig) error { 92 | fs := FlowSetCreate(config.GetName()) 93 | 94 | err := fs.Init(config.Flows, t.ifaces) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | t.flowSets = append(t.flowSets, fs) 100 | 101 | return nil 102 | } 103 | 104 | func (t *FlowTest) StartAll() error { 105 | for _, fs := range t.flowSets { 106 | err := fs.Start(t.ctx) 107 | if err != nil { 108 | return err 109 | } 110 | } 111 | return nil 112 | } 113 | 114 | func (t *FlowTest) StopAll() error { 115 | for _, fs := range t.flowSets { 116 | err := fs.Stop() 117 | if err != nil { 118 | return err 119 | } 120 | } 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /pkg/flowtest/list.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package flowtest 5 | 6 | type TestPacket struct { 7 | prev *TestPacket 8 | next *TestPacket 9 | key string 10 | // timeSend time.Time 11 | // timeExpected time.Time 12 | } 13 | 14 | type ExpectedList struct { 15 | head *TestPacket 16 | tail *TestPacket 17 | } 18 | 19 | func (l *ExpectedList) Insert(record *TestPacket) { 20 | record.prev = nil 21 | record.next = nil 22 | 23 | // nothing in the list ? 24 | if l.head == nil { 25 | l.head = record 26 | l.tail = record 27 | return 28 | } 29 | 30 | // add to the back of the list 31 | tail := l.tail 32 | record.prev = tail 33 | tail.next = record 34 | l.tail = record 35 | } 36 | 37 | func (l *ExpectedList) Find(id string) *TestPacket { 38 | current := l.head 39 | for current != nil { 40 | // is this is the requested packet 41 | if current.key == id { 42 | return current 43 | } 44 | 45 | // look at next packet 46 | current = current.next 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func (l *ExpectedList) Remove(record *TestPacket) { 53 | prev := record.prev 54 | next := record.next 55 | 56 | // first in list so make next the head 57 | if l.head == record { 58 | l.head = next 59 | } 60 | 61 | // last in list so make prev the tail 62 | if l.tail == record { 63 | l.tail = prev 64 | } 65 | 66 | // set previous record next to next if exist 67 | if prev != nil { 68 | prev.next = next 69 | } 70 | 71 | // set next record prev to prev if exist 72 | if next != nil { 73 | next.prev = prev 74 | } 75 | 76 | // clear pointers! 77 | record.prev = nil 78 | record.next = nil 79 | } 80 | -------------------------------------------------------------------------------- /pkg/flowtest/list_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package flowtest 5 | 6 | import "testing" 7 | 8 | func TestList(t *testing.T) { 9 | var list = &ExpectedList{} 10 | var result *TestPacket 11 | 12 | var packet1 = &TestPacket{key: "packet1"} 13 | var packet2 = &TestPacket{key: "packet2"} 14 | var packet3 = &TestPacket{key: "packet3"} 15 | var packet4 = &TestPacket{key: "packet4"} 16 | 17 | list.Insert(packet1) 18 | 19 | result = list.Find(packet1.key) 20 | if result == nil { 21 | t.Fatalf("Did not find %s in list", packet1.key) 22 | } 23 | 24 | idwrong := "wrong1" 25 | result = list.Find(idwrong) 26 | if result != nil { 27 | t.Fatalf("Did find '%s' in list when nothing should be found", result.key) 28 | } 29 | 30 | list.Insert(packet2) 31 | list.Insert(packet3) 32 | list.Insert(packet4) 33 | 34 | result = list.Find(packet1.key) 35 | if result == nil { 36 | t.Fatalf("Did not find %s in list", packet1.key) 37 | } 38 | 39 | result = list.Find(idwrong) 40 | if result != nil { 41 | t.Fatalf("Did find '%s' in list when nothing should be found", result.key) 42 | } 43 | 44 | list.Remove(packet1) 45 | result = list.Find(packet1.key) 46 | if result != nil { 47 | t.Fatalf("Did find '%s' in list when nothing should be found", result.key) 48 | } 49 | 50 | result = list.Find(packet2.key) 51 | if result == nil { 52 | t.Fatalf("Did not find %s in list", packet2.key) 53 | } 54 | 55 | list.Remove(packet4) 56 | result = list.Find(packet4.key) 57 | if result != nil { 58 | t.Fatalf("Did find '%s' in list when nothing should be found", result.key) 59 | } 60 | 61 | result = list.Find(packet3.key) 62 | if result == nil { 63 | t.Fatalf("Did not find %s in list", packet3.key) 64 | } 65 | 66 | list.Remove(packet2) 67 | list.Remove(packet3) 68 | if list.head != nil && list.tail != nil { 69 | t.Fatalf("List wasn't empty!") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pkg/gnmi/README.md: -------------------------------------------------------------------------------- 1 | # This package is under construction -------------------------------------------------------------------------------- /pkg/gnmi/generate.go: -------------------------------------------------------------------------------- 1 | package gnmi 2 | 3 | import ( 4 | // Ensure ygnmi generator is included in this go.mod. 5 | _ "github.com/openconfig/ygnmi/app/ygnmi/cmd" 6 | ) 7 | 8 | //go:generate ./generate.sh 9 | 10 | // @generated 11 | -------------------------------------------------------------------------------- /pkg/gnmi/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2021 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script is used to generate the Ondatra Telemetry and Config Go APIs. 18 | 19 | set -e 20 | 21 | rm -rf public 22 | 23 | git clone https://github.com/openconfig/public.git 24 | 25 | EXCLUDE_MODULES=ietf-interfaces,openconfig-bfd,openconfig-messages 26 | 27 | YANG_FILES=( 28 | public/release/models/acl/openconfig-acl.yang 29 | public/release/models/acl/openconfig-packet-match.yang 30 | public/release/models/aft/openconfig-aft.yang 31 | public/release/models/bfd/openconfig-bfd.yang 32 | public/release/models/bgp/openconfig-bgp-policy.yang 33 | public/release/models/bgp/openconfig-bgp-types.yang 34 | public/release/models/interfaces/openconfig-if-aggregate.yang 35 | public/release/models/interfaces/openconfig-if-ethernet.yang 36 | public/release/models/interfaces/openconfig-if-ip-ext.yang 37 | public/release/models/interfaces/openconfig-if-ip.yang 38 | public/release/models/interfaces/openconfig-interfaces.yang 39 | public/release/models/isis/openconfig-isis.yang 40 | public/release/models/lacp/openconfig-lacp.yang 41 | public/release/models/lldp/openconfig-lldp-types.yang 42 | public/release/models/lldp/openconfig-lldp.yang 43 | public/release/models/local-routing/openconfig-local-routing.yang 44 | public/release/models/mpls/openconfig-mpls-types.yang 45 | public/release/models/multicast/openconfig-pim.yang 46 | public/release/models/network-instance/openconfig-network-instance.yang 47 | public/release/models/openconfig-extensions.yang 48 | public/release/models/optical-transport/openconfig-transport-types.yang 49 | public/release/models/ospf/openconfig-ospfv2.yang 50 | public/release/models/platform/openconfig-platform-cpu.yang 51 | public/release/models/platform/openconfig-platform-integrated-circuit.yang 52 | public/release/models/platform/openconfig-platform-software.yang 53 | public/release/models/platform/openconfig-platform-transceiver.yang 54 | public/release/models/platform/openconfig-platform.yang 55 | public/release/models/policy-forwarding/openconfig-policy-forwarding.yang 56 | public/release/models/policy/openconfig-policy-types.yang 57 | public/release/models/qos/openconfig-qos-elements.yang 58 | public/release/models/qos/openconfig-qos-interfaces.yang 59 | public/release/models/qos/openconfig-qos-types.yang 60 | public/release/models/qos/openconfig-qos.yang 61 | public/release/models/rib/openconfig-rib-bgp.yang 62 | public/release/models/segment-routing/openconfig-segment-routing-types.yang 63 | public/release/models/system/openconfig-system.yang 64 | public/release/models/types/openconfig-inet-types.yang 65 | public/release/models/types/openconfig-types.yang 66 | public/release/models/types/openconfig-yang-types.yang 67 | public/release/models/vlan/openconfig-vlan.yang 68 | public/third_party/ietf/iana-if-type.yang 69 | public/third_party/ietf/ietf-inet-types.yang 70 | public/third_party/ietf/ietf-interfaces.yang 71 | public/third_party/ietf/ietf-yang-types.yang 72 | ) 73 | 74 | rm -r oc || true 75 | mkdir oc 76 | 77 | go run github.com/openconfig/ygnmi/app/ygnmi generator \ 78 | --trim_module_prefix=openconfig \ 79 | --exclude_modules="${EXCLUDE_MODULES}" \ 80 | --base_package_path=github.com/stolsma/go-p4pack/pkg/gnmi/oc \ 81 | --output_dir=oc \ 82 | --paths=public/release/models/...,public/third_party/ietf/... \ 83 | "${YANG_FILES[@]}" 84 | 85 | find oc -name "*.go" -exec goimports -w {} + 86 | find oc -name "*.go" -exec gofmt -w -s {} + 87 | rm -rf public 88 | -------------------------------------------------------------------------------- /pkg/gnmi/gnmi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package gnmi 5 | 6 | import ( 7 | "context" 8 | "time" 9 | 10 | gnmipb "github.com/openconfig/gnmi/proto/gnmi" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | ) 15 | 16 | // Server is the gNMI server implementation. 17 | 18 | type Server struct { 19 | name string 20 | grpcServer *grpc.Server 21 | subscription int 22 | 23 | Responses [][]*gnmipb.SubscribeResponse 24 | GetResponses []interface{} 25 | Errs []error 26 | } 27 | 28 | // New returns a new gNMI server instance. 29 | func New(s *grpc.Server, name string) (*Server, error) { 30 | srv := &Server{ 31 | name: name, 32 | grpcServer: s, 33 | } 34 | 35 | gnmipb.RegisterGNMIServer(s, srv) 36 | return srv, nil 37 | } 38 | 39 | func (s *Server) Capabilities(ctx context.Context, req *gnmipb.CapabilityRequest) (*gnmipb.CapabilityResponse, error) { 40 | return nil, status.Errorf(codes.Unimplemented, "Unimplemented") 41 | } 42 | 43 | func (s *Server) Get(ctx context.Context, req *gnmipb.GetRequest) (*gnmipb.GetResponse, error) { 44 | if len(s.GetResponses) == 0 { 45 | return nil, status.Errorf(codes.Unimplemented, "Unimplemented") 46 | } 47 | 48 | resp := s.GetResponses[0] 49 | s.GetResponses = s.GetResponses[1:] 50 | 51 | switch v := resp.(type) { 52 | case error: 53 | return nil, v 54 | case *gnmipb.GetResponse: 55 | return v, nil 56 | default: 57 | return nil, status.Errorf(codes.DataLoss, "Unknown message type: %T", resp) 58 | } 59 | } 60 | 61 | func (s *Server) Set(ctx context.Context, req *gnmipb.SetRequest) (*gnmipb.SetResponse, error) { 62 | return nil, status.Errorf(codes.Unimplemented, "Unimplemented") 63 | } 64 | 65 | func (s *Server) Subscribe(stream gnmipb.GNMI_SubscribeServer) error { 66 | _, err := stream.Recv() 67 | if err != nil { 68 | return err 69 | } 70 | 71 | srs := s.Responses[s.subscription] 72 | if len(s.Errs) != s.subscription+1 { 73 | s.Errs = append(s.Errs, nil) 74 | } 75 | 76 | srErr := s.Errs[s.subscription] 77 | s.subscription++ 78 | 79 | for _, sr := range srs { 80 | stream.Send(sr) 81 | } 82 | 83 | time.Sleep(5 * time.Second) 84 | return srErr 85 | } 86 | -------------------------------------------------------------------------------- /pkg/kernel/kernel.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // Copyright 2022 - Sander Tolsma. All rights reserved 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package kernel 6 | 7 | import ( 8 | "fmt" 9 | "net" 10 | 11 | "github.com/vishvananda/netlink" 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | // SetInterfaceHWAddr sets the MAC address of a network interface. 16 | func SetInterfaceHWAddr(name string, addr string) error { 17 | link, err := netlink.LinkByName(name) 18 | if err != nil { 19 | return fmt.Errorf("failed to get interface: %w", err) 20 | } 21 | addrBytes, err := net.ParseMAC(addr) 22 | if err != nil { 23 | return fmt.Errorf("failed to set parse addres: %v", err) 24 | } 25 | if err := netlink.LinkSetHardwareAddr(link, addrBytes); err != nil { 26 | return fmt.Errorf("failed to get hwaddr of link: %w", err) 27 | } 28 | return nil 29 | } 30 | 31 | // SetInterfaceIP sets the IP addresses of a network interface. 32 | func SetInterfaceIP(name string, ip string, prefixLen int) error { 33 | link, err := netlink.LinkByName(name) 34 | if err != nil { 35 | return fmt.Errorf("failed to get interface: %w", err) 36 | } 37 | ipNet := &net.IPNet{} 38 | ipNet.IP = net.ParseIP(ip) 39 | if ipNet.IP == nil { 40 | return fmt.Errorf("failed to parse ip") 41 | } 42 | ipNet.Mask = net.CIDRMask(prefixLen, 128) 43 | if ipNet.IP.To4() != nil { // If ip is IPv4. 44 | ipNet.Mask = net.CIDRMask(prefixLen, 32) 45 | } 46 | if err := netlink.AddrReplace(link, &netlink.Addr{IPNet: ipNet}); err != nil { 47 | return fmt.Errorf("failed to add ip to link: %w", err) 48 | } 49 | return nil 50 | } 51 | 52 | // DeleteInterfaceIP delete an IP addresses from a network interface. 53 | func DeleteInterfaceIP(name string, ip *net.IPNet) error { 54 | link, err := netlink.LinkByName(name) 55 | if err != nil { 56 | return fmt.Errorf("failed to get interface: %w", err) 57 | } 58 | if err := netlink.AddrDel(link, &netlink.Addr{IPNet: ip}); err != nil { 59 | return fmt.Errorf("failed to add ip to link: %w", err) 60 | } 61 | return nil 62 | } 63 | 64 | // SetInterfaceState sets a links up or down. 65 | func SetInterfaceState(name string, up bool) error { 66 | link, err := netlink.LinkByName(name) 67 | if err != nil { 68 | return fmt.Errorf("failed to get interface: %w", err) 69 | } 70 | if up { 71 | return netlink.LinkSetUp(link) 72 | } 73 | return netlink.LinkSetDown(link) 74 | } 75 | 76 | // CreateTAP creates kernel TAP interface. 77 | func CreateTAP(name string) (int, error) { 78 | fd, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0) 79 | if err != nil { 80 | return -1, fmt.Errorf("failed to open tun file: %w", err) 81 | } 82 | req, err := unix.NewIfreq(name) 83 | if err != nil { 84 | // TODO Close fd 85 | return -1, fmt.Errorf("failed to create interface req: %w", err) 86 | } 87 | req.SetUint16(unix.IFF_TAP | unix.IFF_NO_PI) 88 | if err := unix.IoctlIfreq(fd, unix.TUNSETIFF, req); err != nil { 89 | // TODO Close fd 90 | return -1, fmt.Errorf("failed to do ioctl: %v", err) 91 | } 92 | return fd, nil 93 | } 94 | -------------------------------------------------------------------------------- /pkg/logging/cli/get.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package cli 6 | 7 | import ( 8 | "github.com/spf13/cobra" 9 | "github.com/stolsma/go-p4pack/pkg/cli" 10 | "github.com/stolsma/go-p4pack/pkg/logging" 11 | ) 12 | 13 | func LogGetCommand(parents ...*cobra.Command) *cobra.Command { 14 | getCmd := &cobra.Command{ 15 | Use: "get", 16 | Short: "Gets a logger attribute (e.g. level)", 17 | } 18 | 19 | LogGetLevelCommand(getCmd) 20 | return cli.AddCommand(parents, getCmd) 21 | } 22 | 23 | func LogGetLevelCommand(parents ...*cobra.Command) *cobra.Command { 24 | levelCmd := &cobra.Command{ 25 | Use: "level logger_name", 26 | Short: "Gets a logger level", 27 | Args: cobra.ExactArgs(1), 28 | Run: runGetLevelCommand, 29 | } 30 | 31 | return cli.AddCommand(parents, levelCmd) 32 | } 33 | 34 | func runGetLevelCommand(cmd *cobra.Command, args []string) { 35 | name := args[0] 36 | if name == "" { 37 | cmd.PrintErrf("The logger name should be provided\n") 38 | return 39 | } 40 | 41 | // get the loggers operational configuration 42 | list := logging.GetLoggerDataList() 43 | if _, ok := list[name]; !ok { 44 | cmd.PrintErrf("The logger name does not exist!\n") 45 | return 46 | } 47 | 48 | cmd.Printf("%s logger level is %s\n", name, list[name].Level) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/logging/cli/list.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/stolsma/go-p4pack/pkg/cli" 8 | "github.com/stolsma/go-p4pack/pkg/logging" 9 | ) 10 | 11 | func LogListCommand(parents ...*cobra.Command) *cobra.Command { 12 | listCmd := &cobra.Command{ 13 | Use: "list", 14 | Short: "Shows all logger domains", 15 | Run: runListCommand, 16 | } 17 | 18 | return cli.AddCommand(parents, listCmd) 19 | } 20 | 21 | func runListCommand(cmd *cobra.Command, args []string) { 22 | var rootString = "root (default, not changeable)" 23 | 24 | // get the loggers operational configuration 25 | list := logging.GetLoggerDataList() 26 | 27 | // sort the returned domains 28 | keys := make([]string, 0, len(list)) 29 | var maxLen = len(rootString) 30 | for k := range list { 31 | keys = append(keys, k) 32 | if l := len(k); l > maxLen { 33 | maxLen = l 34 | } 35 | } 36 | sort.Strings(keys) 37 | 38 | // print the info on domain sorted order 39 | cmd.Printf("Current defined loging domains and loglevel:\n") 40 | cmd.Printf(" %-*s LOG LEVEL\n", maxLen, "DOMAIN") 41 | for _, domain := range keys { 42 | if domain == "root" { 43 | cmd.Printf(" %-*s %s\n", maxLen, rootString, list[domain].Level) 44 | } else { 45 | cmd.Printf(" %-*s %s\n", maxLen, domain, list[domain].Level) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pkg/logging/cli/root.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package cli 6 | 7 | import ( 8 | "github.com/spf13/cobra" 9 | "github.com/stolsma/go-p4pack/pkg/cli" 10 | ) 11 | 12 | // GetCommand returns the root command after adding the logging service commands 13 | func GetCommand(parents ...*cobra.Command) *cobra.Command { 14 | logCmd := &cobra.Command{ 15 | Use: "log {list}/{set/get} level [args]", 16 | Short: "logging api commands", 17 | } 18 | 19 | LogListCommand(logCmd) 20 | LogGetCommand(logCmd) 21 | LogSetCommand(logCmd) 22 | return cli.AddCommand(parents, logCmd) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/logging/cli/set.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package cli 6 | 7 | import ( 8 | "github.com/spf13/cobra" 9 | "github.com/stolsma/go-p4pack/pkg/cli" 10 | "github.com/stolsma/go-p4pack/pkg/logging" 11 | ) 12 | 13 | func LogSetCommand(parents ...*cobra.Command) *cobra.Command { 14 | setCmd := &cobra.Command{ 15 | Use: "set", 16 | Short: "Sets a logger attribute (e.g. level)", 17 | } 18 | 19 | LogSetLevelCommand(setCmd) 20 | return cli.AddCommand(parents, setCmd) 21 | } 22 | 23 | func LogSetLevelCommand(parents ...*cobra.Command) *cobra.Command { 24 | levelCmd := &cobra.Command{ 25 | Use: "level logger_name", 26 | Short: "Sets a logger level", 27 | Args: cobra.ExactArgs(2), 28 | Run: runSetLevelCommand, 29 | } 30 | 31 | return cli.AddCommand(parents, levelCmd) 32 | } 33 | 34 | func runSetLevelCommand(cmd *cobra.Command, args []string) { 35 | name := args[0] 36 | if name == "" { 37 | cmd.PrintErrf("The logger name should be provided\n") 38 | return 39 | } 40 | 41 | levelArg := args[1] 42 | if levelArg == "" { 43 | cmd.PrintErrf("The logger level should be provided\n") 44 | return 45 | } 46 | 47 | // check if level really exists 48 | level := logging.LevelString2Level(levelArg) 49 | if level == logging.LastLevel { 50 | cmd.PrintErrf("The logger level should be one of: %s\n", logging.LevelStrings) 51 | return 52 | } 53 | 54 | // get the loggers operational configuration 55 | list := logging.GetLoggerDataList() 56 | if _, ok := list[name]; !ok { 57 | cmd.PrintErrf("The logger name does not exist!\n") 58 | return 59 | } 60 | 61 | logger := logging.GetLogger(name) 62 | logger.SetLevel(level) 63 | cmd.Printf("%s logger level is set to %s\n", name, level) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/logging/kafka.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package logging 6 | 7 | import ( 8 | "net/url" 9 | "strings" 10 | 11 | kafka "github.com/Shopify/sarama" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | func init() { 16 | err := zap.RegisterSink("kafka", kafkaSinkFactory) 17 | if err != nil { 18 | panic(err) 19 | } 20 | } 21 | 22 | // kafkaSink is a Kafka sink 23 | type kafkaSink struct { 24 | producer kafka.SyncProducer 25 | topic string 26 | key string 27 | } 28 | 29 | // kafkaSinkFactory is a factory for the Kafka sink 30 | func kafkaSinkFactory(u *url.URL) (zap.Sink, error) { 31 | topic := "kafka_default_topic" 32 | key := "kafka_default_key" 33 | m, _ := url.ParseQuery(u.RawQuery) 34 | if len(m["topic"]) != 0 { 35 | topic = m["topic"][0] 36 | } 37 | 38 | if len(m["key"]) != 0 { 39 | key = m["key"][0] 40 | } 41 | 42 | brokers := strings.Split(u.Host, ",") 43 | config := kafka.NewConfig() 44 | config.Producer.Return.Successes = true 45 | 46 | producer, err := kafka.NewSyncProducer(brokers, config) 47 | if err != nil { 48 | return kafkaSink{}, err 49 | } 50 | 51 | return kafkaSink{ 52 | producer: producer, 53 | topic: topic, 54 | key: key, 55 | }, nil 56 | } 57 | 58 | // Write implements zap.Sink Write function 59 | func (s kafkaSink) Write(b []byte) (int, error) { 60 | var returnErr error 61 | for _, topic := range strings.Split(s.topic, ",") { 62 | if s.key != "" { 63 | _, _, err := s.producer.SendMessage(&kafka.ProducerMessage{ 64 | Topic: topic, 65 | Key: kafka.StringEncoder(s.key), 66 | Value: kafka.ByteEncoder(b), 67 | }) 68 | if err != nil { 69 | returnErr = err 70 | } 71 | } else { 72 | _, _, err := s.producer.SendMessage(&kafka.ProducerMessage{ 73 | Topic: topic, 74 | Value: kafka.ByteEncoder(b), 75 | }) 76 | if err != nil { 77 | returnErr = err 78 | } 79 | } 80 | } 81 | return len(b), returnErr 82 | } 83 | 84 | // Sync implement zap.Sink func Sync 85 | func (s kafkaSink) Sync() error { 86 | return nil 87 | } 88 | 89 | // Close implements zap.Sink Close function 90 | func (s kafkaSink) Close() error { 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /pkg/logging/level.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020-2022 Open Networking Foundation 2 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package logging 6 | 7 | import ( 8 | "strings" 9 | 10 | zp "go.uber.org/zap" 11 | zc "go.uber.org/zap/zapcore" 12 | ) 13 | 14 | // Level : 15 | type Level int 16 | 17 | const ( 18 | // DebugLevel logs a message at debug level 19 | DebugLevel Level = iota 20 | // InfoLevel logs a message at info level 21 | InfoLevel 22 | // WarnLevel logs a message at warning level 23 | WarnLevel 24 | // ErrorLevel logs a message at error level 25 | ErrorLevel 26 | // FatalLevel logs a message, then calls os.Exit(1). 27 | FatalLevel 28 | // PanicLevel logs a message, then panics. 29 | PanicLevel 30 | // DPanicLevel logs at PanicLevel; otherwise, it logs at ErrorLevel 31 | DPanicLevel 32 | // Last level in the list 33 | LastLevel 34 | 35 | // EmptyLevel : 36 | EmptyLevel = InfoLevel 37 | ) 38 | 39 | type LevelStringsType []string 40 | 41 | func (ls LevelStringsType) String() string { 42 | return strings.Join(ls, ", ") 43 | } 44 | 45 | var LevelStrings = LevelStringsType{ 46 | DebugLevel: "DEBUG", 47 | InfoLevel: "INFO", 48 | WarnLevel: "WARN", 49 | ErrorLevel: "ERROR", 50 | FatalLevel: "FATAL", 51 | PanicLevel: "PANIC", 52 | DPanicLevel: "DPANIC", 53 | } 54 | 55 | // String : 56 | func (l Level) String() string { 57 | if l >= LastLevel || l < 0 { 58 | return "" 59 | } 60 | return LevelStrings[l] 61 | } 62 | 63 | func levelToAtomicLevel(l Level) zp.AtomicLevel { 64 | switch l { 65 | case DebugLevel: 66 | return zp.NewAtomicLevelAt(zc.DebugLevel) 67 | case InfoLevel: 68 | return zp.NewAtomicLevelAt(zc.InfoLevel) 69 | case WarnLevel: 70 | return zp.NewAtomicLevelAt(zc.WarnLevel) 71 | case ErrorLevel: 72 | return zp.NewAtomicLevelAt(zc.ErrorLevel) 73 | case FatalLevel: 74 | return zp.NewAtomicLevelAt(zc.FatalLevel) 75 | case PanicLevel: 76 | return zp.NewAtomicLevelAt(zc.PanicLevel) 77 | case DPanicLevel: 78 | return zp.NewAtomicLevelAt(zc.DPanicLevel) 79 | } 80 | return zp.NewAtomicLevelAt(zc.ErrorLevel) 81 | } 82 | 83 | func levelStringToLevel(l string) Level { 84 | lvl := LevelString2Level(l) 85 | if lvl == LastLevel { 86 | return ErrorLevel 87 | } 88 | return lvl 89 | } 90 | 91 | func LevelString2Level(l string) Level { 92 | switch strings.ToUpper(l) { 93 | case DebugLevel.String(): 94 | return DebugLevel 95 | case InfoLevel.String(): 96 | return InfoLevel 97 | case WarnLevel.String(): 98 | return WarnLevel 99 | case ErrorLevel.String(): 100 | return ErrorLevel 101 | case FatalLevel.String(): 102 | return FatalLevel 103 | case PanicLevel.String(): 104 | return PanicLevel 105 | case DPanicLevel.String(): 106 | return DPanicLevel 107 | } 108 | return LastLevel 109 | } 110 | -------------------------------------------------------------------------------- /pkg/logging/register.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022-present Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package logging 5 | 6 | type registerFunc func(Logger) 7 | type register map[string]registerFunc 8 | 9 | var logRegister = make(register) 10 | 11 | // Register the logging domain with a reregister callback that will be called when the logging configuration changes. 12 | // The given callback will be called directly from this function with a current Logger based on the current 13 | // configuration. 14 | func Register(domain string, fn registerFunc) { 15 | logRegister[domain] = fn 16 | 17 | // logging package is not configured yet so wait until it is 18 | if root == nil { 19 | return 20 | } 21 | 22 | // initialize a logger by using the current configuration 23 | logger := GetLogger(domain) 24 | fn(logger) 25 | } 26 | 27 | // Will be called when the logging configuration changes 28 | func reRegister() { 29 | for domain, fn := range logRegister { 30 | fn(GetLogger(domain)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/p4device/README.md: -------------------------------------------------------------------------------- 1 | # This package is under construction -------------------------------------------------------------------------------- /pkg/p4device/p4device.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package p4device 5 | 6 | import ( 7 | "fmt" 8 | "net" 9 | 10 | "github.com/stolsma/go-p4pack/pkg/gnmi" 11 | "github.com/stolsma/go-p4pack/pkg/p4rt" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/reflection" 14 | ) 15 | 16 | const GNMIPort = "9339" 17 | const P4RTPort = "9559" 18 | 19 | // P4Device defines a DPDK SWX target device instance 20 | type P4Device struct { 21 | addr string 22 | stop func() 23 | gnmiGrpcServer *grpc.Server 24 | gnmiServer *gnmi.Server 25 | p4rtGrpcServer *grpc.Server 26 | p4rtServer *p4rt.Server 27 | } 28 | 29 | // New returns a new initialized P4Device instance. 30 | func New(addr string, opts ...grpc.ServerOption) (d *P4Device, err error) { 31 | gnmiGrpcServer := grpc.NewServer(opts...) 32 | p4rtGrpcServer := grpc.NewServer(opts...) 33 | 34 | d = &P4Device{ 35 | addr: addr, 36 | gnmiGrpcServer: gnmiGrpcServer, 37 | p4rtGrpcServer: p4rtGrpcServer, 38 | p4rtServer: p4rt.New(p4rtGrpcServer), 39 | } 40 | d.gnmiServer, err = gnmi.New(gnmiGrpcServer, "Test") 41 | if err != nil { 42 | return nil, fmt.Errorf("failed to start gNMI server: %v", err) 43 | } 44 | 45 | reflection.Register(gnmiGrpcServer) 46 | reflection.Register(p4rtGrpcServer) 47 | 48 | if err := d.startServers(); err != nil { 49 | return nil, fmt.Errorf("failed to start device: %v", err) 50 | } 51 | 52 | return d, nil 53 | } 54 | 55 | // Addr returns the currently configured address for the listening services. 56 | func (d *P4Device) Addr() string { 57 | return d.addr 58 | } 59 | 60 | // Stop stops the listening services. 61 | func (d *P4Device) Stop() { 62 | if d.stop == nil { 63 | return 64 | } 65 | d.stop() 66 | } 67 | 68 | // GNMI returns the gnmi server implementation. 69 | func (d *P4Device) GNMI() *gnmi.Server { 70 | return d.gnmiServer 71 | } 72 | 73 | // P4RT returns the p4rt server implementation. 74 | func (d *P4Device) P4RT() *p4rt.Server { 75 | return d.p4rtServer 76 | } 77 | 78 | func (d *P4Device) startServers() error { 79 | gnmiListener, err := net.Listen("tcp", d.addr+":"+GNMIPort) 80 | if err != nil { 81 | return fmt.Errorf("failed to start gnmi listener: %v", err) 82 | } 83 | p4rtListener, err := net.Listen("tcp", d.addr+":"+P4RTPort) 84 | if err != nil { 85 | return fmt.Errorf("failed to start p4runtime listener: %v", err) 86 | } 87 | 88 | // start services 89 | go d.gnmiGrpcServer.Serve(gnmiListener) 90 | go d.p4rtGrpcServer.Serve(p4rtListener) 91 | 92 | d.stop = func() { 93 | d.gnmiGrpcServer.Stop() 94 | gnmiListener.Close() 95 | d.p4rtGrpcServer.Stop() 96 | p4rtListener.Close() 97 | } 98 | 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /pkg/p4device/p4device_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package p4device 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "google.golang.org/grpc" 11 | codes "google.golang.org/grpc/codes" 12 | "google.golang.org/grpc/credentials/insecure" 13 | status "google.golang.org/grpc/status" 14 | "google.golang.org/protobuf/proto" 15 | 16 | // gNMI 17 | gnmipb "github.com/openconfig/gnmi/proto/gnmi" 18 | 19 | // P4rt 20 | p4rtpb "github.com/p4lang/p4runtime/go/p4/v1" 21 | ) 22 | 23 | func TestGNMI(t *testing.T) { 24 | device, err := New("") 25 | if err != nil { 26 | t.Fatalf("failed to start p4device: %v", err) 27 | } 28 | defer device.Stop() 29 | 30 | conn, err := grpc.Dial(device.Addr()+":"+GNMIPort, grpc.WithTransportCredentials(insecure.NewCredentials())) 31 | if err != nil { 32 | t.Fatalf("failed to Dial p4device: %v", err) 33 | } 34 | 35 | want := &gnmipb.GetResponse{ 36 | Notification: []*gnmipb.Notification{{ 37 | Update: []*gnmipb.Update{{ 38 | Path: &gnmipb.Path{ 39 | Elem: []*gnmipb.PathElem{ 40 | {Name: "intefaces"}, 41 | {Name: "inteface", Key: map[string]string{"name": "eth0"}}, 42 | {Name: "mtu"}, 43 | }, 44 | }, 45 | Val: &gnmipb.TypedValue{ 46 | Value: &gnmipb.TypedValue_IntVal{ 47 | IntVal: 1500, 48 | }, 49 | }, 50 | }}, 51 | }}, 52 | } 53 | device.gnmiServer.GetResponses = []interface{}{want} 54 | 55 | cGNMI := gnmipb.NewGNMIClient(conn) 56 | resp, err := cGNMI.Get(context.Background(), &gnmipb.GetRequest{}) 57 | if err != nil { 58 | t.Fatalf("gnmi.Get failed: %v", err) 59 | } 60 | 61 | if !proto.Equal(resp, want) { 62 | t.Fatalf("gnmi.Get failed got %v, want %v", resp, want) 63 | } 64 | } 65 | 66 | func TestP4RT(t *testing.T) { 67 | device, err := New("") 68 | if err != nil { 69 | t.Fatalf("failed to start p4device: %v", err) 70 | } 71 | defer device.Stop() 72 | 73 | conn, err := grpc.Dial(device.Addr()+":"+P4RTPort, grpc.WithTransportCredentials(insecure.NewCredentials())) 74 | if err != nil { 75 | t.Fatalf("failed to Dial p4device: %v", err) 76 | } 77 | 78 | want := &p4rtpb.CapabilitiesRequest{} 79 | 80 | cP4RT := p4rtpb.NewP4RuntimeClient(conn) 81 | _, err = cP4RT.Capabilities(context.Background(), want) 82 | if err == nil { 83 | t.Fatalf("p4rt.Capabilities worked and that shouldnt! ;-)") 84 | } 85 | 86 | if err != nil { 87 | e, _ := status.FromError(err) 88 | if e.Code() != codes.Unimplemented { 89 | t.Fatalf("Unexpected p4rt.Capabilities error: %v", err) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /pkg/p4rt/README.md: -------------------------------------------------------------------------------- 1 | # This package is under construction -------------------------------------------------------------------------------- /pkg/p4rt/p4rt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package p4rt 5 | 6 | import ( 7 | "google.golang.org/grpc" 8 | 9 | p4rtpb "github.com/p4lang/p4runtime/go/p4/v1" 10 | ) 11 | 12 | // Server is a the p4rt server implementation. 13 | type Server struct { 14 | *p4rtpb.UnimplementedP4RuntimeServer 15 | grpcServer *grpc.Server 16 | } 17 | 18 | // New returns a new p4rt server instance. 19 | func New(s *grpc.Server) *Server { 20 | srv := &Server{ 21 | grpcServer: s, 22 | } 23 | 24 | p4rtpb.RegisterP4RuntimeServer(s, srv) 25 | return srv 26 | } 27 | -------------------------------------------------------------------------------- /pkg/pcidevices/README.md: -------------------------------------------------------------------------------- 1 | # Go-P4Pack pcidevices Package 2 | 3 | This package helps to query DPDK compatibles pci devices and to bind/unbind drivers to pci devices in the Linux Kernel. 4 | With this package the commands of dpdk-devbind.py can be re-created in Go. 5 | 6 | ## API 7 | 8 | The package exports the following API functions that return a PciDevice pointer or an array of PciDevice pointers: 9 | 10 | New(id string) (*PciDevice, error) // input supports PCI identifier string or NIC name 11 | NewDeviceByPciID(pciID string) (*PciDevice, error) 12 | NewDeviceByNicName(nicName string) (*PciDevice, error) 13 | GetPciDevices(classFilter ClassesFilter) ([]*PciDevice, error) // returns a filtered list of current active PCI devs 14 | 15 | The PciDevice supports the following methods: 16 | 17 | PciDevice.Bind(driver string) error 18 | PciDevice.Unbind() error 19 | PciDevice.Probe() error 20 | PciDevice.CurrentDriver() (string, error) 21 | PciDevice.ID() string 22 | PciDevice.String() string 23 | 24 | ClassFilter values: 25 | 26 | AllDevices 27 | NetworkDevices 28 | BasebandDevices 29 | CryptoDevices 30 | DmaDevices 31 | EventdevDevices 32 | MempoolDevices 33 | CompressDevices 34 | RegexDevices 35 | MiscDevices 36 | 37 | ## Extra exported low level API functions 38 | 39 | Lower Level API functions: 40 | 41 | BindPci(devID, driver, vendor, device string) (string, error) 42 | UnbindPci(devID, driver string) error 43 | ProbePci(devID string) (string, error) 44 | GetCurrentPciDriver(devID string) (string, error) 45 | IsModuleLoaded(driver string) bool -------------------------------------------------------------------------------- /pkg/pcidevices/bind.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Intel Corporation. 2 | // Copyright 2023 - Sander Tolsma. All rights reserved 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Package pcidevices helps to query DPDK compatibles devices and to bind/unbind drivers 6 | package pcidevices 7 | 8 | import ( 9 | "errors" 10 | "strings" 11 | ) 12 | 13 | // New returns a corresponding device by given input name 14 | func New(id string) (*PciDevice, error) { 15 | switch { 16 | case IsPciID.Match([]byte(id)): 17 | return NewDeviceByPciID(id) 18 | default: 19 | return NewDeviceByNicName(id) 20 | } 21 | } 22 | 23 | // NewDeviceByPciID returns a PCI device by given PCI ID 24 | func NewDeviceByPciID(pciID string) (*PciDevice, error) { 25 | device, err := GetPciDeviceByPciID(pciID) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return device, nil 31 | } 32 | 33 | // NewDeviceByNicName returns a device by given NIC name, e.g. eth0. 34 | func NewDeviceByNicName(nicName string) (*PciDevice, error) { 35 | devID, err := getDeviceID(nicName) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | if !IsPciID.Match([]byte(devID)) { 41 | return nil, ErrNoValidPciID 42 | } 43 | 44 | return GetPciDeviceByPciID(devID) 45 | } 46 | 47 | // IsModuleLoaded checks if the kernel has already loaded the driver or not. 48 | func IsModuleLoaded(driver string) bool { 49 | output, err := cmdOutputWithTimeout(defaultTimeoutLimitation, "lsmod") 50 | if err != nil { 51 | // Can't run lsmod, return false 52 | return false 53 | } 54 | lines := strings.Split(string(output), "\n") 55 | for _, line := range lines { 56 | if checkModuleLine(line, driver) { 57 | return true 58 | } 59 | } 60 | return false 61 | } 62 | 63 | // get the list of current PCI devices on this system. The list can be filteren by adding a classesfilter list 64 | func GetPciDevices(classFilter ClassesFilter) ([]*PciDevice, error) { 65 | var devices = []*PciDevice{} 66 | var device *PciDevice 67 | 68 | out, err := cmdOutputWithTimeout(defaultTimeoutLimitation, "lspci", "-Dvmmnk") 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | lines := strings.Split(string(out), "\n") 74 | busy := false 75 | for _, line := range lines { 76 | if line != "" { 77 | if !busy { 78 | // get device ID 79 | sLine := strings.Split(line, "\t") 80 | if sLine[0] != "Slot:" { 81 | return nil, errors.New("error parsing device list") 82 | } 83 | 84 | // get device info 85 | device, err = GetPciDeviceByPciID(sLine[1]) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | // only return requested types 91 | if deviceInFilter(device, classFilter) { 92 | devices = append(devices, device) 93 | } 94 | 95 | // forget all other lines 96 | busy = true 97 | } 98 | // go to next line 99 | continue 100 | } else { 101 | // next device 102 | busy = false 103 | } 104 | } 105 | 106 | return devices, nil 107 | } 108 | 109 | func deviceInFilter(device *PciDevice, filter ClassesFilter) bool { 110 | for _, class := range filter { 111 | if stringInList(device.class[0:2], class.Class) && 112 | stringInList(device.vendor, class.Vendor) && 113 | stringInList(device.device, class.Device) { 114 | return true 115 | } 116 | } 117 | 118 | return false 119 | } 120 | 121 | func stringInList(item string, list string) bool { 122 | if list == "" { 123 | return true 124 | } 125 | return strings.Contains(list, item) 126 | } 127 | -------------------------------------------------------------------------------- /pkg/pcidevices/bind_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package pcidevices helps to query DPDK compatibles devices and to bind/unbind drivers 5 | package pcidevices 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestGetPciDevices(t *testing.T) { 12 | devices, err := GetPciDevices(AllDevices) 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | 17 | if devices == nil { 18 | t.Fail() 19 | } 20 | 21 | devices, err = GetPciDevices(NetworkDevices) 22 | if err != nil { 23 | t.Error(err) 24 | } 25 | 26 | if devices == nil { 27 | t.Fail() 28 | } 29 | } 30 | 31 | func TestDeviceInFilter(t *testing.T) { 32 | device1 := &PciDevice{ 33 | class: "02", 34 | vendor: "8086", 35 | device: "1521", 36 | } 37 | 38 | device2 := &PciDevice{ 39 | class: "08", 40 | vendor: "8087", 41 | device: "6f23", 42 | } 43 | 44 | device3 := &PciDevice{ 45 | class: "08", 46 | vendor: "8086", 47 | device: "6f23", 48 | } 49 | 50 | device4 := &PciDevice{ 51 | class: "08", 52 | vendor: "8086", 53 | device: "6f50", 54 | } 55 | 56 | if !deviceInFilter(device1, NetworkDevices) { 57 | t.Fail() 58 | } 59 | 60 | if deviceInFilter(device2, DmaDevices) { 61 | t.Fail() 62 | } 63 | 64 | if !deviceInFilter(device3, DmaDevices) { 65 | t.Fail() 66 | } 67 | 68 | if deviceInFilter(device4, DmaDevices) { 69 | t.Fail() 70 | } 71 | } 72 | 73 | func TestNewDeviceByNicName(t *testing.T) { 74 | res, err := NewDeviceByNicName("eth0") 75 | if err != nil && err != ErrNoDeviceID { 76 | t.Error(err) 77 | } 78 | if res == nil { 79 | return 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pkg/pcidevices/cli/pci.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | "github.com/stolsma/go-p4pack/pkg/cli" 9 | "github.com/stolsma/go-p4pack/pkg/pcidevices" 10 | ) 11 | 12 | func PciCmd(parents ...*cobra.Command) *cobra.Command { 13 | var pciCmd = &cobra.Command{ 14 | Use: "pci", 15 | Short: "Base command for all PCI device actions", 16 | } 17 | 18 | PciListCmd(pciCmd) 19 | PciBindCmd(pciCmd) 20 | PciUnbindCmd(pciCmd) 21 | return cli.AddCommand(parents, pciCmd) 22 | } 23 | 24 | func PciListCmd(parents ...*cobra.Command) *cobra.Command { 25 | listCmd := &cobra.Command{ 26 | Use: "list", 27 | Short: "List all available PCI devices", 28 | Run: func(cmd *cobra.Command, args []string) { 29 | devices, err := pcidevices.GetPciDevices(pcidevices.AllDevices) 30 | if err != nil { 31 | cmd.PrintErrf("List err: %v\n", err) 32 | } 33 | 34 | cmd.Printf("%-13.13s %-30.30s %-27.27s %-27.27s %-15s\n", "Slot", "Class", "Vendor", "Device", "Driver") 35 | for _, d := range devices { 36 | cmd.Printf("%-13.13s %-30.30s %-27.27s %-27.27s %-15.15s\n", d.ID(), d.ClassExt(), d.VendorExt(), d.DeviceExt(), d.Driver()) 37 | } 38 | }, 39 | } 40 | 41 | return cli.AddCommand(parents, listCmd) 42 | } 43 | 44 | func PciBindCmd(parents ...*cobra.Command) *cobra.Command { 45 | bindCmd := &cobra.Command{ 46 | Use: "bind [pci device id] [driver]", 47 | Short: "bind PCI device to given driver", 48 | Args: cobra.ExactArgs(2), 49 | ValidArgsFunction: cli.ValidateArguments( 50 | cli.AppendHelp("You must choose a pci address for the pci device you want to bind"), 51 | cli.AppendHelp("You must specify the name of the driver you want to bind to"), 52 | cli.AppendLastHelp(2, "This command does not take any more arguments"), 53 | ), 54 | Run: func(cmd *cobra.Command, args []string) { 55 | device, err := pcidevices.New(args[0]) 56 | if err != nil { 57 | cmd.PrintErrf("Get device error: %v\n", err) 58 | return 59 | } 60 | 61 | if err := device.Bind(args[1]); err != nil { 62 | cmd.PrintErrf("Device bind error: %v\n", err) 63 | return 64 | } 65 | 66 | cmd.Printf("Device %s is now bound to driver %s\n", args[0], args[1]) 67 | }, 68 | } 69 | 70 | return cli.AddCommand(parents, bindCmd) 71 | } 72 | 73 | func PciUnbindCmd(parents ...*cobra.Command) *cobra.Command { 74 | bindCmd := &cobra.Command{ 75 | Use: "unbind [pci device id]", 76 | Short: "Unbind PCI device from current driver", 77 | Args: cobra.ExactArgs(1), 78 | ValidArgsFunction: cli.ValidateArguments( 79 | cli.AppendHelp("You must choose a pci address for the pci device you want to unbind"), 80 | cli.AppendLastHelp(1, "This command does not take any more arguments"), 81 | ), 82 | Run: func(cmd *cobra.Command, args []string) { 83 | device, err := pcidevices.New(args[0]) 84 | if err != nil { 85 | cmd.PrintErrf("Get device error: %v\n", err) 86 | return 87 | } 88 | 89 | if err := device.Unbind(); err != nil { 90 | cmd.PrintErrf("Device unbind error: %v\n", err) 91 | return 92 | } 93 | 94 | cmd.Printf("Device %s is now not bound to any driver!\n", args[0]) 95 | }, 96 | } 97 | 98 | return cli.AddCommand(parents, bindCmd) 99 | } 100 | -------------------------------------------------------------------------------- /pkg/pcidevices/cli/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cli 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | "github.com/stolsma/go-p4pack/pkg/logging" 9 | ) 10 | 11 | var log logging.Logger 12 | 13 | func init() { 14 | // keep the logger up to date, also after new log config 15 | logging.Register("pcidevices/cli", func(logger logging.Logger) { 16 | log = logger 17 | }) 18 | } 19 | 20 | // GetCommand returns the given parent (root) command with all pcidevices sub commands added 21 | func GetCommand(parent *cobra.Command) *cobra.Command { 22 | log.Info("Adding pcidevices cli commands") 23 | 24 | // add all dpdkinfra cli commands 25 | PciCmd(parent) 26 | 27 | return parent 28 | } 29 | -------------------------------------------------------------------------------- /pkg/pcidevices/common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Intel Corporation. 2 | // Copyright 2023 - Sander Tolsma. All rights reserved 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | // Package pcidevices helps to query DPDK compatibles devices and to bind/unbind drivers 6 | package pcidevices 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "os" 12 | "os/exec" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | var defaultTimeoutLimitation = 5 * time.Second 18 | 19 | // GetDeviceID returns the device ID of given NIC name. 20 | func getDeviceID(nicName string) (string, error) { 21 | // DEV_ID=$(basename $(readlink /sys/class/net/)) 22 | raw, err := readlinkCmd(pathSysClassNetDevice.With(nicName)) 23 | if err != nil { 24 | return "", err 25 | } 26 | 27 | // raw should be like /sys/devices/pci0002:00/0000:00:08.0/virtio2/net/ens8 28 | // or /sys/devices/pci0000:00/0000:00:01.0/0000:03:00.2/net/ens4f2 29 | raws := strings.Split(raw, "/") 30 | if len(raws) < 6 { 31 | return "", fmt.Errorf("path not correct") 32 | } 33 | 34 | switch { 35 | case IsPciID.Match([]byte(raws[5])): 36 | return raws[5], nil 37 | case IsPciID.Match([]byte(raws[4])): 38 | return raws[4], nil 39 | case len(raws) >= 11 && IsPciID.Match([]byte(raws[10])): 40 | return raws[10], nil 41 | default: 42 | return "", ErrNoDeviceID 43 | } 44 | } 45 | 46 | func writeToTargetWithData(sysfs string, flag int, mode os.FileMode, data string) error { 47 | writer, err := os.OpenFile(sysfs, flag, mode) 48 | if err != nil { 49 | return fmt.Errorf("OpenFile failed: %s", err.Error()) 50 | } 51 | defer writer.Close() 52 | 53 | _, err = writer.Write([]byte(data)) 54 | if err != nil { 55 | return fmt.Errorf("WriteFile failed: %s", err.Error()) 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func readlinkCmd(path string) (string, error) { 62 | output, err := cmdOutputWithTimeout(defaultTimeoutLimitation, "readlink", "-f", path) 63 | if err != nil { 64 | return "", fmt.Errorf("cmd execute readlink failed: %s", err.Error()) 65 | } 66 | outputStr := strings.Trim(string(output), "\n") 67 | return outputStr, nil 68 | } 69 | 70 | func checkModuleLine(line, driver string) bool { 71 | if !strings.Contains(line, driver) { 72 | return false 73 | } 74 | elements := strings.Split(line, " ") 75 | return elements[0] == driver 76 | } 77 | 78 | func cmdOutputWithTimeout(duration time.Duration, cmd string, parameters ...string) ([]byte, error) { 79 | ctx, cancel := context.WithTimeout(context.Background(), duration) 80 | defer cancel() 81 | output, err := exec.CommandContext(ctx, cmd, parameters...).Output() 82 | if err != nil { 83 | return nil, err 84 | } 85 | return output, nil 86 | } 87 | -------------------------------------------------------------------------------- /pkg/signals/signals.go: -------------------------------------------------------------------------------- 1 | package signals 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | ) 9 | 10 | var capturedSignals = []os.Signal{syscall.SIGTERM, syscall.SIGINT} 11 | 12 | // RegisterSignalHandlers registers a signal handler for capturedSignals and starts a goroutine that 13 | // will block until a signal is received. The first signal received will cause the stopCh channel to 14 | // be closed, giving the opportunity to the program to exist gracefully. If a second signal is 15 | // received before then, we will force exit with code 1. 16 | func RegisterSignalHandlers() <-chan struct{} { 17 | notifyCh := make(chan os.Signal, 2) 18 | stopCh := make(chan struct{}) 19 | 20 | go func() { 21 | <-notifyCh 22 | close(stopCh) 23 | <-notifyCh 24 | log.Println("Received second signal, will force exit") 25 | os.Exit(1) 26 | }() 27 | 28 | signal.Notify(notifyCh, capturedSignals...) 29 | 30 | return stopCh 31 | } 32 | -------------------------------------------------------------------------------- /pkg/sshshell/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package sshshell 5 | 6 | import "github.com/gliderlabs/ssh" 7 | 8 | type Auth struct { 9 | users map[string]User 10 | } 11 | 12 | func NewAuth(config *Config) *Auth { 13 | return &Auth{ 14 | users: config.Users, 15 | } 16 | } 17 | 18 | func (a *Auth) PasswordHandler(ctx ssh.Context, password string) bool { 19 | user, exists := a.users[ctx.User()] 20 | if !exists { 21 | return false 22 | } 23 | 24 | return user.Password == password 25 | } 26 | -------------------------------------------------------------------------------- /pkg/sshshell/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package sshshell 5 | 6 | type User struct { 7 | Password string `json:"password"` 8 | } 9 | 10 | type Config struct { 11 | HistorySize int `json:"historysize"` 12 | HostKeyFile string `json:"hostkeyfile" mapstructure:"host-key-file"` 13 | Users map[string]User `json:"users"` 14 | Bind string `json:"bind"` 15 | } 16 | 17 | const ( 18 | DefaultConfigHistorySize = 100 19 | DefaultBind = ":22" 20 | ) 21 | 22 | func useOrDefaultInt(value int, defaultValue int) int { 23 | if value == 0 { 24 | return defaultValue 25 | } 26 | return value 27 | } 28 | 29 | func useOrDefaultString(value string, defaultValue string) string { 30 | if value == "" { 31 | return defaultValue 32 | } 33 | return value 34 | } 35 | -------------------------------------------------------------------------------- /pkg/sshshell/key.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package sshshell 5 | 6 | import ( 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "crypto/x509" 10 | "encoding/pem" 11 | "os" 12 | 13 | "github.com/gliderlabs/ssh" 14 | gossh "golang.org/x/crypto/ssh" 15 | ) 16 | 17 | type HostKeyResolver struct { 18 | hostKeyFile string 19 | } 20 | 21 | func NewHostKeyResolver(config *Config) *HostKeyResolver { 22 | return &HostKeyResolver{ 23 | hostKeyFile: config.HostKeyFile, 24 | } 25 | } 26 | 27 | func (r *HostKeyResolver) Resolve() string { 28 | if r.hostKeyFile == "" { 29 | return "" 30 | } 31 | 32 | _, err := os.Stat(r.hostKeyFile) 33 | if err != nil { 34 | if os.IsNotExist(err) { 35 | err := r.createHostKeyFile() 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | } else { 40 | log.Fatal(err) 41 | } 42 | } 43 | 44 | return r.hostKeyFile 45 | } 46 | 47 | func (r *HostKeyResolver) createHostKeyFile() error { 48 | key, err := rsa.GenerateKey(rand.Reader, 2048) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | publicKey, err := gossh.NewPublicKey(key.Public()) 54 | if err != nil { 55 | return err 56 | } 57 | fingerprint := gossh.FingerprintSHA256(publicKey) 58 | log.Infof("I: Generating host key with fingerprint %s to %s\n", fingerprint, r.hostKeyFile) 59 | file, err := os.Create(r.hostKeyFile) 60 | if err != nil { 61 | return err 62 | } 63 | //noinspection GoUnhandledErrorResult 64 | defer file.Close() 65 | 66 | var keyBlock = &pem.Block{ 67 | Type: "RSA PRIVATE KEY", 68 | Bytes: x509.MarshalPKCS1PrivateKey(key), 69 | } 70 | 71 | err = pem.Encode(file, keyBlock) 72 | return err 73 | } 74 | 75 | func (r *HostKeyResolver) ResolveOption() ssh.Option { 76 | if r.hostKeyFile == "" { 77 | return func(s *ssh.Server) error { 78 | return nil 79 | } 80 | } 81 | return ssh.HostKeyFile(r.Resolve()) 82 | } 83 | -------------------------------------------------------------------------------- /pkg/tdi/README.md: -------------------------------------------------------------------------------- 1 | # This package is under construction -------------------------------------------------------------------------------- /pkg/tdi/targets/p4-dpdk-target/p4-dpdk-target.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package p4DpdkTarget 5 | 6 | func InitTarget() error { 7 | return nil 8 | } 9 | -------------------------------------------------------------------------------- /pkg/tdi/tdi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 - Sander Tolsma. All rights reserved 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package tdi 5 | --------------------------------------------------------------------------------