diff --git a/.fmf/version b/.fmf/version new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/.fmf/version @@ -0,0 +1 @@ +1 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index feed9ad44..3b2e22c7d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,11 +3,21 @@ version: 2 updates: - - package-ecosystem: "cargo" - directory: "/" + - package-ecosystem: cargo + directory: / schedule: - interval: "weekly" - - package-ecosystem: "github-actions" - directory: "/" + interval: weekly + groups: + production-dependencies: + dependency-type: production + development-dependencies: + dependency-type: development + - package-ecosystem: github-actions + directory: / schedule: - interval: "weekly" + interval: weekly + groups: + production-dependencies: + dependency-type: production + development-dependencies: + dependency-type: development diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 72b126086..2657b2f01 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ on: # yamllint disable-line rule:truthy push: branches: [main] tags: - - "v*" + - v* pull_request: branches: @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Check License Header - uses: apache/skywalking-eyes@a790ab8dd23a7f861c18bd6aaa9b012e3a234bce + uses: apache/skywalking-eyes@cd7b195c51fd3d6ad52afceb760719ddc6b3ee91 build: runs-on: ubuntu-latest @@ -39,7 +39,6 @@ jobs: libssl-dev \ gcc-multilib \ libelf-dev \ - musl-tools - uses: actions/checkout@v4 - uses: actions/checkout@v4 with: @@ -58,7 +57,9 @@ jobs: components: rustfmt, clippy, rust-src override: false - - uses: Swatinem/rust-cache@v2 + ## TODO(astoycos) Deactivate the rust-cache action until we can determine + ## why it's freezing at the end of the install. + ## - uses: Swatinem/rust-cache@v2 - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov @@ -87,28 +88,37 @@ jobs: run: | cargo +nightly clippy --all -- --deny warnings + - name: Check public API + run: cargo xtask public-api + - name: Build run: cargo build --verbose + - name: Build manpages + run: cargo xtask build-man-page + + - name: Build CLI TAB Completion + run: cargo xtask build-completion + ## If the push is a tag....build and upload the release bpfman binaries to an archive - name: Build-Release if: startsWith(github.ref, 'refs/tags/v') run: | - rustup target add x86_64-unknown-linux-musl - cargo build --release --target x86_64-unknown-linux-musl + cargo build --release --target x86_64-unknown-linux-gnu - name: Package-Binaries if: startsWith(github.ref, 'refs/tags/v') run: | - tar -czvf bpfman-linux-x86_64.tar.gz ./target/x86_64-unknown-linux-musl/release/bpfman + cd target/x86_64-unknown-linux-gnu/release + tar -czvf bpfman-linux-x86_64.tar.gz bpfman bpfman-rpc bpfman-ns bpf-metrics-exporter bpf-log-exporter - name: Archive bpfman Release Binaries if: startsWith(github.ref, 'refs/tags/v') - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: bpfman-release path: | - ./bpfman-linux-x86_64.tar.gz + ./target/x86_64-unknown-linux-gnu/release/bpfman-linux-x86_64.tar.gz - name: Run tests run: cargo llvm-cov test --all-features -p bpfman -p bpfman-api --lcov --output-path lcov.info @@ -116,38 +126,55 @@ jobs: RUST_BACKTRACE: full - name: Archive Rust code coverage results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: coverage + name: coverage-rust path: lcov.info if-no-files-found: error ## Build go modules build-go: runs-on: ubuntu-latest + # k8s codegen requires this to be set + env: + GOPATH: ${{ github.workspace }} defaults: run: - working-directory: ./bpfman-operator + ## For us to run the controller-gen generate commands from within + ## github actions the package name MUST match the directory layout + ## (i.e `GOPATH/src/github.com/bpfman/bpfman`). Otherwise when + ## running `make generate` generated code is deposited at + ## `home/runner/work/bpfman/bpfman/bpfman-operator/PKG_NAME` instead + ## of in `home/runner/work/bpfman/bpfman/bpfman-operator/pkg/client/...`. + ## This is annoying and gross but cannot be resolved until + ## https://github.com/kubernetes/kubernetes/issues/86753 is properly + ## addressed. + working-directory: ${{ env.GOPATH }}/src/github.com/bpfman/bpfman/bpfman-operator steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 with: - go-version: "1.21" + path: ${{ env.GOPATH }}/src/github.com/bpfman/bpfman + + - uses: actions/setup-go@v5 + with: + # prettier-ignore + go-version: '1.21' # yamllint disable-line rule:quoted-strings - name: Go mod check - working-directory: ${{ github.workspace }} run: | go mod tidy - git diff --exit-code go.mod go.sum + git diff --exit-code ../go.mod ../go.sum - name: Lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: + ## https://github.com/golangci/golangci-lint-action/issues/369 + working-directory: ${{ env.GOPATH }}/src/github.com/bpfman/bpfman version: v1.54.2 skip-cache: true skip-pkg-cache: true skip-build-cache: true - args: -v --timeout 5m --enable=gofmt + args: -v --timeout 5m - name: Build Examples run: | @@ -163,15 +190,28 @@ jobs: run: make test - name: Archive Go code coverage results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: coverage - path: ./bpfman-operator/cover.out + name: coverage-go + path: ${{ env.GOPATH }}/src/github.com/bpfman/bpfman/bpfman-operator/cover.out if-no-files-found: error + build-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Mkdocs Build + run: | + pip install mkdocs-material + pip install -r requirements.txt + mkdocs build --strict + basic-integration-tests: runs-on: ubuntu-latest - needs: ["build", "build-go"] + needs: [build, build-go, build-docs] env: CARGO_TERM_COLOR: always steps: @@ -209,6 +249,12 @@ jobs: - name: Build bpfman run: cargo build --verbose + - name: Build manpages + run: cargo xtask build-man-page + + - name: Build CLI TAB Completion + run: cargo xtask build-completion + - name: Run the bpfman installer run: sudo ./scripts/setup.sh install @@ -216,24 +262,30 @@ jobs: run: sleep 5 - name: Verify the bpfman systemd service is active - run: systemctl is-active bpfman + run: systemctl is-active bpfman.socket - name: Verify the CLI can reach bpfman run: sudo bpfman list + - name: Verify the manpages are installed + run: man bpfman list + - name: Stop the bpfman systemd service - run: sudo systemctl stop bpfman + run: | + sudo systemctl stop bpfman + sudo ./scripts/setup.sh uninstall - name: Run integration tests run: cargo xtask integration-test kubernetes-integration-tests: - needs: ["build", "build-go"] + needs: [build, build-go, build-docs] runs-on: ubuntu-latest env: - BPFMAN_IMG: "quay.io/bpfman/bpfman:int-test" - BPFMAN_AGENT_IMG: "quay.io/bpfman/bpfman-agent:int-test" - BPFMAN_OPERATOR_IMG: "quay.io/bpfman/bpfman-operator:int-test" + BPFMAN_IMG: quay.io/bpfman/bpfman:int-test + BPFMAN_AGENT_IMG: quay.io/bpfman/bpfman-agent:int-test + BPFMAN_OPERATOR_IMG: quay.io/bpfman/bpfman-operator:int-test + XDP_PASS_PRIVATE_IMAGE_CREDS: ${{ secrets.XDP_PASS_PRIVATE_IMAGE_CREDS }} steps: - name: Install dependencies run: | @@ -246,12 +298,13 @@ jobs: libbpf-dev - name: setup golang - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: "^1.19" + # prettier-ignore + go-version: '1.21' # yamllint disable-line rule:quoted-strings - name: cache go modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-build-codegen-${{ hashFiles('**/go.sum') }} @@ -283,27 +336,31 @@ jobs: ## Upload diagnostics if integration test step failed. - name: upload diagnostics if: ${{ failure() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: kubernetes-integration-test-diag path: /tmp/ktf-diag* if-no-files-found: ignore coverage: - needs: ["build", "build-go"] + needs: [build, build-go, build-docs] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Download coverage artifacts - uses: actions/download-artifact@v3 + - name: Download golang coverage artifacts + uses: actions/download-artifact@v4 with: - name: coverage + name: coverage-go + + - name: Download rust coverage artifacts + uses: actions/download-artifact@v4 + with: + name: coverage-rust - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: - fail_ci_if_error: true files: ./cover.out,./lcov.info verbose: true @@ -312,7 +369,7 @@ jobs: # Publish's bpfman and bpfman-api crates to crates.io release: if: startsWith(github.ref, 'refs/tags/v') - needs: ["build"] + needs: [build] environment: crates.io runs-on: ubuntu-latest steps: @@ -322,33 +379,42 @@ jobs: run: | echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: bpfman-release - name: release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: body_path: ./changelogs/CHANGELOG-${{ env.RELEASE_VERSION }}.md files: | bpfman-linux-x86_64.tar.gz - ## TODO once we're using an aya mainline version - # - name: publish bpfman crate - # run: cargo publish -p bpfman --token ${{ secrets.BPFMAN_DEV_TOKEN }} + - name: publish bpfman-csi crate + run: cargo publish -p bpfman-csi --token ${{ secrets.BPFMAN_DEV_TOKEN }} + + - name: publish bpfman crate + run: cargo publish -p bpfman --token ${{ secrets.BPFMAN_DEV_TOKEN }} - name: publish bpfman-api crate run: cargo publish -p bpfman-api --token ${{ secrets.BPFMAN_DEV_TOKEN }} + - name: publish bpf-log-exporter crate + run: cargo publish -p bpfman-log-exporter --token ${{ secrets.BPFMAN_DEV_TOKEN }} + + - name: publish bpf-metrics-exporter crate + run: cargo publish -p bpfman-metrics-exporter --token ${{ secrets.BPFMAN_DEV_TOKEN }} + build-workflow-complete: needs: [ - "check-license", - "build", - "build-go", - "coverage", - "basic-integration-tests", - "kubernetes-integration-tests", + check-license, + build, + build-go, + build-docs, + coverage, + basic-integration-tests, + kubernetes-integration-tests, ] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/docs-build.yaml b/.github/workflows/docs-build.yaml new file mode 100644 index 000000000..cd0ed448a --- /dev/null +++ b/.github/workflows/docs-build.yaml @@ -0,0 +1,47 @@ +name: bpfman-docs + +on: # yamllint disable-line rule:truthy + push: + branches: [main] + tags: + - v* + +jobs: + build-docs: + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - uses: actions/checkout@v4 + with: + ref: gh-pages + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.8 + - uses: actions/setup-go@v5 + with: + # prettier-ignore + go-version: '1.21' # yamllint disable-line rule:quoted-strings + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Build API docs + run: | + make -C bpfman-operator apidocs.html + + - name: Configure Git user + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + - name: Deploy Docs (Release Version) + if: startsWith(github.ref_name, 'v') + run: | + mike deploy --push --update-aliases ${{ github.ref_name }} latest + + - name: Deploy Docs (Development Version) + if: github.ref_name == 'main' + run: | + mike deploy --push main diff --git a/.github/workflows/image-build.yaml b/.github/workflows/image-build.yaml index 5b39bbdab..257620ca5 100644 --- a/.github/workflows/image-build.yaml +++ b/.github/workflows/image-build.yaml @@ -4,10 +4,10 @@ on: # yamllint disable-line rule:truthy push: branches: [main] tags: - - "v*" + - v* pull_request: - paths: [".github/workflows/image-build.yaml"] + paths: [.github/workflows/image-build.yaml] jobs: build-and-push-images: @@ -22,6 +22,7 @@ jobs: matrix: image: - registry: quay.io + build_language: rust repository: bpfman image: bpfman dockerfile: ./Containerfile.bpfman @@ -35,6 +36,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: go repository: bpfman image: bpfman-agent dockerfile: ./bpfman-operator/Containerfile.bpfman-agent @@ -48,6 +50,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: go repository: bpfman image: bpfman-operator dockerfile: ./bpfman-operator/Containerfile.bpfman-operator @@ -61,6 +64,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: go repository: bpfman image: bpfman-operator-bundle context: ./bpfman-operator @@ -74,6 +78,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: go repository: bpfman-userspace image: go-xdp-counter context: . @@ -87,6 +92,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: go repository: bpfman-userspace image: go-tc-counter context: . @@ -100,6 +106,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: go repository: bpfman-userspace image: go-tracepoint-counter context: . @@ -113,6 +120,49 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: go + repository: bpfman-userspace + image: go-kprobe-counter + context: . + dockerfile: ./examples/go-kprobe-counter/container-deployment/Containerfile.go-kprobe-counter + tags: | + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=sha,format=long + # set latest tag for default branch + type=raw,value=latest,enable={{is_default_branch}} + + - registry: quay.io + build_language: go + repository: bpfman-userspace + image: go-uprobe-counter + context: . + dockerfile: ./examples/go-uprobe-counter/container-deployment/Containerfile.go-uprobe-counter + tags: | + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=sha,format=long + # set latest tag for default branch + type=raw,value=latest,enable={{is_default_branch}} + + - registry: quay.io + build_language: go + repository: bpfman-userspace + image: go-target + context: . + dockerfile: ./examples/go-target/container-deployment/Containerfile.go-target + tags: | + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=sha,format=long + # set latest tag for default branch + type=raw,value=latest,enable={{is_default_branch}} + + - registry: quay.io + build_language: go bpf_build_wrapper: go repository: bpfman-bytecode image: go-xdp-counter @@ -132,6 +182,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: go bpf_build_wrapper: go repository: bpfman-bytecode image: go-tc-counter @@ -151,6 +202,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: go bpf_build_wrapper: go repository: bpfman-bytecode image: go-tracepoint-counter @@ -170,6 +222,47 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: go + bpf_build_wrapper: go + repository: bpfman-bytecode + image: go-kprobe-counter + context: ./examples/go-kprobe-counter + dockerfile: ./Containerfile.bytecode + build_args: | + PROGRAM_NAME=kprobe_counter + BPF_FUNCTION_NAME=kprobe_counter + PROGRAM_TYPE=kprobe + BYTECODE_FILENAME=bpf_bpfel.o + tags: | + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=sha,format=long + # set latest tag for default branch + type=raw,value=latest,enable={{is_default_branch}} + + - registry: quay.io + build_language: go + bpf_build_wrapper: go + repository: bpfman-bytecode + image: go-uprobe-counter + context: ./examples/go-uprobe-counter + dockerfile: ./Containerfile.bytecode + build_args: | + PROGRAM_NAME=uprobe_counter + BPF_FUNCTION_NAME=uprobe_counter + PROGRAM_TYPE=uprobe + BYTECODE_FILENAME=bpf_bpfel.o + tags: | + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=sha,format=long + # set latest tag for default branch + type=raw,value=latest,enable={{is_default_branch}} + + - registry: quay.io + build_language: rust bpf_build_wrapper: rust repository: bpfman-bytecode image: xdp_pass @@ -189,6 +282,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: rust bpf_build_wrapper: rust repository: bpfman-bytecode image: xdp_pass_private @@ -208,6 +302,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: rust bpf_build_wrapper: rust repository: bpfman-bytecode image: tc_pass @@ -227,6 +322,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: rust bpf_build_wrapper: rust repository: bpfman-bytecode image: tracepoint @@ -246,6 +342,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: rust bpf_build_wrapper: rust repository: bpfman-bytecode image: uprobe @@ -265,6 +362,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: rust bpf_build_wrapper: rust repository: bpfman-bytecode image: uretprobe @@ -284,6 +382,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: rust bpf_build_wrapper: rust repository: bpfman-bytecode image: kprobe @@ -303,6 +402,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: rust bpf_build_wrapper: rust repository: bpfman-bytecode image: kretprobe @@ -322,6 +422,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - registry: quay.io + build_language: rust bpf_build_wrapper: rust repository: bpfman image: xdp-dispatcher @@ -332,6 +433,7 @@ jobs: type=raw,value=v1,enable=true - registry: quay.io + build_language: rust bpf_build_wrapper: rust repository: bpfman image: xdp-dispatcher @@ -342,6 +444,7 @@ jobs: type=raw,value=v2,enable=true - registry: quay.io + build_language: rust bpf_build_wrapper: rust repository: bpfman image: tc-dispatcher @@ -355,12 +458,13 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 - if: ${{ matrix.image.bpf_build_wrapper == 'go' }} + - uses: actions/setup-go@v5 + if: ${{ matrix.image.build_language == 'go' }} with: - go-version: "1.21" + # prettier-ignore + go-version: '1.21' # yamllint disable-line rule:quoted-strings - - uses: sigstore/cosign-installer@v3.2.0 + - uses: sigstore/cosign-installer@v3.4.0 - uses: actions/checkout@v4 if: ${{ matrix.image.bpf_build_wrapper == 'rust' }} @@ -369,7 +473,7 @@ jobs: path: libbpf - uses: actions-rs/toolchain@v1 - if: ${{ matrix.image.bpf_build_wrapper == 'rust' }} + if: ${{ matrix.image.build_language == 'rust' }} with: toolchain: stable override: true @@ -426,7 +530,7 @@ jobs: - name: Extract metadata (tags, labels) for image id: meta - uses: docker/metadata-action@v5.0.0 + uses: docker/metadata-action@v5.5.1 with: images: ${{ matrix.image.registry }}/${{ matrix.image.repository }}/${{ matrix.image.image }} tags: ${{ matrix.image.tags }} diff --git a/.github/workflows/put-issue-in-project.yaml b/.github/workflows/put-issue-in-project.yaml deleted file mode 100644 index c2003f943..000000000 --- a/.github/workflows/put-issue-in-project.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: Add all issues to bpfman project - -on: # yamllint disable-line rule:truthy - issues: - types: - - opened - -jobs: - add-to-project: - name: Add issue to project - runs-on: ubuntu-latest - steps: - - uses: actions/add-to-project@v0.5.0 - with: - # You can target a project in a different organization - # to the issue - project-url: https://github.com/orgs/bpfman/projects/4 - github-token: ${{ secrets.BPFMAN_PROJECT_PAT }} diff --git a/.gitignore b/.gitignore index 1ab154082..a242a51ef 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,13 @@ tests/_tmp # Ignore compiled C files .output + +# Ignore docs +site/ + +# RPM build artifacts +*.tar.gz +*.rpm +results_*/ +*x86_64/ +cargo-vendor-filterer diff --git a/.golangci.yaml b/.golangci.yaml index d211011b5..141c21632 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -4,6 +4,15 @@ run: # - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ # Default: true skip-dirs-use-default: false + +linters: enable: - gofmt + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused - typecheck + - loggercheck diff --git a/.packit.sh b/.packit.sh new file mode 100755 index 000000000..bdb7a5d60 --- /dev/null +++ b/.packit.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +GIT_SHA=$(git rev-parse HEAD) +GIT_SHORT_SHA=$(git rev-parse --short HEAD) + +sed -i "s/GITSHA/${GIT_SHA}/g" bpfman.spec +sed -i "s/GITSHORTSHA/${GIT_SHORT_SHA}/g" bpfman.spec + +sed -i -r "s/Release:(\s*)\S+/Release:\1${PACKIT_RPMSPEC_RELEASE}%{?dist}/" bpfman.spec diff --git a/.packit.yml b/.packit.yml new file mode 100644 index 000000000..0f10a5a57 --- /dev/null +++ b/.packit.yml @@ -0,0 +1,49 @@ +downstream_package_name: bpfman +upstream_project_url: https://github.com/bpfman/bpfman +specfile_path: bpfman.spec +prerelease_suffix_pattern: ([.\-_~^]?)(dev|alpha|beta|rc|pre(view)?)([.\-_]?\d+)? +srpm_build_deps: + - cargo + - rust + - git + - jq + - openssl-devel +actions: + fix-spec-file: + - bash .packit.sh + get-current-version: + - bash -c 'cargo metadata --format-version 1 | jq -r ".packages[] | select(.name == \"bpfman\") | .version"' + post-upstream-clone: + bash -c 'if [[ ! -d /var/tmp/cargo-vendor-filterer ]]; then git clone https://github.com/coreos/cargo-vendor-filterer.git /var/tmp/cargo-vendor-filterer; fi && + cd /var/tmp/cargo-vendor-filterer && + cargo build && + cd - && + cp /var/tmp/cargo-vendor-filterer/target/debug/cargo-vendor-filterer . && + ./cargo-vendor-filterer --format tar.gz --prefix vendor bpfman-bpfman-vendor.tar.gz' +jobs: + - job: copr_build + trigger: commit + branch: main + specfile_path: bpfman.spec + owner: "@ebpf-sig" + project: bpfman-next + targets: + - fedora-all + - job: tests + trigger: commit + targets: + - fedora-all + specfile_path: bpfman.spec + - job: copr_build + trigger: release + branch: main + specfile_path: bpfman.spec + owner: "@ebpf-sig" + project: bpfman + targets: + - fedora-all + - job: tests + trigger: release + targets: + - fedora-all + specfile_path: bpfman.spec diff --git a/.yamllint.yaml b/.yamllint.yaml index 7808dc35d..3bfb0a8ed 100644 --- a/.yamllint.yaml +++ b/.yamllint.yaml @@ -5,6 +5,9 @@ rules: document-start: disable comments: min-spaces-from-content: 1 + quoted-strings: + required: only-when-needed + quote-type: double ignore: - libbpf/* - vendor/* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c067f4fc1..a4d3c52d6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,12 @@ # Contributing Guide -* [New Contributor Guide](#contributing-guide) - * [Ways to Contribute](#ways-to-contribute) - * [Find an Issue](#find-an-issue) - * [Ask for Help](#ask-for-help) - * [Pull Request Lifecycle](#pull-request-lifecycle) - * [Development Environment Setup](#development-environment-setup) - * [Signoff Your Commits](#signoff-your-commits) - * [Pull Request Checklist](#pull-request-checklist) +* [Ways to Contribute](#ways-to-contribute) +* [Find an Issue](#find-an-issue) +* [Ask for Help](#ask-for-help) +* [Pull Request Lifecycle](#pull-request-lifecycle) +* [Development Environment Setup](#development-environment-setup) +* [Signoff Your Commits](#signoff-your-commits) +* [Pull Request Checklist](#pull-request-checklist) Welcome! We are glad that you want to contribute to our project! 💖 @@ -81,13 +80,13 @@ Our process is currently as follows: 1. When you open a PR a maintainer will automatically be assigned for review 1. Make sure that your PR is passing CI - if you need help with failing checks please feel free to ask! 1. Once it is passing all CI checks, a maintainer will review your PR and you may be asked to make changes. -1. When you have received at least one approval from a maintainer, your PR will be merged automiatcally. +1. When you have received at least one approval from a maintainer, your PR will be merged automatically. In some cases, other changes may conflict with your PR. If this happens, you will get notified by a comment in the issue that your PR requires a rebase, and the `needs-rebase` label will be applied. Once a rebase has been performed, this label will be automatically removed. ## Development Environment Setup -[Instructions](https://bpfman.netlify.app/building-bpfman/#development-environment-setup) +See [Setup and Building bpfman](https://bpfman.io/main/getting-started/building-bpfman/#development-environment-setup) ## Signoff Your Commits @@ -138,8 +137,8 @@ A good commit message should describe what changed and why. Examples: - * bpfman: validate program section names - * bpf: add dispatcher program test slot + * bpfman: validate program section names + * bpf: add dispatcher program test slot 2. Keep the second line blank. 3. Wrap all other lines at 72 columns (except for long URLs). @@ -180,45 +179,46 @@ accept and merge it. We recommend that you check the following things locally before you submit your code: * Verify that Rust code has been formatted and that all clippy lints have been fixed: - -```console -cd src/bpfman/ -cargo +nightly fmt --all -- --check -cargo +nightly clippy --all -- --deny warnings -``` - * Verify that Go code has been formatted and linted * Verify that Yaml files have been formatted (see - [Install Yaml Formatter](https://bpfman.io/getting-started/building-bpfman/#install-yaml-formatter)) -* Verify that unit tests are passing locally (see - [Unit Testing](https://bpfman.io/developer-guide/testing/#unit-testing)): + [Install Yaml Formatter](https://bpfman.io/main/getting-started/building-bpfman/#install-yaml-formatter)) +* Verify that Bash scripts have been linted using `shellcheck` -```console -cd src/bpfman/ -cargo test -``` + ```console + cd bpfman/ + cargo xtask lint + ``` + +* Verify that unit tests are passing locally (see + [Unit Testing](https://bpfman.io/main/developer-guide/testing/#unit-testing)): + + ```console + cd bpfman/ + cargo xtask unit-test + ``` + +* Verify any changes to the bpfman API have been "blessed". + After running the below command, any changes to any of the files in + `bpfman/xtask/public-api/*.txt` indicate changes to the bpfman API. + Verify that these changes were intentional. + CI uses the latest nightly Rust toolchain, so make sure the public-apis + are verified against latest. + + ```console + cd bpfman/ + rustup update nightly + cargo +nightly xtask public-api --bless + ``` * Verify that integration tests are passing locally (see - [Basic Integration Tests](https://bpfman.io/developer-guide/testing/#basic-integration-tests)): + [Basic Integration Tests](https://bpfman.io/main/developer-guide/testing/#basic-integration-tests)): -```console -cd src/bpfman/ -cargo xtask integration-test -``` + ```console + cd bpfman/ + cargo xtask integration-test + ``` -* If developing the bpfman-operator, verify that bpfman-operator unit tests +* If developing the bpfman-operator, verify that bpfman-operator unit and integration tests are passing locally: - -```console -cd src/bpfman/bpfman-operator/ -make test -``` - -* If developing the bpfman-operator, verify that bpfman-operator integration tests - are passing locally (see - [Kubernetes Integration Tests](https://bpfman.io/developer-guide/testing/#kubernetes-integration-tests)): - -```console -cd src/bpfman/bpfman-operator/ -make test-integration -``` + + See [Kubernetes Operator Tests](https://bpfman.io/main/developer-guide/testing/#kubernetes-operator-tests). diff --git a/Cargo.lock b/Cargo.lock index 4497f9424..c358c009b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -29,9 +39,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -40,9 +50,20 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.6" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", @@ -52,9 +73,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -82,9 +103,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -96,49 +117,49 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "assert_cmd" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" +checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" dependencies = [ "anstyle", "bstr", @@ -155,6 +176,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-recursion" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -174,25 +206,25 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "axum" @@ -241,12 +273,13 @@ dependencies = [ [[package]] name = "aya" -version = "0.11.0" -source = "git+https://github.com/aya-rs/aya?branch=main#33b2e45ad313f7468a7d11667f13d9c365e2a263" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90eea657cc8028447cbda5068f4e10c4fadba0131624f4f7dd1a9c46ffc8d81f" dependencies = [ "assert_matches", "aya-obj", - "bitflags 2.4.1", + "bitflags 2.5.0", "bytes", "lazy_static", "libc", @@ -258,11 +291,12 @@ dependencies = [ [[package]] name = "aya-obj" version = "0.1.0" -source = "git+https://github.com/aya-rs/aya?branch=main#33b2e45ad313f7468a7d11667f13d9c365e2a263" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c02024a307161cf3d1f052161958fd13b1a33e3e038083e58082c0700fdab85" dependencies = [ "bytes", "core-error", - "hashbrown 0.14.2", + "hashbrown 0.14.3", "log", "object", "thiserror", @@ -270,9 +304,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -297,9 +331,15 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -315,9 +355,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block-buffer" @@ -337,12 +377,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bpf-log-exporter" +version = "0.4.0" +dependencies = [ + "anyhow", + "env_logger", + "futures", + "hex", + "log", + "netlink-packet-audit", + "netlink-packet-core", + "netlink-sys", + "regex", +] + [[package]] name = "bpf-metrics-exporter" -version = "0.1.0" +version = "0.4.0" dependencies = [ "anyhow", "aya", + "bpfman", + "bpfman-api", "chrono", "clap", "opentelemetry", @@ -355,15 +412,14 @@ dependencies = [ [[package]] name = "bpfman" -version = "0.3.1" +version = "0.4.0" dependencies = [ "anyhow", "assert_matches", "async-trait", "aya", "base16ct", - "base64 0.21.5", - "bpfman-api", + "base64 0.22.1", "bpfman-csi", "caps", "chrono", @@ -373,15 +429,18 @@ dependencies = [ "flate2", "futures", "hex", + "lazy_static", "log", "netlink-packet-route", - "nix 0.27.1", + "nix 0.28.0", "oci-distribution", + "rand", "rtnetlink", "serde", "serde_json", "sha2", "sigstore", + "sled", "systemd-journal-logger", "tar", "tempfile", @@ -389,25 +448,52 @@ dependencies = [ "tokio", "tokio-stream", "toml", - "tonic 0.10.2", + "tonic", "tower", "url", - "users", ] [[package]] name = "bpfman-api" -version = "0.3.1" +version = "0.4.0" dependencies = [ + "anyhow", + "async-trait", "aya", + "base16ct", + "base64 0.22.1", + "bpfman", + "bpfman-csi", + "caps", + "chrono", "clap", + "env_logger", + "flate2", + "futures", + "hex", + "lazy_static", + "libsystemd", "log", - "prost 0.12.3", + "netlink-packet-route", + "nix 0.28.0", + "oci-distribution", + "prost", + "rand", + "rtnetlink", "serde", + "serde_json", + "sha2", + "sigstore", + "sled", + "systemd-journal-logger", + "tar", + "tempfile", "thiserror", "tokio", + "tokio-stream", "toml", - "tonic 0.10.2", + "tonic", + "tower", "url", ] @@ -415,16 +501,29 @@ dependencies = [ name = "bpfman-csi" version = "1.8.0" dependencies = [ - "prost 0.12.3", + "prost", "prost-types", - "tonic 0.10.2", + "tonic", +] + +[[package]] +name = "bpfman-ns" +version = "0.4.0" +dependencies = [ + "anyhow", + "aya", + "caps", + "clap", + "env_logger", + "log", + "nix 0.28.0", ] [[package]] name = "bstr" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "regex-automata", @@ -433,9 +532,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -445,21 +544,22 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cached" -version = "0.44.0" +version = "0.49.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b195e4fbc4b6862bbd065b991a34750399c119797efff72492f28a5864de8700" +checksum = "8e8e463fceca5674287f32d252fb1d94083758b8709c160efae66d263e5f4eba" dependencies = [ + "ahash 0.8.11", "async-trait", "cached_proc_macro", "cached_proc_macro_types", "futures", - "hashbrown 0.13.2", + "hashbrown 0.14.3", "instant", "once_cell", "thiserror", @@ -468,12 +568,11 @@ dependencies = [ [[package]] name = "cached_proc_macro" -version = "0.17.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b48814962d2fd604c50d2b9433c2a41a0ab567779ee2c02f7fba6eca1221f082" +checksum = "ad9f16c0d84de31a2ab7fdf5f7783c14631f7075cf464eb3bb43119f61c9cb2a" dependencies = [ - "cached_proc_macro_types", - "darling", + "darling 0.14.4", "proc-macro2", "quote", "syn 1.0.109", @@ -481,9 +580,18 @@ dependencies = [ [[package]] name = "cached_proc_macro_types" -version = "0.1.0" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" + +[[package]] +name = "camino" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] [[package]] name = "caps" @@ -495,6 +603,40 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cargo-manifest" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6ff49a028a52bf61913e19f5ba3b9bd34145cc97eb1649865576c8f06b408d" +dependencies = [ + "serde", + "thiserror", + "toml", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cbc" version = "0.1.2" @@ -506,12 +648,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" [[package]] name = "cesu8" @@ -525,11 +664,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -537,7 +682,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.4", ] [[package]] @@ -553,9 +698,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.8" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", @@ -563,33 +708,52 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.8" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", +] + +[[package]] +name = "clap_complete" +version = "4.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbca90c87c2a04da41e95d1856e8bcd22f159bdbfa147314d2ce5218057b0e58" +dependencies = [ + "clap", ] [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "clap_mangen" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "74b70fc13e60c0e1d490dc50eb73a749be6d81f4ef03783df1d9b7b0c62bc937" +dependencies = [ + "clap", + "roff", +] [[package]] name = "colorchoice" @@ -599,9 +763,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "combine" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", @@ -609,9 +773,9 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" +checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ "crossterm", "strum", @@ -619,11 +783,24 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-error" @@ -636,9 +813,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -646,38 +823,53 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "crossterm" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "crossterm_winapi", "libc", - "parking_lot", + "parking_lot 0.12.1", "winapi", ] @@ -692,9 +884,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f85c3514d2a6e64160359b45a3918c3b4178bcbf4ae5d03ab2d02e521c479a" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core", @@ -730,9 +922,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -753,7 +945,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] @@ -762,8 +954,18 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core 0.20.8", + "darling_macro 0.20.8", ] [[package]] @@ -776,32 +978,63 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn 1.0.109", ] +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 2.0.58", +] + [[package]] name = "darling_macro" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ - "darling_core", + "darling_core 0.14.4", "quote", "syn 1.0.109", ] +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core 0.20.8", + "quote", + "syn 2.0.58", +] + [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "decoded-char" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "5440d1dc8ea7cae44cda3c64568db29bfa2434aba51ae66a50c00488841a65a3" [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "der_derive", @@ -818,9 +1051,36 @@ checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "thiserror", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "difflib" version = "0.4.0" @@ -847,15 +1107,15 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest", @@ -877,9 +1137,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", @@ -892,15 +1152,15 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "elliptic-curve" -version = "0.13.7" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9775b22bc152ad86a0cf23f0f348b884b26add12bf741e7ffc4d4ab2ab4d205" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", @@ -917,11 +1177,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -932,18 +1198,28 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", +] + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", ] [[package]] name = "env_logger" -version = "0.10.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ + "env_filter", "log", ] @@ -955,19 +1231,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "ff" @@ -981,20 +1257,20 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a56f0780318174bad1c127063fd0c5fdfb35398e3cd79ffaab931a6c79df80" +checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", ] [[package]] @@ -1005,15 +1281,15 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flagset" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a7e408202050813e6f1d9addadcaafef3dca7530c7ddfb005d4081cce6779" +checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "libz-sys", @@ -1027,19 +1303,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "form_urlencoded" -version = "1.2.1" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1052,9 +1353,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1062,15 +1363,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1079,38 +1380,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1124,6 +1425,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1137,9 +1447,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -1148,9 +1458,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -1160,15 +1470,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", - "fnv", "log", - "regex", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1184,9 +1494,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.22" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -1194,7 +1504,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.1.0", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1202,24 +1512,27 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "hashbag" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "98f494b2060b2a8f5e63379e1e487258e014cee1b1725a735816c0107a2e9d93" [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash", + "ahash 0.8.11", "allocator-api2", ] @@ -1229,11 +1542,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1241,11 +1560,56 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hickory-proto" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.4.0", + "ipnet", + "once_cell", + "rand", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot 0.12.1", + "rand", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -1261,11 +1625,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1281,9 +1645,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1292,18 +1656,18 @@ dependencies = [ [[package]] name = "http-auth" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5430cacd7a1f9a02fbeb350dfc81a0e5ed42d81f3398cb0ba184017f85bdcfbc" +checksum = "643c9bbf6a4ea8a656d6b4cd53d34f79e3f841ad5203c1a55fb7d761923bc255" dependencies = [ "memchr", ] [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -1324,9 +1688,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -1339,7 +1703,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -1372,11 +1736,24 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1429,16 +1806,18 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", + "serde", ] [[package]] @@ -1466,7 +1845,7 @@ version = "0.1.0" dependencies = [ "anyhow", "assert_cmd", - "bpfman-api", + "bpfman", "env_logger", "integration-test-macros", "inventory", @@ -1481,14 +1860,14 @@ name = "integration-test-macros" version = "0.1.0" dependencies = [ "quote", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] name = "inventory" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0508c56cfe9bfd5dfeb0c22ab9a6abfda2f27bdca422132e494266351ed8d83c" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" [[package]] name = "ipconfig" @@ -1496,7 +1875,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.5", + "socket2", "widestring", "windows-sys 0.48.0", "winreg", @@ -1510,27 +1889,18 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" @@ -1556,13 +1926,44 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-number" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c54d19ae7e6fc83aafa649707655a9a0ac956a0f62793bde4cfd193b0693fdf" +dependencies = [ + "lexical", + "ryu-js", + "serde", + "smallvec", +] + +[[package]] +name = "json-syntax" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe45447363747ecc18deb478f945df8482edafbae21e51bdc73eab76883c6a5" +dependencies = [ + "decoded-char", + "hashbrown 0.12.3", + "indexmap 1.9.3", + "json-number", + "locspan", + "locspan-derive", + "ryu-js", + "serde", + "smallstr", + "smallvec", + "utf8-decode", +] + [[package]] name = "jwt" version = "0.16.0" @@ -1579,28 +1980,92 @@ dependencies = [ ] [[package]] -name = "keccak" -version = "0.1.4" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "cpufeatures", + "spin", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "lexical" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7aefb36fd43fef7003334742cbf77b243fcd36418a1d1bdd480d613a67968f6" +dependencies = [ + "lexical-core", +] + +[[package]] +name = "lexical-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" +dependencies = [ + "lexical-parse-integer", + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-parse-integer" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" +dependencies = [ + "lexical-util", + "static_assertions", +] + +[[package]] +name = "lexical-util" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "lexical-write-float" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" dependencies = [ - "spin 0.5.2", + "lexical-util", + "lexical-write-integer", + "static_assertions", +] + +[[package]] +name = "lexical-write-integer" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" +dependencies = [ + "lexical-util", + "static_assertions", ] [[package]] name = "libc" -version = "0.2.150" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -1610,14 +2075,14 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libsystemd" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b9597a67aa1c81a6624603e6bd0bcefb9e0f94c9c54970ec53771082104b4e" +checksum = "c592dc396b464005f78a5853555b9f240bc5378bf5221acc4e129910b2678869" dependencies = [ "hmac", "libc", "log", - "nix 0.26.4", + "nix 0.27.1", "nom", "once_cell", "serde", @@ -1628,9 +2093,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.12" +version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" dependencies = [ "cc", "pkg-config", @@ -1645,9 +2110,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -1659,11 +2124,29 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "locspan" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33890449fcfac88e94352092944bf321f55e5deb4e289a6f51c87c55731200a0" + +[[package]] +name = "locspan-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88991223b049a3d29ca1f60c05639581336a0f3ee4bf8a659dddecc11c4961a" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" dependencies = [ "value-bag", ] @@ -1698,36 +2181,17 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" - -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -1746,18 +2210,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", @@ -1766,9 +2230,27 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.3" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + +[[package]] +name = "native-tls" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] [[package]] name = "ndk-context" @@ -1776,6 +2258,21 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +[[package]] +name = "netlink-packet-audit" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2754802fc8722062c38128d0c5dffd86fe4462877db60e736ab58b0daf5144" +dependencies = [ + "anyhow", + "byteorder", + "bytes", + "log", + "netlink-packet-core", + "netlink-packet-utils", + "netlink-proto", +] + [[package]] name = "netlink-packet-core" version = "0.7.0" @@ -1789,14 +2286,14 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.17.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +checksum = "74c171cd77b4ee8c7708da746ce392440cb7bcf618d122ec9ecc607b12938bf4" dependencies = [ "anyhow", - "bitflags 1.3.2", "byteorder", "libc", + "log", "netlink-packet-core", "netlink-packet-utils", ] @@ -1815,9 +2312,9 @@ dependencies = [ [[package]] name = "netlink-proto" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842c6770fc4bb33dd902f41829c61ef872b8e38de1405aa0b938b27b8fba12c3" +checksum = "86b33524dc0968bfad349684447bfce6db937a9ac3332a1fe60c0c5a5ce63f21" dependencies = [ "bytes", "futures", @@ -1830,9 +2327,9 @@ dependencies = [ [[package]] name = "netlink-sys" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" +checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" dependencies = [ "bytes", "futures", @@ -1843,26 +2340,27 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.4" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "cfg-if", "libc", - "memoffset 0.7.1", + "memoffset", ] [[package]] name = "nix" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "cfg-if", + "cfg_aliases", "libc", - "memoffset 0.9.0", + "memoffset", ] [[package]] @@ -1888,26 +2386,30 @@ dependencies = [ "num-iter", "num-traits", "rand", - "serde", "smallvec", "zeroize", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", "num-integer", @@ -1916,9 +2418,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -1945,20 +2447,21 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "oci-distribution" -version = "0.9.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac5b780ce1bd6c3c2ff72a3013f4b2d56d53ae03b20d424e99d2f6556125138" +checksum = "2a635cabf7a6eb4e5f13e9e82bd9503b7c2461bf277132e38638a935ebd684b4" dependencies = [ - "futures", + "bytes", + "chrono", "futures-util", "http", "http-auth", @@ -1972,20 +2475,10 @@ dependencies = [ "sha2", "thiserror", "tokio", - "tokio-util", "tracing", "unicase", ] -[[package]] -name = "oid" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2" -dependencies = [ - "serde", -] - [[package]] name = "olpc-cjson" version = "0.1.3" @@ -1999,25 +2492,68 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] [[package]] name = "opentelemetry" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" +checksum = "900d57987be3f2aeb70d385fff9b27fb74c5723cc9a52d904d4f9c807a0667bf" dependencies = [ "futures-core", "futures-sink", - "indexmap 2.1.0", "js-sys", "once_cell", "pin-project-lite", @@ -2027,9 +2563,9 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f24cda83b20ed2433c68241f918d0f6fdec8b1d43b7a9590ab4420c5095ca930" +checksum = "1a016b8d9495c639af2145ac22387dcb88e44118e45320d9238fbf4e7889abcb" dependencies = [ "async-trait", "futures-core", @@ -2038,38 +2574,35 @@ dependencies = [ "opentelemetry-proto", "opentelemetry-semantic-conventions", "opentelemetry_sdk", - "prost 0.11.9", + "prost", "thiserror", "tokio", - "tonic 0.9.2", + "tonic", ] [[package]] name = "opentelemetry-proto" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2e155ce5cc812ea3d1dffbd1539aed653de4bf4882d60e6e04dcf0901d674e1" +checksum = "3a8fddc9b68f5b80dae9d6f510b88e02396f006ad48cac349411fbecc80caae4" dependencies = [ "opentelemetry", "opentelemetry_sdk", - "prost 0.11.9", - "tonic 0.9.2", + "prost", + "tonic", ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5774f1ef1f982ef2a447f6ee04ec383981a3ab99c8e77a1a7b30182e65bbc84" -dependencies = [ - "opentelemetry", -] +checksum = "f9ab5bd6c42fb9349dcf28af2ba9a0667f697f9bdcca045d39f2cec5543e2910" [[package]] name = "opentelemetry_sdk" -version = "0.21.1" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968ba3f2ca03e90e5187f5e4f46c791ef7f2c163ae87789c8ce5f5ca3b7b7de5" +checksum = "9e90c7113be649e31e9a0f8b5ee24ed7a16923b322c3c5ab6367469c049d6b7e" dependencies = [ "async-trait", "futures-channel", @@ -2086,9 +2619,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "4.1.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "536900a8093134cf9ccf00a27deb3532421099e958d9dd431135d0c7543ca1e8" +checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" dependencies = [ "num-traits", ] @@ -2117,6 +2650,17 @@ dependencies = [ "sha2", ] +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -2124,7 +2668,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -2157,24 +2715,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" -[[package]] -name = "path-absolutize" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5" -dependencies = [ - "path-dedot", -] - -[[package]] -name = "path-dedot" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397" -dependencies = [ - "once_cell", -] - [[package]] name = "pbkdf2" version = "0.12.2" @@ -2187,20 +2727,11 @@ dependencies = [ [[package]] name = "pem" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" -dependencies = [ - "base64 0.13.1", -] - -[[package]] -name = "pem" -version = "2.0.1" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b13fe415cdf3c8e44518e18a7c95a13431d9bdf6d15367d82b23c377fdd441a" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.21.5", + "base64 0.22.1", "serde", ] @@ -2226,100 +2757,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.1.0", -] - -[[package]] -name = "picky" -version = "7.0.0-rc.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cccdaffd2f361b4b4eb70b4249bd71d89bb66cb84b7f76483ecd1640c543ce" -dependencies = [ - "base64 0.21.5", - "digest", - "ed25519-dalek", - "md-5", - "num-bigint-dig", - "p256", - "p384", - "picky-asn1", - "picky-asn1-der", - "picky-asn1-x509", - "rand", - "rand_core", - "rsa", - "serde", - "sha1", - "sha2", - "sha3", - "thiserror", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "picky-asn1" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295eea0f33c16be21e2a98b908fdd4d73c04dd48c8480991b76dbcf0cb58b212" -dependencies = [ - "oid", - "serde", - "serde_bytes", - "zeroize", -] - -[[package]] -name = "picky-asn1-der" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df7873a9e36d42dadb393bea5e211fe83d793c172afad5fb4ec846ec582793f" -dependencies = [ - "picky-asn1", - "serde", - "serde_bytes", -] - -[[package]] -name = "picky-asn1-x509" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5f20f71a68499ff32310f418a6fad8816eac1a2859ed3f0c5c741389dd6208" -dependencies = [ - "base64 0.21.5", - "num-bigint-dig", - "oid", - "picky-asn1", - "picky-asn1-der", - "serde", - "zeroize", + "indexmap 2.2.6", ] [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2367,15 +2832,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "platforms" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "poly1305" @@ -2388,6 +2853,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2396,13 +2867,12 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "predicates" -version = "3.0.4" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" dependencies = [ "anstyle", "difflib", - "itertools 0.11.0", "predicates-core", ] @@ -2424,12 +2894,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] @@ -2442,89 +2912,102 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.69" +name = "proc-macro-error" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ - "unicode-ident", + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", ] [[package]] -name = "prost" -version = "0.11.9" +name = "proc-macro-error-attr" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "bytes", - "prost-derive 0.11.9", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", ] [[package]] name = "prost" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.3", + "prost-derive", ] [[package]] name = "prost-build" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac" +checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" dependencies = [ "bytes", - "heck", - "itertools 0.11.0", + "heck 0.5.0", + "itertools", "log", "multimap", "once_cell", "petgraph", "prettyplease", - "prost 0.12.3", + "prost", "prost-types", "regex", - "syn 2.0.39", + "syn 2.0.58", "tempfile", - "which", ] [[package]] name = "prost-derive" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.58", ] [[package]] -name = "prost-derive" -version = "0.12.3" +name = "prost-types" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "anyhow", - "itertools 0.11.0", - "proc-macro2", - "quote", - "syn 2.0.39", + "prost", ] [[package]] -name = "prost-types" -version = "0.12.2" +name = "public-api" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8339f32236f590281e2f6368276441394fcd1b2133b549cc895d0ae80f2f9a52" +checksum = "b759eeee807ac96096fce568abeb9ef8d23db7be771a65252b1e5093fe7938c4" dependencies = [ - "prost 0.12.3", + "hashbag", + "rustdoc-types", + "serde", + "serde_json", + "thiserror", ] [[package]] @@ -2535,9 +3018,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -2580,9 +3063,9 @@ checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags 1.3.2", ] @@ -2598,9 +3081,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -2610,9 +3093,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -2621,30 +3104,33 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", + "hickory-resolver", "http", "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -2653,12 +3139,13 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", "tower-service", - "trust-dns-resolver", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -2690,38 +3177,30 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.20" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", + "getrandom", "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", + "spin", + "untrusted", + "windows-sys 0.52.0", ] [[package]] -name = "ring" -version = "0.17.5" +name = "roff" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" -dependencies = [ - "cc", - "getrandom", - "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.48.0", -] +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" [[package]] name = "rsa" -version = "0.9.3" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ "const-oid", "digest", @@ -2739,9 +3218,9 @@ dependencies = [ [[package]] name = "rtnetlink" -version = "0.13.1" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" +checksum = "b684475344d8df1859ddb2d395dd3dac4f8f3422a1aa0725993cb375fc5caba5" dependencies = [ "futures", "log", @@ -2750,7 +3229,7 @@ dependencies = [ "netlink-packet-utils", "netlink-proto", "netlink-sys", - "nix 0.26.4", + "nix 0.27.1", "thiserror", "tokio", ] @@ -2770,28 +3249,50 @@ dependencies = [ "semver", ] +[[package]] +name = "rustdoc-json" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21e727474edad247ba206da91e5d3f76c0ff4a4ddac1fb7bba508df49101388" +dependencies = [ + "cargo-manifest", + "cargo_metadata", + "serde", + "thiserror", + "toml", +] + +[[package]] +name = "rustdoc-types" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc646f09069d689083b975698bdac4edb96c2f0e7d270b701760814b886bb224" +dependencies = [ + "serde", +] + [[package]] name = "rustix" -version = "0.38.24" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.8" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", - "ring 0.17.5", - "rustls-webpki", + "ring", + "rustls-webpki 0.101.7", "sct", ] @@ -2801,30 +3302,62 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", ] +[[package]] +name = "rustls-pki-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + [[package]] name = "rustls-webpki" version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.5", - "untrusted 0.9.0", + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustup-toolchain" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64406f60467f189c326fc2e7f74ee643c28d1ff500068e0fa0f5e56a04e2cc95" +dependencies = [ + "thiserror", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "ryu-js" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f" [[package]] name = "salsa20" @@ -2844,6 +3377,58 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "schemafy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9725c16a64e85972fcb3630677be83fef699a1cd8e4bfbdcf3b3c6675f838a19" +dependencies = [ + "Inflector", + "schemafy_core", + "schemafy_lib", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "syn 1.0.109", +] + +[[package]] +name = "schemafy_core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bec29dddcfe60f92f3c0d422707b8b56473983ef0481df8d5236ed3ab8fdf24" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "schemafy_lib" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3d87f1df246a9b7e2bfd1f4ee5f88e48b11ef9cfc62e63f0dead255b1a6f5f" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "schemafy_core", + "serde", + "serde_derive", + "serde_json", + "syn 1.0.109", + "uriparse", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2868,8 +3453,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.5", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -2887,45 +3472,62 @@ dependencies = [ ] [[package]] -name = "semver" -version = "1.0.20" +name = "security-framework" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] [[package]] -name = "serde" -version = "1.0.193" +name = "security-framework-sys" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ - "serde_derive", + "core-foundation-sys", + "libc", ] [[package]] -name = "serde_bytes" -version = "0.11.12" +name = "semver" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" dependencies = [ "serde", ] +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -2941,11 +3543,22 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "serde_spanned" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -2962,6 +3575,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +dependencies = [ + "base64 0.21.7", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.6", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +dependencies = [ + "darling 0.20.8", + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2985,14 +3628,10 @@ dependencies = [ ] [[package]] -name = "sha3" -version = "0.10.8" +name = "shell-words" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest", - "keccak", -] +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "signal-hook-registry" @@ -3005,9 +3644,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core", @@ -3015,12 +3654,12 @@ dependencies = [ [[package]] name = "sigstore" -version = "0.7.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a306742205ee5e287f0c0cbb8f8361f6eda60a67232860c9e285778f001680fa" +checksum = "86debd0b4c8afb24efa09179ccba9da5daf9bbc7a4b786f3fb7ea26214bdfaf7" dependencies = [ "async-trait", - "base64 0.21.5", + "base64 0.22.1", "cached", "cfg-if", "chrono", @@ -3031,26 +3670,34 @@ dependencies = [ "ed25519", "ed25519-dalek", "elliptic-curve", + "futures", + "futures-util", "getrandom", + "hex", + "json-syntax", "lazy_static", "oci-distribution", "olpc-cjson", "p256", "p384", - "pem 2.0.1", - "picky", + "pem", "pkcs1", "pkcs8", "rand", "regex", "rsa", + "rustls-webpki 0.102.2", "scrypt", "serde", "serde_json", + "serde_repr", + "serde_with", "sha2", "signature", + "sigstore_protobuf_specs", "thiserror", "tokio", + "tokio-util", "tough", "tracing", "url", @@ -3060,68 +3707,91 @@ dependencies = [ ] [[package]] -name = "slab" -version = "0.4.9" +name = "sigstore_protobuf_specs" +version = "0.1.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54c3284a3ed53bd585dfbbe80b81142ad35128d7cba817623c4e066a4a95a2b" +dependencies = [ + "schemafy", + "schemafy_core", + "serde", + "serde_json", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", +] + +[[package]] +name = "smallstr" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "63b1aefdf380735ff8ded0b15f31aab05daf1f70216c01c02a12926badd1df9d" dependencies = [ - "autocfg", + "serde", + "smallvec", ] [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "snafu" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +checksum = "75976f4748ab44f6e5332102be424e7c2dc18daeaf7e725f2040c3ebb133512e" dependencies = [ - "doc-comment", + "futures-core", + "pin-project", "snafu-derive", ] [[package]] name = "snafu-derive" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +checksum = "b4b19911debfb8c2fb1107bc6cb2d61868aaf53a988449213959bb1b5b1ed95f" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.58", ] [[package]] name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -3130,37 +3800,49 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" [[package]] name = "strum_macros" -version = "0.25.3" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] @@ -3182,9 +3864,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -3220,19 +3902,19 @@ dependencies = [ [[package]] name = "systemd-journal-logger" -version = "1.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356b5cb52ce54916cbfaee19b07d305c7ea8ce5435a088c58743d4a0211f3eff" +checksum = "b5f3848dd723f2a54ac1d96da793b32923b52de8dfcced8722516dac312a5b2a" dependencies = [ - "libsystemd", "log", + "rustix", ] [[package]] name = "tar" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" dependencies = [ "filetime", "libc", @@ -3240,15 +3922,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.1", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3259,22 +3940,53 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", +] + +[[package]] +name = "time" +version = "0.3.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef89ece63debf11bc32d1ed8d078ac870cbeb44da02afb02a9ff135ae7ca0582" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", ] [[package]] @@ -3292,21 +4004,42 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls_codec" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "tokio" -version = "1.34.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", - "parking_lot", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", "windows-sys 0.48.0", ] @@ -3323,13 +4056,23 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ] [[package]] @@ -3344,9 +4087,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -3355,25 +4098,24 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", - "futures-io", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml" -version = "0.7.8" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -3382,20 +4124,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -3404,42 +4146,14 @@ dependencies = [ [[package]] name = "tonic" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" -dependencies = [ - "async-trait", - "axum", - "base64 0.21.5", - "bytes", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost 0.11.9", - "tokio", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" dependencies = [ "async-stream", "async-trait", "axum", - "base64 0.21.5", + "base64 0.21.7", "bytes", "h2", "http", @@ -3448,7 +4162,7 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.12.3", + "prost", "tokio", "tokio-stream", "tower", @@ -3459,40 +4173,47 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" +checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" dependencies = [ "prettyplease", "proc-macro2", "prost-build", "quote", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] name = "tough" -version = "0.13.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c259b2bd13fdff3305a5a92b45befb1adb315d664612c8991be57fb6a83dc126" +checksum = "b8d7a87d51ca5a113542e1b9f5ee2b14b6864bf7f34d103740086fa9c3d57d3b" dependencies = [ + "async-recursion", + "async-trait", + "bytes", "chrono", "dyn-clone", + "futures", + "futures-core", "globset", "hex", "log", "olpc-cjson", - "path-absolutize", - "pem 1.1.1", + "pem", "percent-encoding", "reqwest", - "ring 0.16.20", + "ring", "serde", "serde_json", "serde_plain", "snafu", "tempfile", - "untrusted 0.7.1", + "tokio", + "tokio-util", + "typed-path", + "untrusted", "url", "walkdir", ] @@ -3549,7 +4270,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] @@ -3562,56 +4283,16 @@ dependencies = [ ] [[package]] -name = "trust-dns-proto" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand", - "smallvec", - "thiserror", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.23.2" +name = "try-lock" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" -dependencies = [ - "cfg-if", - "futures-util", - "ipconfig", - "lru-cache", - "once_cell", - "parking_lot", - "rand", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "tracing", - "trust-dns-proto", -] +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "try-lock" -version = "0.2.4" +name = "typed-path" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "668404597c2c687647f6f8934f97c280fd500db28557f52b07c56b92d3dc500a" [[package]] name = "typenum" @@ -3630,9 +4311,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -3642,9 +4323,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -3667,21 +4348,25 @@ dependencies = [ [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] -name = "untrusted" -version = "0.9.0" +name = "uriparse" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna 0.5.0", @@ -3695,13 +4380,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] -name = "users" -version = "0.11.0" +name = "utf8-decode" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" -dependencies = [ - "libc", -] +checksum = "ca61eb27fa339aa08826a29f03e87b99b4d8f0fc2255306fd266bb1b6a9de498" [[package]] name = "utf8parse" @@ -3711,18 +4393,18 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.5.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "serde", ] [[package]] name = "value-bag" -version = "1.4.2" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72e1902dde2bd6441347de2b70b7f5d59bf157c6c62f0c44572607a1d55bbe" +checksum = "74797339c3b98616c009c7c3eb53a0ce41e85c8ec66bd3db96ed132d20cfdee8" [[package]] name = "vcpkg" @@ -3747,9 +4429,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -3772,9 +4454,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3782,24 +4464,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -3809,9 +4491,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3819,28 +4501,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-streams" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" dependencies = [ "futures-util", "js-sys", @@ -3851,9 +4533,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -3861,9 +4543,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b2391658b02c27719fc5a0a73d6e696285138e8b12fba9d4baa70451023c71" +checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" dependencies = [ "core-foundation", "home", @@ -3878,27 +4560,15 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" - -[[package]] -name = "which" -version = "4.4.2" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -3933,11 +4603,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.4", ] [[package]] @@ -3958,6 +4628,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -3988,6 +4667,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -4000,6 +4694,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -4012,6 +4712,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -4024,6 +4730,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -4036,6 +4748,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -4048,6 +4766,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -4060,6 +4784,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -4072,11 +4802,17 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + [[package]] name = "winnow" -version = "0.5.19" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" dependencies = [ "memchr", ] @@ -4091,27 +4827,18 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "x25519-dalek" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" -dependencies = [ - "curve25519-dalek", - "rand_core", - "serde", - "zeroize", -] - [[package]] name = "x509-cert" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eefca1d99701da3a57feb07e5079fc62abba059fc139e98c13bbb250f3ef29" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" dependencies = [ "const-oid", "der", + "sha1", + "signature", "spki", + "tls_codec", ] [[package]] @@ -4119,37 +4846,48 @@ name = "xtask" version = "0.1.0" dependencies = [ "anyhow", + "bpfman", + "bpfman-api", + "cargo_metadata", "clap", + "clap_complete", + "clap_mangen", + "dialoguer", + "diff", + "hex", "lazy_static", + "public-api", + "rustdoc-json", + "rustup-toolchain", "serde_json", "tonic-build", ] [[package]] name = "zerocopy" -version = "0.7.26" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.26" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", ] [[package]] name = "zeroize" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12a3946ecfc929b583800f4629b6c25b88ac6e92a40ea5670f77112a85d40a8b" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -4162,5 +4900,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.58", ] diff --git a/Cargo.toml b/Cargo.toml index 2825f6795..f575ea933 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,10 @@ [workspace] members = [ + "bpf-log-exporter", "bpf-metrics-exporter", "bpfman", "bpfman-api", + "bpfman-ns", "csi", "tests/integration-test", "tests/integration-test-macros", @@ -10,60 +12,89 @@ members = [ ] resolver = "2" -[patch.crates-io] -aya = { git = "https://github.com/aya-rs/aya", branch = "main" } +[workspace.package] +documentation = "https://docs.bpfman.io" +edition = "2021" +homepage = "https://bpfman.io" +license = "Apache-2.0" +repository = "https://github.com/bpfman/bpfman" +version = "0.4.0" [workspace.dependencies] anyhow = { version = "1", default-features = false } assert_cmd = { version = "2", default-features = false } assert_matches = { version = "1", default-features = false } async-trait = { version = "0.1", default-features = false } -aya = { version = "0.11", default-features = false } +aya = { version = "0.12", default-features = false } base16ct = { version = "0.2.0", default-features = false } -base64 = { version = "0.21.5", default-features = false } -bpfman-api = { version = "0.3.1", path = "./bpfman-api" } +base64 = { version = "0.22.1", default-features = false } +bpfman = { version = "0.4.0", path = "./bpfman" } +bpfman-api = { version = "0.4.0", path = "./bpfman-api" } bpfman-csi = { version = "1.8.0", path = "./csi" } caps = { version = "0.5.4", default-features = false } -chrono = { version = "0.4.31", default-features = false } +cargo_metadata = { version = "0.18.0", default-features = false } +chrono = { version = "0.4.38", default-features = false } clap = { version = "4", default-features = false } -comfy-table = { version = "7.1.0", default-features = false } -env_logger = { version = "0.10", default-features = false } +clap_complete = { version = "4.5.6", default-features = false } +clap_mangen = { version = "0.2.21", default-features = false } +comfy-table = { version = "7.1.1", default-features = false } +dialoguer = { version = "0.11", default-features = false } +diff = { version = "0.1.13", default-features = false } +env_logger = { version = "0.11.3", default-features = false } flate2 = { version = "1.0", default-features = false } -futures = { version = "0.3.29", default-features = false } +futures = { version = "0.3.30", default-features = false } hex = { version = "0.4.3", default-features = false } integration-test-macros = { path = "./tests/integration-test-macros" } inventory = { version = "0.3", default-features = false } lazy_static = { version = "1", default-features = false } +libsystemd = { version = "0.7.0", default-features = false } log = { version = "0.4", default-features = false } -netlink-packet-route = { version = "0.17.1", default-features = false } -nix = { version = "0.27", default-features = false } -oci-distribution = { version = "0.9", default-features = false } -opentelemetry = { version = "0.21.0", default-features = false } -opentelemetry-otlp = { version = "0.14.0", default-features = false } -opentelemetry-semantic-conventions = { version = "0.13.0" } -opentelemetry_sdk = { version = "0.21.1", default-features = false } +netlink-packet-audit = { version = "^0.5", default-features = false } +netlink-packet-core = { version = "^0.7", default-features = false } +netlink-packet-route = { version = "^0.19", default-features = false } +netlink-sys = { version = "^0.8", default-features = false } +nix = { version = "0.28", default-features = false } +oci-distribution = { version = "0.10", default-features = false } +opentelemetry = { version = "0.22.0", default-features = false } +opentelemetry-otlp = { version = "0.15.0", default-features = false } +opentelemetry-semantic-conventions = { version = "0.14.0" } +opentelemetry_sdk = { version = "0.22.1", default-features = false } predicates = { version = "3.0.4", default-features = false } -prost = { version = "0.12.3", default-features = false } -prost-types = { version = "0.12.2", default-features = false } +prost = { version = "0.12.6", default-features = false } +prost-types = { version = "0.12.6", default-features = false } +public-api = { version = "0.33.1", default-features = false } quote = { version = "1", default-features = false } rand = { version = "0.8", default-features = false } -regex = { version = "1.9.6", default-features = false } -rtnetlink = { version = "0.13.1", default-features = false } +regex = { version = "1.10.5", default-features = false } +rtnetlink = { version = "0.14", default-features = false } +rustdoc-json = { version = "0.8.9", default-features = false } +rustup-toolchain = { version = "0.1.6", default-features = false } serde = { version = "1.0", default-features = false } serde_json = { version = "1", default-features = false } sha2 = { version = "0.10.8", default-features = false } -sigstore = { version = "0.7.2", default-features = false } +sigstore = { version = "0.9.0", default-features = false } +sled = { version = "0.34.7", default-features = false } syn = { version = "2.0", default-features = false } -systemd-journal-logger = { version = "1.0.0", default-features = false } +systemd-journal-logger = { version = "2.1.1", default-features = false } tar = { version = "0.4", default-features = false } -tempfile = { version = "3.8.1", default-features = false } +tempfile = { version = "3.10.1", default-features = false } thiserror = { version = "1", default-features = false } -tokio = { version = "1.34.0", default-features = false } -tokio-stream = { version = "0.1.12", default-features = false } -tokio-util = { version = "0.7.10", default-features = false } -toml = { version = "0.7", default-features = false } -tonic = { version = "0.10.2", default-features = false } -tonic-build = { version = "0.10.2", default-features = false } +tokio = { version = "1.38.0", default-features = false } +tokio-stream = { version = "0.1.15", default-features = false } +tokio-util = { version = "0.7.11", default-features = false } +toml = { version = "0.8.14", default-features = false } +tonic = { version = "0.11.0", default-features = false } +tonic-build = { version = "0.11.0", default-features = false } tower = { version = "0.4.13", default-features = false } -url = { version = "2.5.0", default-features = false } +url = { version = "2.5.2", default-features = false } users = { version = "0.11.0", default-features = false } + + +[workspace.metadata.vendor-filter] +platforms = [ + "aarch64-unknown-linux-gnu", + "i686-unknown-linux-gnu", + "powerpc64le-unknown-linux-gnu", + "s390x-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", +] diff --git a/Containerfile.bpfman b/Containerfile.bpfman index 2a10b5d40..320e9eb12 100644 --- a/Containerfile.bpfman +++ b/Containerfile.bpfman @@ -8,20 +8,20 @@ RUN apt-get update && apt-get install -y\ protobuf-compiler\ libelf-dev\ gcc-multilib\ - musl-tools + libssl-dev WORKDIR /usr/src/bpfman COPY ./ /usr/src/bpfman -RUN rustup target add x86_64-unknown-linux-musl - # Compile only the C ebpf bytecode RUN cargo xtask build-ebpf --release --libbpf-dir /usr/src/bpfman/libbpf -# Compile only bpfman -RUN cargo build --release -p bpfman --target x86_64-unknown-linux-musl +# Compile bpfman cli, bpfman-ns, and bpfman-rpc binaries +RUN cargo build --release -FROM scratch +FROM redhat/ubi9-minimal -COPY --from=bpfman-build /usr/src/bpfman/target/x86_64-unknown-linux-musl/release/bpfman . +COPY --from=bpfman-build /usr/src/bpfman/target/release/bpfman . +COPY --from=bpfman-build /usr/src/bpfman/target/release/bpfman-ns . +COPY --from=bpfman-build /usr/src/bpfman/target/release/bpfman-rpc . -ENTRYPOINT ["./bpfman"] +ENTRYPOINT ["./bpfman-rpc", "--timeout=0"] diff --git a/Containerfile.bpfman.local b/Containerfile.bpfman.local index d10184266..13a68e56d 100644 --- a/Containerfile.bpfman.local +++ b/Containerfile.bpfman.local @@ -2,39 +2,32 @@ ## builds, dramatically speeding up the local development process. FROM rust:1 as bpfman-build -RUN git clone https://github.com/libbpf/libbpf --branch v0.8.0 /usr/src/bpfman/libbpf - RUN apt-get update && apt-get install -y\ - git\ - clang\ - protobuf-compiler\ - libelf-dev\ - gcc-multilib\ - musl-tools + gcc-multilib\ + libssl-dev WORKDIR /usr/src/bpfman COPY ./ /usr/src/bpfman -RUN rustup target add x86_64-unknown-linux-musl - -# Compile only the C ebpf bytecode +# Compile bpfman cli, bpfman-ns, and bpfman-rpc binaries RUN --mount=type=cache,target=/usr/src/bpfman/target/ \ --mount=type=cache,target=/usr/local/cargo/registry \ - cargo xtask build-ebpf --release --libbpf-dir /usr/src/bpfman/libbpf + cargo build --release -# Compile only bpfman RUN --mount=type=cache,target=/usr/src/bpfman/target/ \ - --mount=type=cache,target=/usr/local/cargo/registry \ - cargo build --release --target x86_64-unknown-linux-musl + cp /usr/src/bpfman/target/release/bpfman ./bpfman/ + +RUN --mount=type=cache,target=/usr/src/bpfman/target/ \ + cp /usr/src/bpfman/target/release/bpfman-ns ./bpfman/ RUN --mount=type=cache,target=/usr/src/bpfman/target/ \ - cp /usr/src/bpfman/target/x86_64-unknown-linux-musl/release/bpfman ./bpfman/ + cp /usr/src/bpfman/target/release/bpfman-rpc ./bpfman/ ## Image for Local testing is much more of a debug image, give it bpftool and tcpdump -FROM fedora:38 +FROM fedora:39 RUN dnf makecache --refresh && dnf -y install bpftool tcpdump COPY --from=bpfman-build ./usr/src/bpfman/bpfman . -ENTRYPOINT ["./bpfman", "system", "service"] +ENTRYPOINT ["./bpfman-rpc", "--timeout=0"] diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 775efc2d3..214fa3d31 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -2,7 +2,7 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for general contribution guidelines. See [GOVERNANCE.md](./GOVERNANCE.md) for governance guidelines and maintainer responsibilities. -See [CODEOWNERS](./CODEOWNERS) for a detailed list of owners for the various source directories. +See [CODEOWNERS](https://github.com/bpfman/bpfman/blob/main/CODEOWNERS) for a detailed list of owners for the various source directories. | Name | Employer | Responsibilities | | ---- | -------- | ---------------- | diff --git a/README.md b/README.md index a14195163..fdf065c8f 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,58 @@ -# bpfman: An eBPF Manager +![bpfman logo](./docs/img/bpfman_logo_256.png) -[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -[![License](https://img.shields.io/badge/License-BSD_2--Clause-orange.svg)](https://opensource.org/licenses/BSD-2-Clause) -[![License: GPL -v2](https://img.shields.io/badge/License-GPL_v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) -![Build status][build-badge] [![Book][book-badge]][book-url] -[![Netlify Status](https://api.netlify.com/api/v1/badges/557ca612-4b7f-480d-a1cc-43b453502992/deploy-status)](https://app.netlify.com/sites/bpfman/deploys) +# bpfman: An eBPF Manager -[build-badge]: - https://img.shields.io/github/actions/workflow/status/bpfman/bpfman/build.yml?branch=main +[![License][apache2-badge]][apache2-url] +[![License][bsd2-badge]][bsd2-url] +[![License][gpl-badge]][gpl-url] +![Build status][build-badge] +[![Book][book-badge]][book-url] +[![Netlify Status][netlify-badge]][netlify-url] +[![Copr build status][copr-badge]][copr-url] + +[apache2-badge]: https://img.shields.io/badge/License-Apache%202.0-blue.svg +[apache2-url]: https://opensource.org/licenses/Apache-2.0 +[bsd2-badge]: https://img.shields.io/badge/License-BSD%202--Clause-orange.svg +[bsd2-url]: https://opensource.org/licenses/BSD-2-Clause +[gpl-badge]: https://img.shields.io/badge/License-GPL%20v2-blue.svg +[gpl-url]: https://opensource.org/licenses/GPL-2.0 +[build-badge]: https://img.shields.io/github/actions/workflow/status/bpfman/bpfman/build.yml?branch=main [book-badge]: https://img.shields.io/badge/read%20the-book-9cf.svg [book-url]: https://bpfman.io/ +[copr-badge]: https://copr.fedorainfracloud.org/coprs/g/ebpf-sig/bpfman-next/package/bpfman/status_image/last_build.png +[copr-url]: https://copr.fedorainfracloud.org/coprs/g/ebpf-sig/bpfman-next/package/bpfman/ +[netlify-badge]: https://api.netlify.com/api/v1/badges/557ca612-4b7f-480d-a1cc-43b453502992/deploy-status +[netlify-url]: https://app.netlify.com/sites/bpfman/deploys _Formerly know as `bpfd`_ ## Welcome to bpfman -bpfman is a system daemon aimed at simplifying the deployment and management of eBPF programs. -It's goal is to enhance the developer-experience as well as provide features to improve security, -visibility and program-cooperation. -bpfman includes a Kubernetes operator to bring those same features to Kubernetes, allowing users to -safely deploy eBPF via custom resources across nodes in a cluster. +bpfman operates as an eBPF manager, focusing on simplifying the deployment and administration of eBPF programs. Its notable features encompass: + +- **System Overview**: Provides insights into how eBPF is utilized in your system. +- **eBPF Program Loader**: Includes a built-in program loader that supports program cooperation for XDP and TC programs, as well as deployment of eBPF programs from OCI images. +- **eBPF Filesystem Management**: Manages the eBPF filesystem, facilitating the deployment of eBPF applications without requiring additional privileges. + +Our program loader and eBPF filesystem manager ensure the secure deployment of eBPF applications. +Furthermore, bpfman includes a Kubernetes operator, extending these capabilities to Kubernetes. +This allows users to confidently deploy eBPF through custom resource definitions across nodes in a cluster. Here are some links to help in your bpfman journey (all links are from the bpfman website ): - [Welcome to bpfman](https://bpfman.io/) for overview of bpfman. -- [Setup and Building bpfman](https://bpfman.io/getting-started/building-bpfman/) for - instructions on setting up your development environment and building bpfman. -- [Tutorial](https://bpfman.io/getting-started/tutorial/) for some examples of starting - `bpfman`, managing logs, and using the CLI. -- [Example eBPF Programs](https://bpfman.io/getting-started/example-bpf/) for some +- [Deploying Example eBPF Programs On Local Host](https://bpfman.io/main/getting-started/example-bpf-local/) + for some examples of running `bpfman` on local host and using the CLI to install + eBPF programs on the host. +- [Deploying Example eBPF Programs On Kubernetes](https://bpfman.io/main/getting-started/example-bpf-k8s/) + for some examples of deploying eBPF programs through `bpfman` in a Kubernetes deployment. +- [Setup and Building bpfman](https://bpfman.io/main/getting-started/building-bpfman/) for instructions + on setting up your development environment and building bpfman. +- [Example eBPF Programs](https://bpfman.io/main/getting-started/example-bpf/) for some examples of eBPF programs written in Go, interacting with `bpfman`. -- [How to Deploy bpfman on Kubernetes](https://bpfman.io/developer-guide/develop-operator/) for details on launching +- [Deploying the bpfman-operator](https://bpfman.io/main/developer-guide/develop-operator/) for details on launching bpfman in a Kubernetes cluster. -- [Meet the Community](https://bpfman.io/governance/meetings/) for details on community meeting details. +- [Meet the Community](https://bpfman.io/main/governance/meetings/) for details on community meeting details. ## License @@ -65,3 +84,4 @@ dual licensed as above, without any additional terms or conditions. [GNU General Public License, Version 2]: LICENSE-GPL2 [BSD 2 Clause]: LICENSE-BSD2 [libxdp]: https://github.com/xdp-project/xdp-tools +[TC dispatcher]:https://github.com/bpfman/bpfman/blob/main/bpf/tc_dispatcher.bpf.c diff --git a/RELEASE.md b/RELEASE.md index e5bcc20fe..72884decb 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,49 +1,65 @@ # Release Process +This document describes how to cut a release for the bpfman project. + ## Overview A release for the bpfman project is comprised of the following major components: -- bpfman binaries -- Core GRPC API protobuf definitions -- Kubernetes Custom Resource Definitions (CRDs) -- Corresponding go pkg in the form of `github.com/bpfman/bpfman` which includes the following: - - `github.com/bpfman/bpfman/clients/gobpfman/v1`: The go client for the bpfman GRPC API - - `github.com/bpfman/bpfman/bpfman-operator/apis`: The go bindings for the - bpfman CRD API - - `github.com/bpfman/bpfman/bpfman-operator/pkg/client`: The autogenerated - clientset for the bpfman CRD API - - `github.com/bpfman/bpfman/bpfman-operator/pkg/helpers`: The provided bpfman CRD - API helpers. -- Corresponding `bpfman-api` and `bpfman`rust crates which house the rust client for the bpfman GRPC API +- bpfman (Core library) and bpfman-api (Core GRPC API protobuf definitions) library crates +- bpfman (CLI), and bpfman-rpc ( gRPC server ) binary crates +- bpf-metrics-exporter and bpf-log-exporter binary crates +- Kubernetes User Facing Custom Resource Definitions (CRDs) + - `TcProgram` + - `XdpProgram` + - `TracepointProgram` + - `UprobeProgram` + - `KprobeProgram` + - `FentryProgram` + - `FexitProgram` +- Corresponding go pkgs in the form of `github.com/bpfman/bpfman` which includes the following: + - `github.com/bpfman/bpfman/clients/gobpfman/v1`: The go client for the bpfman GRPC API + - `github.com/bpfman/bpfman/bpfman-operator/apis`: The go bindings for the + bpfman CRD API + - `github.com/bpfman/bpfman/bpfman-operator/pkg/client`: The autogenerated + clientset for the bpfman CRD API + - `github.com/bpfman/bpfman/bpfman-operator/pkg/helpers`: The provided bpfman CRD + API helpers. - The following core component container images with tag : - - `quay.io/bpfman/bpfman` - - `quay.io/bpfman/bpfman-operator` - - `quay.io/bpfman/bpfman-agent` - - `quay.io/bpfman/bpfman-operator-bundle` - - `quay.io/bpfman/xdp-dispatcher` - - `quay.io/bpfman/tc-dispatcher` + - `quay.io/bpfman/bpfman` + - `quay.io/bpfman/bpfman-operator` + - `quay.io/bpfman/bpfman-agent` + - `quay.io/bpfman/bpfman-operator-bundle` + - `quay.io/bpfman/xdp-dispatcher` + - `quay.io/bpfman/tc-dispatcher` - The relevant example bytecode container images with tag from source code located in the bpfman project: - - `quay.io/bpfman-bytecode/go_xdp_counter` - - `quay.io/bpfman-bytecode/go_tc_counter` - - `quay.io/bpfman-bytecode/go_tracepoint_counter` - - `quay.io/bpfman-bytecode/xdp_pass` - - `quay.io/bpfman-bytecode/tc_pass` - - `quay.io/bpfman-bytecode/tracepoint` - - `quay.io/bpfman-bytecode/xdp_pass_private` - - `quay.io/bpfman-bytecode/uprobe` - - `quay.io/bpfman-bytecode/kprobe` - - `quay.io/bpfman-bytecode/uretprobe` - - `quay.io/bpfman-bytecode/kretprobe` + - `quay.io/bpfman-bytecode/go-xdp-counter` + - `quay.io/bpfman-userspace/go-target` + - `quay.io/bpfman-bytecode/go-tc-counter` + - `quay.io/bpfman-bytecode/go-tracepoint-counter` + - `quay.io/bpfman-bytecode/xdp-pass` + - `quay.io/bpfman-bytecode/tc-pass` + - `quay.io/bpfman-bytecode/tracepoint` + - `quay.io/bpfman-bytecode/xdp-pass-private` + - `quay.io/bpfman-bytecode/go-uprobe-counter` + - `quay.io/bpfman-bytecode/go-kprobe-counter` + - `quay.io/bpfman-bytecode/uprobe` + - `quay.io/bpfman-bytecode/kprobe` + - `quay.io/bpfman-bytecode/uretprobe` + - `quay.io/bpfman-bytecode/kretprobe` + - `quay.io/bpfman-bytecode/fentry` + - `quay.io/bpfman-bytecode/fexit` - The relevant example userspace container images with tag from source code located in the bpfman project: - - `quay.io/bpfman-userspace/go_xdp_counter` - - `quay.io/bpfman-userspace/go_tc_counter` - - `quay.io/bpfman-userspace/go_tracepoint_counter` + - `quay.io/bpfman-userspace/go-xdp-counter` + - `quay.io/bpfman-userspace/go-tc-counter` + - `quay.io/bpfman-userspace/go-tracepoint-counter` + - `quay.io/bpfman-userspace/go-uprobe-counter` + - `quay.io/bpfman-userspace/go-kprobe-counter` - The OLM (Operator Lifecycle Manager) for the Kubernetes Operator. - - This includes a `bundle` directory on disk as well as the - `quay.io/bpfman/bpfman-operator-bundle` with the tag . + - This includes a `bundle` directory on disk as well as the + `quay.io/bpfman/bpfman-operator-bundle` with the tag . ## Versioning strategy @@ -94,21 +110,21 @@ For a **PATCH** release: `git checkout -b release-x.x.x ` - Create a pull request of the `/release-x.x.x` branch into the `release-x.x` branch upstream. Add a hold on this PR waiting for at least one maintainer/codeowner to provide a `lgtm`. This PR should: - - Add a new changelog for the release - - Update the cargo.toml versions for the bpfman-api and bpfman crates - - Update the bpfman-operator version in it's MAKEFILE and run `make bundle` to update the bundle version. - This will generate a new `/bpfman-operator/bundle` directory which will ONLY be tracked in the - `release-x.x` branch not `main`. + - Add a new changelog for the release + - Update the cargo.toml version for the workspace. + - Update the bpfman-operator version in it's MAKEFILE and run `make bundle` to update the bundle version. + This will generate a new `/bpfman-operator/bundle` directory which will ONLY be tracked in the + `release-x.x` branch not `main`. - Verify the CI tests pass and merge the PR into `release-x.x`. - Create a tag using the `HEAD` of the `release-x.x.x` branch. This can be done using the `git` CLI or Github's [release][release] page. - The Release will be automatically created, after that is complete do the following: - - run `make build-release-yamls` and attach the yamls for the version to the release. These will include: - - `bpfman-crds-install-vx.x.x.yaml` - - `bpfman-operator-install-vx.x.x.yaml` - - `go-xdp-counter-install-vx.x.x.yaml` - - `go-tc-counter-install-vx.x.x.yaml` - - `go-tracepoint-counter-install-vx.x.x.yaml` + - run `make build-release-yamls` and attach the yamls for the version to the release. These will include: + - `bpfman-crds-install.yaml` + - `bpfman-operator-install.yaml` + - `go-xdp-counter-install.yaml` + - `go-tc-counter-install.yaml` + - `go-tracepoint-counter-install.yaml` - Update the [community-operator](https://github.com/k8s-operatorhub/community-operators) and [community-operators-prod](https://github.com/redhat-openshift-ecosystem/community-operators-prod) repositories with the latest bundle manifests. See the following PRs as examples: @@ -118,10 +134,10 @@ For a **PATCH** release: For a **MAJOR** or **MINOR** release: - Open an update PR that: - - Adds a new changelog for the release - - Updates the cargo.toml versions for the bpfman-api and bpfman crates - - Updates the bpfman-operator version in it's MAKEFILE and run `make bundle` to update the bundle version - - Add's a new `examples` config directory for the release version + - Adds a new changelog for the release + - Updates the cargo.toml version for the workspace. + - Updates the bpfman-operator version in it's MAKEFILE and run `make bundle` to update the bundle version + - Add's a new `examples` config directory for the release version - Make sure CI is green and merge the update PR. - Create a tag using the `HEAD` of the `main` branch. This can be done using the `git` CLI or Github's [release][release] page. @@ -129,12 +145,12 @@ For a **MAJOR** or **MINOR** release: This can be done using the `git` CLI or Github's [release][release] page. - The Release will be automatically created, after that is complete do the following: - - run `make build-release-yamls` and attach the yamls for the version to the release. These will include: - - `bpfman-crds-install-vx.x.x.yaml` - - `bpfman-operator-install-vx.x.x.yaml` - - `go-xdp-counter-install-vx.x.x.yaml` - - `go-tc-counter-install-vx.x.x.yaml` - - `go-tracepoint-counter-install-vx.x.x.yaml` + - run `make build-release-yamls` and attach the yamls for the version to the release. These will include: + - `bpfman-crds-install.yaml` + - `bpfman-operator-install.yaml` + - `go-xdp-counter-install.yaml` + - `go-tc-counter-install.yaml` + - `go-tracepoint-counter-install.yaml` [release]: https://github.com/bpfman/bpfman/releases [bpfman-team]: https://github.com/bpfman/bpfman/blob/main/CODEOWNERS diff --git a/REVIEWING.md b/REVIEWING.md index 7a495a644..d296236e7 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -35,7 +35,7 @@ Be trustworthy. During a review, your actions both build and help maintain the t ## Process -* Reviewers are automatically assigned based on the [CODEOWNERS](./CODEOWNERS) file. +* Reviewers are automatically assigned based on the [CODEOWNERS](https://github.com/bpfman/bpfman/blob/main/CODEOWNERS) file. * Reviewers should wait for automated checks to pass before reviewing * At least 1 approved review is required from a maintainer before a pull request can be merged * All CI checks must pass diff --git a/bpf-log-exporter/Cargo.toml b/bpf-log-exporter/Cargo.toml new file mode 100644 index 000000000..8e2b1aecb --- /dev/null +++ b/bpf-log-exporter/Cargo.toml @@ -0,0 +1,20 @@ +[package] +description = "Binary for exporting eBPF events via logs" +documentation.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +name = "bpf-log-exporter" +repository.workspace = true +version.workspace = true + +[dependencies] +anyhow = { workspace = true } +env_logger = { workspace = true } +futures = { workspace = true } +hex = { workspace = true, features = ["alloc"] } +log = { workspace = true } +netlink-packet-audit = { workspace = true } +netlink-packet-core = { workspace = true } +netlink-sys = { workspace = true } +regex = { workspace = true } diff --git a/bpf-log-exporter/src/main.rs b/bpf-log-exporter/src/main.rs new file mode 100644 index 000000000..b88520975 --- /dev/null +++ b/bpf-log-exporter/src/main.rs @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use std::{cell::RefCell, num::NonZeroI32, str::from_utf8}; + +use anyhow::{anyhow, Context}; +use log::info; +use netlink_packet_audit::AuditMessage; +use netlink_packet_core::{NetlinkMessage, NetlinkPayload}; +use netlink_sys::{protocols::NETLINK_AUDIT, Socket, SocketAddr}; +use regex::Regex; + +fn main() -> anyhow::Result<()> { + env_logger::init(); + let audit = NetlinkAudit::new(); + audit + .recieve_loop() + .context("failed to receive audit events")?; + Ok(()) +} + +struct NetlinkAudit { + sock: RefCell, +} + +#[derive(Debug)] +struct LogMessage { + timestamp: String, + prog_id: u32, + op: String, + syscall_op: u32, + pid: u32, + uid: u32, + gid: u32, + comm: String, + cmdline: String, +} + +impl LogMessage { + fn new() -> Self { + LogMessage { + timestamp: String::new(), + prog_id: 0, + op: String::new(), + syscall_op: 0, + pid: 0, + uid: 0, + gid: 0, + comm: String::new(), + cmdline: String::new(), + } + } +} + +const NETLINK_GROUP_READ_LOG: u32 = 0x1; + +impl NetlinkAudit { + fn new() -> Self { + let mut sock = Socket::new(NETLINK_AUDIT).unwrap(); + sock.bind_auto().unwrap(); + sock.connect(&SocketAddr::new(0, 0)).unwrap(); + sock.add_membership(NETLINK_GROUP_READ_LOG) + .expect("failed to add membership"); + NetlinkAudit { + sock: RefCell::new(sock), + } + } + + fn recieve_loop(&self) -> anyhow::Result<()> { + let mut receive_buffer = vec![0; 4096]; + let socket = self.sock.borrow_mut(); + let is_hex = Regex::new(r"^[0-9A-F]+$").unwrap(); + let mut log_paylod = LogMessage::new(); + loop { + let n = socket.recv(&mut &mut receive_buffer[..], 0)?; + let bytes = &receive_buffer[..n]; + let rx_packet = >::deserialize(bytes).unwrap(); + match rx_packet.payload { + NetlinkPayload::InnerMessage(AuditMessage::Event((event_id, message))) => { + match event_id { + 1300 => { + // syscall event - 321 is sys_bpf + if message.contains("syscall=321") { + let parts: Vec<&str> = message.split_whitespace().collect(); + let a0 = parts + .iter() + .find(|s| s.starts_with("a0=")) + .unwrap() + .split('=') + .last() + .unwrap(); + let uid = parts + .iter() + .find(|s| s.starts_with("uid=")) + .expect("uid not found") + .split('=') + .last() + .unwrap(); + let pid = parts + .iter() + .find(|s| s.starts_with("pid=")) + .expect("pid not found") + .split('=') + .last() + .unwrap(); + let gid = parts + .iter() + .find(|s| s.starts_with("gid=")) + .expect("gid not found") + .split('=') + .last() + .unwrap(); + let comm = parts + .iter() + .find(|s| s.starts_with("comm=")) + .unwrap() + .split('=') + .last() + .unwrap(); + + log_paylod.syscall_op = a0.parse().unwrap(); + log_paylod.uid = uid.parse().unwrap(); + log_paylod.pid = pid.parse().unwrap(); + log_paylod.gid = gid.parse().unwrap(); + log_paylod.comm = comm.replace('"', "").to_string(); + } + } + 1334 => { + // ebpf event + let parts: Vec<&str> = message.split_whitespace().collect(); + let prog_id = parts + .iter() + .find(|s| s.starts_with("prog-id=")) + .unwrap() + .split('=') + .last() + .unwrap(); + let op = parts + .iter() + .find(|s| s.starts_with("op=")) + .unwrap() + .split('=') + .last() + .unwrap(); + log_paylod.prog_id = prog_id.parse().unwrap(); + log_paylod.op = op.to_string(); + } + 1327 => { + // proctitle emit event + let parts = message.split_whitespace().collect::>(); + let timestamp = parts + .iter() + .find(|s| s.starts_with("audit(")) + .unwrap() + .split(':') + .next() + .unwrap(); + log_paylod.timestamp = timestamp.replace("audit(", "").to_string(); + + let proctile = parts + .iter() + .find(|s| s.starts_with("proctitle=")) + .unwrap() + .split('=') + .last() + .unwrap(); + if is_hex.is_match(proctile) { + log_paylod.cmdline = from_utf8(&hex::decode(proctile).unwrap()) + .unwrap() + .replace('\0', " ") + .to_string(); + } else { + log_paylod.cmdline = proctile.replace('"', "").to_string(); + } + } + 1320 => { + info!("{:?}", log_paylod); + log_paylod = LogMessage::new(); + } + _ => {} + } + } + NetlinkPayload::InnerMessage(AuditMessage::Other(_)) => { + // discard messages that are not events + } + NetlinkPayload::Error(e) => { + if e.code == NonZeroI32::new(-17) { + break; + } else { + return Err(anyhow!(e)); + } + } + m => return Err(anyhow!("unexpected netlink message {:?}", m)), + } + } + Ok(()) + } +} diff --git a/bpf-metrics-exporter/Cargo.toml b/bpf-metrics-exporter/Cargo.toml index e7a30dacf..d7c75f9df 100644 --- a/bpf-metrics-exporter/Cargo.toml +++ b/bpf-metrics-exporter/Cargo.toml @@ -1,11 +1,16 @@ [package] -edition = "2021" +description = "Binary for exporting eBPF subsystem metrics via prometheus" +edition.workspace = true +license.workspace = true name = "bpf-metrics-exporter" -version = "0.1.0" +repository.workspace = true +version.workspace = true [dependencies] anyhow = { workspace = true } aya = { workspace = true } +bpfman = { workspace = true } +bpfman-api = { workspace = true } chrono = { workspace = true, features = ["std"] } clap = { workspace = true, features = [ "color", diff --git a/bpf-metrics-exporter/README.md b/bpf-metrics-exporter/README.md index 20dd8e393..86f998812 100644 --- a/bpf-metrics-exporter/README.md +++ b/bpf-metrics-exporter/README.md @@ -12,26 +12,77 @@ to correlate process IDs -> containers -> k8s pods. ## Metrics -The following metrics are exported: - -- `bpf_program_size_jitted_bytes`: The size of the BPF program in bytes. +The following metrics are currently exported, this list will continue to expand: + +### [Gauges](https://opentelemetry.io/docs/specs/otel/metrics/api/#gauge) + +- `bpf_program_info`: Information on each loaded BPF Program + - Labels: + - `id`: The ID of the BPF program + - `name`: The name of the BPF program + - `type`: The type of the BPF program as a readable string + - `tag`: The tag of the BPF program + - `gpl_compatible`: Whether the BPF program is GPL compatible + - `load_time`: The time the BPF program was loaded +- `bpf_map_info`: Information of each loaded BPF Map + - Labels: + - `id`: The ID of the BPF map + - `name`: The name of the BPF map + - `type`: The type of the BPF map as an `u32` which corresponds to the following [kernel enumeration](https://elixir.bootlin.com/linux/v6.6.3/source/include/uapi/linux/bpf.h#L906) + - `key_size`: The key size in bytes for the BPF map + - `value_size`: The value size for the BPF map + - `max_entries`: The maximum number of entries for the BPF map. + - `flags`: Loadtime specific flags for the BPF map +- `bpf_link_info`: Information on each of the loaded BPF Link + - Labels: + - `id`: The ID of the bpf Link + - `prog_id`: The Program ID of the BPF program which is using the Link. + - `type`: The BPF Link type as a `u32` which corresponds to the following [kernel enumeration](https://elixir.bootlin.com/linux/v6.6.3/source/include/uapi/linux/bpf.h#L1048) +- `bpf_program_load_time`: The standard UTC time the program was loaded in seconds + - Labels: + - `id`: The ID of the BPF program + - `name`: The name of the BPF program + - `type`: The type of the BPF program as a readable string + +### [Counters](https://opentelemetry.io/docs/specs/otel/metrics/api/#counter) + +**Note**: All counters will have the [suffix `_total` appended](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#counter-1). + +- `bpf_program_size_jitted_bytes`: The size in bytes of the program's JIT-compiled machine code. + - Labels: + - `id`: The ID of the BPF program + - `name`: The name of the BPF program + - `type`: The type of the BPF program as a readable string - `bpf_program_size_translated_bytes`: The size of the BPF program in bytes. + - Labels: + - `id`: The ID of the BPF program + - `name`: The name of the BPF program + - `type`: The type of the BPF program as a readable string - `bpf_program_mem_bytes`: The amount of memory used by the BPF program in bytes. -- `bpf_program_verified_instructions_total`: The number of instructions in the BPF program. - - -The following will be added pending: https://github.com/open-telemetry/opentelemetry-rust/issues/1242 - -- `bpf_programs_total`: The number of BPF programs loaded into the kernel. - -Labels: - -- `id`: The ID of the BPF program -- `name`: The name of the BPF program -- `type`: The type of the BPF program -- `tag`: The tag of the BPF program -- `gpl_compatible`: Whether the BPF program is GPL compatible -- `load_time`: The time the BPF program was loaded + - Labels: + - `id`: The ID of the BPF program + - `name`: The name of the BPF program + - `type`: The type of the BPF program as a readable string +- `bpf_program_verified_instructions`: The number of instructions in the BPF program. + - Labels: + - `id`: The ID of the BPF program + - `name`: The name of the BPF program + - `type`: The type of the BPF program as a readable string +- `bpf_map_key_size`: The size of the BPF map key + - Labels: + - `id`: The ID of the BPF map + - `name`: The name of the BPF map + - `type`: The type of the BPF map as an `u32` which corresponds to the following [kernel enumeration](https://elixir.bootlin.com/linux/v6.6.3/source/include/uapi/linux/bpf.h#L906) +- `bpf_map_value_size`: The size of the BPF map value + - Labels: + - `id`: The ID of the BPF map + - `name`: The name of the BPF map + - `type`: The type of the BPF map as an `u32` which corresponds to the following [kernel enumeration](https://elixir.bootlin.com/linux/v6.6.3/source/include/uapi/linux/bpf.h#L906) +- `bpf_map_max_entries`: The maximum number of entries allowed for the BPF map + - Labels: + - `id`: The ID of the BPF map + - `name`: The name of the BPF map + - `type`: The type of the BPF map as an `u32` which corresponds to the following [kernel enumeration](https://elixir.bootlin.com/linux/v6.6.3/source/include/uapi/linux/bpf.h#L906) ## Try it out @@ -44,9 +95,20 @@ podman play kube metrics-stack.yaml Then, you can deploy the exporter: -``` +```bash sudo ./target/debug/bpf-metrics-exporter ``` -You can log into grafana at http://localhost:3000/ with the credentials `admin:admin`. Once there, you can explore the metrics in the prometheus -datasource. +You can log into grafana at `http://localhost:3000/` using the default user:password +`admin:admin`. + +From there simply select the default dashboard titled `eBPF Subsystem Metrics`: + +![eBPF Subsystem Metrics](dashboard-example.png) + +In order to clean everything up simply exit the bpf-metrics-exporter process with +`ctrl+c` and run: + +```bash +podman kube down metrics-stack.yaml +``` diff --git a/bpf-metrics-exporter/bpf_traces.bt b/bpf-metrics-exporter/bpf_traces.bt new file mode 100755 index 000000000..13406c231 --- /dev/null +++ b/bpf-metrics-exporter/bpf_traces.bt @@ -0,0 +1,37 @@ +#!/usr/bin/env bpftrace + +kfunc:vmlinux:bpf_prog_load { + printf("Process %d is loading a %d bpf program at %s\n", pid, args->attr->prog_type, strftime("%H:%M:%S", nsecs)); +} + +kfunc:vmlinux:bpf_prog_new_fd { + printf("Process %d is loading a %d bpf program with id %d at %s\n", pid, args->prog->aux->saved_dst_prog_type, args->prog->aux->id, strftime("%H:%M:%S", nsecs)); +} + +kretfunc:vmlinux:bpf_prog_load { + if (retval != 0){ + printf("Process %d failed to load bpfprogram", pid) + } else { + printf("Process %d successfully loaded a bpfprogram\n", pid); + } +} + +kfunc:vmlinux:bpf_prog_free { + printf("Program %d released\n", args->fp->aux->id) +} + +kfunc:vmlinux:bpf_prog_free_id { + printf("Program %d unloaded", args->prog->aux->id) +} + +kretfunc:vmlinux:bpf_prog_free_id { + printf("Program %d unloaded", args->prog->aux->id) +} + +tracepoint:syscalls:sys_enter_bpf { + printf("Enter bpf with pid %d \n", pid) +} + +tracepoint:syscalls:sys_exit_bpf { + printf("Exit bpf with pid %d and ret %d \n", pid, args->ret) +} \ No newline at end of file diff --git a/bpf-metrics-exporter/dashboard-example.png b/bpf-metrics-exporter/dashboard-example.png new file mode 100644 index 000000000..2d5418ae1 Binary files /dev/null and b/bpf-metrics-exporter/dashboard-example.png differ diff --git a/bpf-metrics-exporter/metrics-stack.yaml b/bpf-metrics-exporter/metrics-stack.yaml index 28509c0df..9d60b5a81 100644 --- a/bpf-metrics-exporter/metrics-stack.yaml +++ b/bpf-metrics-exporter/metrics-stack.yaml @@ -88,6 +88,984 @@ data: receivers: [otlp] exporters: [prometheusremotewrite] --- +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-datasources + namespace: monitoring +data: + prometheus.yaml: |- + { + "apiVersion": 1, + "datasources": [ + { + "access":"proxy", + "editable": true, + "name": "prometheus", + "orgId": 1, + "type": "prometheus", + "url": "http://localhost:9009/prometheus", + "version": 1 + } + ] + } +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: grafana-dashboards + namespace: monitoring +data: + dashboard.yaml: |- + { + "apiVersion": 1, + "providers": [ + { + "name":"eBPF Subsystem Metrics", + "type":"file", + "options": { + "path": "/etc/grafana/provisioning/dashboards/ebpf-subsystem-metrics.json" + } + } + ] + } +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ebpf-subsystem-metrics-dashboard + namespace: monitoring +data: + ebpf-subsystem-metrics.json: |- + { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "bpf_programs {__name__=\"bpf_programs\", job=\"bpf-metrics-exporter\"}" + }, + "properties": [ + { + "id": "displayName", + "value": "Programs" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "{__name__=\"bpf_maps\", job=\"bpf-metrics-exporter\"}" + }, + "properties": [ + { + "id": "displayName", + "value": "Maps" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "{__name__=\"bpf_links\", job=\"bpf-metrics-exporter\"}" + }, + "properties": [ + { + "id": "displayName", + "value": "Links" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum(bpf_program_info)", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Programs", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum(bpf_map_info)", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Maps", + "range": true, + "refId": "B", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum(bpf_link_info)", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Links", + "range": true, + "refId": "C", + "useBackend": false + } + ], + "title": "Subsystem Entities", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "purple", + "mode": "palette-classic" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "gradient", + "type": "color-background" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 3, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "10.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "bpf_program_info", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "A", + "useBackend": false + } + ], + "title": "Loaded Program Info", + "transformations": [ + { + "id": "reduce", + "options": { + "includeTimeField": true, + "mode": "reduceFields", + "reducers": [ + "last" + ] + } + }, + { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "gpl_compatible", + "job", + "load_time", + "tag", + "type", + "name", + "map_ids", + "id" + ], + "mode": "columns" + } + }, + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "bpf_programs": true, + "job": true + }, + "indexByName": { + "Time": 0, + "bpf_programs": 1, + "gpl_compatible": 9, + "id": 4, + "job": 6, + "load_time": 5, + "map_ids": 7, + "name": 2, + "tag": 8, + "type": 3 + }, + "renameByName": {} + } + }, + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": [] + } + } + ], + "transparent": true, + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "purple", + "mode": "palette-classic" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "gradient", + "type": "color-background" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 6, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 3, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "load_time" + } + ] + }, + "pluginVersion": "10.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "bpf_map_info", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "A", + "useBackend": false + } + ], + "title": "Loaded Map Info", + "transformations": [ + { + "id": "reduce", + "options": { + "includeTimeField": true, + "mode": "reduceFields", + "reducers": [ + "last" + ] + } + }, + { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "flags", + "id", + "key_size", + "max_entries", + "name", + "type", + "value_size" + ], + "mode": "columns" + } + }, + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "bpf_maps": true, + "bpf_programs": true, + "job": true + }, + "indexByName": { + "Time": 0, + "bpf_maps": 4, + "flags": 8, + "id": 1, + "key_size": 5, + "max_entries": 7, + "name": 2, + "type": 3, + "value_size": 6 + }, + "renameByName": {} + } + }, + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": [] + } + } + ], + "transparent": true, + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "purple", + "mode": "palette-classic" + }, + "custom": { + "align": "auto", + "cellOptions": { + "mode": "gradient", + "type": "color-background" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 7, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 3, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "load_time" + } + ] + }, + "pluginVersion": "10.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "exemplar": false, + "expr": "bpf_link_info", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "A", + "useBackend": false + } + ], + "title": "Loaded Link Info", + "transformations": [ + { + "id": "reduce", + "options": { + "includeTimeField": true, + "mode": "reduceFields", + "reducers": [ + "last" + ] + } + }, + { + "id": "labelsToFields", + "options": { + "keepLabels": [ + "flags", + "id", + "key_size", + "max_entries", + "name", + "type", + "value_size", + "prog_id" + ], + "mode": "columns" + } + }, + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "bpf_links": true, + "bpf_maps": true, + "bpf_programs": true, + "job": true + }, + "indexByName": { + "Time": 0, + "bpf_links": 4, + "id": 2, + "prog_id": 1, + "type": 3 + }, + "renameByName": {} + } + }, + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": [] + } + } + ], + "transparent": true, + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 25 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum by() (bpf_program_verified_instructions_total)", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Verified Instructions Total", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 25 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum(bpf_program_size_translated_bytes_total)", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Translated Bytes Total", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 4, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 25 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "P1809F7CD0C75ACF3" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "sum(bpf_program_mem_bytes_total)", + "fullMetaSearch": false, + "includeNullMetadata": false, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total Memory Locked by Bpf Programs", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 38, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "eBPF Subsystem Metrics", + "uid": "ce0664f5-8526-430a-aa32-832686caa496", + "version": 1, + "weekStart": "" + } +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -127,6 +1105,11 @@ spec: - mountPath: /etc/grafana/provisioning/datasources name: grafana-datasources readOnly: true + - mountPath: /etc/grafana/provisioning/dashboards + name: grafana-dashboards + - mountPath: /etc/grafana/provisioning/dashboards/ebpf-subsystem-metrics.json + name: ebpf-subsystem-metrics-dashboard + subPath: ebpf-subsystem-metrics.json - name: mimir image: docker.io/grafana/mimir:latest args: @@ -150,6 +1133,12 @@ spec: - name: grafana-datasources configMap: name: grafana-datasources + - name: grafana-dashboards + configMap: + name: grafana-dashboards + - name: ebpf-subsystem-metrics-dashboard + configMap: + name: ebpf-subsystem-metrics-dashboard - name: mimir-data emptyDir: {} - name: mimir-config diff --git a/bpf-metrics-exporter/src/main.rs b/bpf-metrics-exporter/src/main.rs index 501844526..46262b6ea 100644 --- a/bpf-metrics-exporter/src/main.rs +++ b/bpf-metrics-exporter/src/main.rs @@ -1,4 +1,8 @@ -use aya::loaded_programs; +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use aya::{loaded_programs, maps::loaded_maps, programs::loaded_links}; +use bpfman::types::ProgramType; use chrono::{prelude::DateTime, Utc}; use clap::Parser; use opentelemetry::{ @@ -6,7 +10,7 @@ use opentelemetry::{ KeyValue, }; use opentelemetry_otlp::WithExportConfig; -use opentelemetry_sdk::{metrics::MeterProvider as SdkMeterProvider, runtime, Resource}; +use opentelemetry_sdk::{metrics::SdkMeterProvider, runtime, Resource}; use tokio::signal::ctrl_c; fn init_meter_provider(grpc_endpoint: &str) -> SdkMeterProvider { @@ -42,82 +46,207 @@ async fn main() -> anyhow::Result<()> { // Create a meter from the above MeterProvider. let meter = meter_provider.meter("bpf-metrics"); - let bpf_program_size_jitted_bytes = meter - .u64_observable_counter("bpf_program_size_jitted_bytes") - .with_description("BPF program size in bytes") - .with_unit(Unit::new("bytes")) + // GAUGE instruments: + + let bpf_program_info = meter + .u64_observable_gauge("bpf_program_info") + .with_description("eBPF Program metadata") + .init(); + + let bpf_map_info = meter + .u64_observable_gauge("bpf_map_info") + .with_description("eBPF Map metadata") + .init(); + + let bpf_link_info = meter + .u64_observable_gauge("bpf_link_info") + .with_description("eBPF Link metadata") .init(); + let bpf_program_load_time = meter + .i64_observable_gauge("bpf_program_load_time") + .with_description("BPF program load time") + .with_unit(Unit::new("seconds")) + .init(); + + // COUNTER instruments: + let bpf_program_size_translated_bytes = meter .u64_observable_counter("bpf_program_size_translated_bytes") .with_description("BPF program size in bytes") .with_unit(Unit::new("bytes")) .init(); + let bpf_program_size_jitted_bytes = meter + .u64_observable_counter("bpf_program_size_jitted_bytes") + .with_description("The size in bytes of the program's JIT-compiled machine code.") + .with_unit(Unit::new("bytes")) + .init(); + let bpf_program_mem_bytes = meter .u64_observable_counter("bpf_program_mem_bytes") .with_description("BPF program memory usage in bytes") .with_unit(Unit::new("bytes")) .init(); - let bpf_program_verified_instructions_total = meter - .u64_observable_counter("bpf_program_verified_instructions_total") + let bpf_program_verified_instructions = meter + .u64_observable_counter("bpf_program_verified_instructions") .with_description("BPF program verified instructions") .with_unit(Unit::new("instructions")) .init(); + let bpf_map_key_size = meter + .u64_observable_counter("bpf_map_key_size") + .with_description("BPF map key size") + .with_unit(Unit::new("bytes")) + .init(); + + let bpf_map_value_size = meter + .u64_observable_counter("bpf_map_value_size") + .with_description("BPF map value size") + .with_unit(Unit::new("bytes")) + .init(); + + let bpf_map_max_entries = meter + .u64_observable_counter("bpf_map_max_entries") + .with_description("BPF map maxiumum number of entries") + .with_unit(Unit::new("bytes")) + .init(); + meter .register_callback( &[ + bpf_program_info.as_any(), + bpf_map_info.as_any(), + bpf_link_info.as_any(), + bpf_program_load_time.as_any(), bpf_program_size_jitted_bytes.as_any(), bpf_program_size_translated_bytes.as_any(), bpf_program_mem_bytes.as_any(), - bpf_program_verified_instructions_total.as_any(), + bpf_program_verified_instructions.as_any(), + bpf_map_key_size.as_any(), + bpf_map_value_size.as_any(), + bpf_map_max_entries.as_any(), ], move |observer| { for program in loaded_programs().flatten() { + let id = program.id(); let name = program.name_as_str().unwrap_or_default().to_string(); - let ty = program.program_type().to_string(); + let ty: ProgramType = program.program_type().try_into().unwrap(); let tag = program.tag().to_string(); let gpl_compatible = program.gpl_compatible(); - let load_time = DateTime::::from(program.loaded_at()); + let map_ids = program.map_ids().unwrap_or_default(); + let load_time = DateTime::::from(program.loaded_at()); let jitted_bytes = program.size_jitted(); let translated_bytes = program.size_translated(); let mem_bytes = program.memory_locked().unwrap_or_default(); let verified_instructions = program.verified_instruction_count(); - let labels = [ - KeyValue::new("name", name), - KeyValue::new("type", ty), - KeyValue::new("tag", tag), + let prog_info_labels = [ + KeyValue::new("id", id.to_string()), + KeyValue::new("name", name.clone()), + KeyValue::new("type", format!("{ty}")), + KeyValue::new("tag", tag.clone()), KeyValue::new("gpl_compatible", gpl_compatible), + KeyValue::new("map_ids", format!("{map_ids:?}")), KeyValue::new( "load_time", load_time.format("%Y-%m-%d %H:%M:%S").to_string(), ), ]; + observer.observe_u64(&bpf_program_info, 1, &prog_info_labels); + + let prog_key_labels = [ + KeyValue::new("id", id.to_string()), + KeyValue::new("name", name), + KeyValue::new("type", format!("{ty}")), + ]; + + observer.observe_i64( + &bpf_program_load_time, + load_time.timestamp(), + &prog_key_labels, + ); + observer.observe_u64( &bpf_program_size_jitted_bytes, jitted_bytes.into(), - &labels, + &prog_key_labels, ); observer.observe_u64( &bpf_program_size_translated_bytes, translated_bytes.into(), - &labels, + &prog_key_labels, ); - observer.observe_u64(&bpf_program_mem_bytes, mem_bytes.into(), &labels); + observer.observe_u64( + &bpf_program_mem_bytes, + mem_bytes.into(), + &prog_key_labels, + ); observer.observe_u64( - &bpf_program_verified_instructions_total, + &bpf_program_verified_instructions, verified_instructions.into(), - &labels, + &prog_key_labels, ); } + + for link in loaded_links().flatten() { + let id = link.id; + let prog_id = link.prog_id; + let _type = link.type_; + // TODO getting more link metadata will require an aya_patch + // let link_info = match link.__bindgen_anon_1 { + // // aya_obj::bpf_link_info__bindgen_ty_1::raw_tracepoint(i) => "tracepoint", + // aya_obj::generated::bpf_link_info__bindgen_ty_1{ raw_tracepoint } => format!("tracepoint name: "), + // } + + let link_labels = [ + KeyValue::new("id", id.to_string()), + KeyValue::new("prog_id", prog_id.to_string()), + KeyValue::new("type", _type.to_string()), + ]; + + observer.observe_u64(&bpf_link_info, 1, &link_labels); + } + + for map in loaded_maps().flatten() { + let map_id = map.id(); + let name = map.name_as_str().unwrap_or_default().to_string(); + let ty = map.map_type().to_string(); + let key_size = map.key_size(); + let value_size = map.value_size(); + let max_entries = map.max_entries(); + let flags = map.map_flags(); + + let map_labels = [ + KeyValue::new("name", name.clone()), + KeyValue::new("id", map_id.to_string()), + KeyValue::new("type", ty.clone()), + KeyValue::new("key_size", key_size.to_string()), + KeyValue::new("value_size", value_size.to_string()), + KeyValue::new("max_entries", max_entries.to_string()), + KeyValue::new("flags", flags.to_string()), + ]; + + observer.observe_u64(&bpf_map_info, 1, &map_labels); + + let map_key_labels = [ + KeyValue::new("name", name), + KeyValue::new("id", map_id.to_string()), + KeyValue::new("type", ty), + ]; + + observer.observe_u64(&bpf_map_key_size, key_size.into(), &map_key_labels); + + observer.observe_u64(&bpf_map_value_size, value_size.into(), &map_key_labels); + + observer.observe_u64(&bpf_map_max_entries, max_entries.into(), &map_key_labels); + } }, ) .expect("failed to register callback"); diff --git a/bpfman-api/Cargo.toml b/bpfman-api/Cargo.toml index 3502e9fe8..c958902c4 100644 --- a/bpfman-api/Cargo.toml +++ b/bpfman-api/Cargo.toml @@ -1,19 +1,72 @@ [package] description = "gRPC bindings to the bpfman API" -edition = "2021" -license = "Apache-2.0" +edition.workspace = true +license.workspace = true name = "bpfman-api" -repository = "https://github.com/bpfman/bpfman" -version = "0.3.1" +repository.workspace = true +version.workspace = true + +[[bin]] +name = "bpfman-rpc" +path = "src/bin/rpc/main.rs" [dependencies] +anyhow = { workspace = true, features = ["std"] } +async-trait = { workspace = true } aya = { workspace = true } -clap = { workspace = true, features = ["derive", "std"] } +base16ct = { workspace = true, features = ["alloc"] } +base64 = { workspace = true } +bpfman = { workspace = true } +bpfman-csi = { workspace = true } +caps = { workspace = true } +chrono = { workspace = true } +clap = { workspace = true, features = [ + "color", + "derive", + "help", + "std", + "suggestions", + "usage", +] } +env_logger = { workspace = true } +flate2 = { workspace = true, features = ["zlib"] } +futures = { workspace = true } +hex = { workspace = true, features = ["std"] } +lazy_static = { workspace = true } +libsystemd = { workspace = true } log = { workspace = true } +netlink-packet-route = { workspace = true } +nix = { workspace = true, features = [ + "fs", + "mount", + "net", + "resource", + "socket", + "user", +] } +oci-distribution = { workspace = true, default-features = false, features = [ + "rustls-tls", + "trust-dns", +] } prost = { workspace = true, features = ["prost-derive", "std"] } +rand = { workspace = true } +rtnetlink = { workspace = true, features = ["tokio_socket"] } serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, features = ["std"] } +sha2 = { workspace = true } +sigstore = { workspace = true, features = [ + "cached-client", + "cosign-rustls-tls", + "sigstore-trust-root", +] } +sled = { workspace = true } +systemd-journal-logger = { workspace = true } +tar = { workspace = true } +tempfile = { workspace = true } thiserror = { workspace = true } -tokio = { workspace = true, features = ["full"] } +tokio = { workspace = true, features = ["full", "signal"] } +tokio-stream = { workspace = true, features = ["net"] } toml = { workspace = true, features = ["parse"] } -tonic = { workspace = true, features = ["codegen", "prost"] } +tonic = { workspace = true, features = ["codegen", "prost", "transport"] } +tower = { workspace = true } url = { workspace = true } diff --git a/bpfman-api/src/bin/rpc/main.rs b/bpfman-api/src/bin/rpc/main.rs new file mode 100644 index 000000000..392e96d9d --- /dev/null +++ b/bpfman-api/src/bin/rpc/main.rs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman +use std::path::PathBuf; + +use clap::{Args, Parser}; + +use crate::serve::serve; + +mod rpc; +mod serve; +mod storage; + +#[derive(Parser, Debug)] +#[command(long_about = "A rpc server proxy for the bpfman library")] +#[command(name = "bpfman-rpc")] +pub(crate) struct Rpc { + /// Optional: Enable CSI support. Only supported when run in a Kubernetes + /// environment with bpfman-agent. + #[clap(long, verbatim_doc_comment)] + pub(crate) csi_support: bool, + /// Optional: Shutdown after N seconds of inactivity. Use 0 to disable. + #[clap(long, verbatim_doc_comment, default_value = "15")] + pub(crate) timeout: u64, + #[clap(long, default_value = "/run/bpfman-sock/bpfman.sock")] + /// Optional: Configure the location of the bpfman unix socket. + pub(crate) socket_path: PathBuf, +} + +#[derive(Args, Debug)] +#[command(disable_version_flag = true)] +pub(crate) struct ServiceArgs { + /// Optional: Shutdown after N seconds of inactivity. Use 0 to disable. + #[clap(long, verbatim_doc_comment, default_value = "15")] + pub(crate) timeout: u64, + #[clap(long, default_value = "/run/bpfman-sock/bpfman.sock")] + /// Optional: Configure the location of the bpfman unix socket. + pub(crate) socket_path: PathBuf, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let args = Rpc::parse(); + //TODO https://github.com/bpfman/bpfman/issues/881 + serve(args.csi_support, args.timeout, &args.socket_path).await?; + + Ok(()) +} diff --git a/bpfman-api/src/bin/rpc/rpc.rs b/bpfman-api/src/bin/rpc/rpc.rs new file mode 100644 index 000000000..69d0addcb --- /dev/null +++ b/bpfman-api/src/bin/rpc/rpc.rs @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman +use bpfman::{ + add_program, get_program, list_programs, pull_bytecode, remove_program, + types::{ + FentryProgram, FexitProgram, KprobeProgram, ListFilter, Location, Program, ProgramData, + TcProceedOn, TcProgram, TracepointProgram, UprobeProgram, XdpProceedOn, XdpProgram, + }, +}; +use bpfman_api::v1::{ + attach_info::Info, bpfman_server::Bpfman, bytecode_location::Location as RpcLocation, + list_response::ListResult, FentryAttachInfo, FexitAttachInfo, GetRequest, GetResponse, + KprobeAttachInfo, ListRequest, ListResponse, LoadRequest, LoadResponse, PullBytecodeRequest, + PullBytecodeResponse, TcAttachInfo, TracepointAttachInfo, UnloadRequest, UnloadResponse, + UprobeAttachInfo, XdpAttachInfo, +}; +use tonic::{Request, Response, Status}; + +pub struct BpfmanLoader {} + +impl BpfmanLoader { + pub(crate) fn new() -> BpfmanLoader { + BpfmanLoader {} + } +} + +#[tonic::async_trait] +impl Bpfman for BpfmanLoader { + async fn load(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + + let bytecode_source = match request + .bytecode + .ok_or(Status::aborted("missing bytecode info"))? + .location + .ok_or(Status::aborted("missing location"))? + { + RpcLocation::Image(i) => Location::Image(i.into()), + RpcLocation::File(p) => Location::File(p), + }; + + let data = ProgramData::new( + bytecode_source, + request.name, + request.metadata, + request.global_data, + request.map_owner_id, + ) + .map_err(|e| Status::aborted(format!("failed to create ProgramData: {e}")))?; + + let program = match request + .attach + .ok_or(Status::aborted("missing attach info"))? + .info + .ok_or(Status::aborted("missing info"))? + { + Info::XdpAttachInfo(XdpAttachInfo { + priority, + iface, + position: _, + proceed_on, + }) => Program::Xdp( + XdpProgram::new( + data, + priority, + iface, + XdpProceedOn::from_int32s(proceed_on) + .map_err(|_| Status::aborted("failed to parse proceed_on"))?, + ) + .map_err(|e| Status::aborted(format!("failed to create xdpprogram: {e}")))?, + ), + Info::TcAttachInfo(TcAttachInfo { + priority, + iface, + position: _, + direction, + proceed_on, + }) => { + let direction = direction + .try_into() + .map_err(|_| Status::aborted("direction is not a string"))?; + Program::Tc( + TcProgram::new( + data, + priority, + iface, + TcProceedOn::from_int32s(proceed_on) + .map_err(|_| Status::aborted("failed to parse proceed_on"))?, + direction, + ) + .map_err(|e| Status::aborted(format!("failed to create tcprogram: {e}")))?, + ) + } + Info::TracepointAttachInfo(TracepointAttachInfo { tracepoint }) => Program::Tracepoint( + TracepointProgram::new(data, tracepoint) + .map_err(|e| Status::aborted(format!("failed to create tcprogram: {e}")))?, + ), + Info::KprobeAttachInfo(KprobeAttachInfo { + fn_name, + offset, + retprobe, + container_pid, + }) => Program::Kprobe( + KprobeProgram::new(data, fn_name, offset, retprobe, container_pid) + .map_err(|e| Status::aborted(format!("failed to create kprobeprogram: {e}")))?, + ), + Info::UprobeAttachInfo(UprobeAttachInfo { + fn_name, + offset, + target, + retprobe, + pid, + container_pid, + }) => Program::Uprobe( + UprobeProgram::new(data, fn_name, offset, target, retprobe, pid, container_pid) + .map_err(|e| Status::aborted(format!("failed to create uprobeprogram: {e}")))?, + ), + Info::FentryAttachInfo(FentryAttachInfo { fn_name }) => Program::Fentry( + FentryProgram::new(data, fn_name) + .map_err(|e| Status::aborted(format!("failed to create fentryprogram: {e}")))?, + ), + Info::FexitAttachInfo(FexitAttachInfo { fn_name }) => Program::Fexit( + FexitProgram::new(data, fn_name) + .map_err(|e| Status::aborted(format!("failed to create fexitprogram: {e}")))?, + ), + }; + + let program = add_program(program) + .await + .map_err(|e| Status::aborted(format!("{e}")))?; + + let reply_entry = + LoadResponse { + info: Some((&program).try_into().map_err(|e| { + Status::aborted(format!("convert Program to GRPC program: {e}")) + })?), + kernel_info: Some((&program).try_into().map_err(|e| { + Status::aborted(format!("convert Program to GRPC kernel program info: {e}")) + })?), + }; + + Ok(Response::new(reply_entry)) + } + + async fn unload( + &self, + request: Request, + ) -> Result, Status> { + let reply = UnloadResponse {}; + let request = request.into_inner(); + + remove_program(request.id) + .await + .map_err(|e| Status::aborted(format!("{e}")))?; + + Ok(Response::new(reply)) + } + + async fn get(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + let id = request.id; + + let program = get_program(id) + .await + .map_err(|e| Status::aborted(format!("{e}")))?; + + let reply_entry = + GetResponse { + info: if let Program::Unsupported(_) = program { + None + } else { + Some((&program).try_into().map_err(|e| { + Status::aborted(format!("failed to get program metadata: {e}")) + })?) + }, + kernel_info: Some((&program).try_into().map_err(|e| { + Status::aborted(format!("convert Program to GRPC kernel program info: {e}")) + })?), + }; + Ok(Response::new(reply_entry)) + } + + async fn list(&self, request: Request) -> Result, Status> { + let mut reply = ListResponse { results: vec![] }; + + let filter = ListFilter::new( + request.get_ref().program_type, + request.get_ref().match_metadata.clone(), + request.get_ref().bpfman_programs_only(), + ); + + // Await the response + for r in list_programs(filter) + .await + .map_err(|e| Status::aborted(format!("failed to list programs: {e}")))? + { + // Populate the response with the Program Info and the Kernel Info. + let reply_entry = ListResult { + info: if let Program::Unsupported(_) = r { + None + } else { + Some((&r).try_into().map_err(|e| { + Status::aborted(format!("failed to get program metadata: {e}")) + })?) + }, + kernel_info: Some((&r).try_into().map_err(|e| { + Status::aborted(format!("convert Program to GRPC kernel program info: {e}")) + })?), + }; + reply.results.push(reply_entry) + } + Ok(Response::new(reply)) + } + + async fn pull_bytecode( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + let request = request.into_inner(); + let image = match request.image { + Some(i) => i.into(), + None => return Err(Status::aborted("Empty pull_bytecode request received")), + }; + + pull_bytecode(image) + .await + .map_err(|e| Status::aborted(format!("{e}")))?; + + let reply = PullBytecodeResponse {}; + Ok(Response::new(reply)) + } +} diff --git a/bpfman-api/src/bin/rpc/serve.rs b/bpfman-api/src/bin/rpc/serve.rs new file mode 100644 index 000000000..dfd9c7c5d --- /dev/null +++ b/bpfman-api/src/bin/rpc/serve.rs @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use std::{ + fs::{create_dir_all, remove_file}, + os::unix::prelude::{FromRawFd, IntoRawFd}, + path::Path, +}; + +use anyhow::{anyhow, Context}; +use bpfman::utils::{set_file_permissions, SOCK_MODE}; +use bpfman_api::v1::bpfman_server::BpfmanServer; +use libsystemd::activation::IsType; +use log::{debug, error, info}; +use tokio::{ + join, + net::UnixListener, + signal::unix::{signal, SignalKind}, + sync::broadcast, + task::{JoinHandle, JoinSet}, +}; +use tokio_stream::wrappers::UnixListenerStream; +use tonic::transport::Server; + +use crate::{rpc::BpfmanLoader, storage::StorageManager}; + +pub(crate) const RTDIR_SOCK: &str = "/run/bpfman-sock"; +// The CSI socket must be in it's own sub directory so we can easily create a dedicated +// K8s volume mount for it. +pub(crate) const RTDIR_BPFMAN_CSI: &str = "/run/bpfman/csi"; + +pub async fn serve(csi_support: bool, timeout: u64, socket_path: &Path) -> anyhow::Result<()> { + let (shutdown_tx, shutdown_rx1) = broadcast::channel(32); + let shutdown_rx3 = shutdown_tx.subscribe(); + let shutdown_handle = tokio::spawn(shutdown_handler(timeout, shutdown_tx)); + + let loader = BpfmanLoader::new(); + let service = BpfmanServer::new(loader); + + let mut listeners: Vec<_> = Vec::new(); + + let handle = serve_unix(socket_path, service.clone(), shutdown_rx1).await?; + listeners.push(handle); + + if csi_support { + create_dir_all(RTDIR_BPFMAN_CSI).context("unable to create CSI directory")?; + create_dir_all(RTDIR_SOCK).context("unable to create socket directory")?; + let storage_manager = StorageManager::new(); + let storage_manager_handle = + tokio::spawn(async move { storage_manager.run(shutdown_rx3).await }); + let (_, res_storage, _) = join!( + join_listeners(listeners), + storage_manager_handle, + shutdown_handle + ); + if let Some(e) = res_storage.err() { + return Err(e.into()); + } + } else { + let (_, res) = join!(join_listeners(listeners), shutdown_handle); + if let Some(e) = res.err() { + return Err(e.into()); + } + } + + Ok(()) +} + +pub(crate) async fn shutdown_handler(timeout: u64, shutdown_tx: broadcast::Sender<()>) { + let mut joinset = JoinSet::new(); + if timeout > 0 { + info!("Using inactivity timer of {} seconds", timeout); + let duration: std::time::Duration = std::time::Duration::from_secs(timeout); + joinset.spawn(Box::pin(tokio::time::sleep(duration))); + } else { + info!("Using no inactivity timer"); + } + let mut sigint = signal(SignalKind::interrupt()).unwrap(); + joinset.spawn(async move { + sigint.recv().await; + debug!("Received SIGINT"); + }); + + let mut sigterm = signal(SignalKind::terminate()).unwrap(); + joinset.spawn(async move { + sigterm.recv().await; + debug!("Received SIGTERM"); + }); + + joinset.join_next().await; + shutdown_tx.send(()).unwrap(); +} + +async fn join_listeners(listeners: Vec>) { + for listener in listeners { + match listener.await { + Ok(()) => {} + Err(e) => eprintln!("Error = {e:?}"), + } + } +} + +async fn serve_unix( + path: &Path, + service: BpfmanServer, + mut shutdown_channel: broadcast::Receiver<()>, +) -> anyhow::Result> { + let uds_stream = if let Ok(stream) = systemd_unix_stream() { + stream + } else { + std_unix_stream(path).await? + }; + + let serve = Server::builder() + .add_service(service) + .serve_with_incoming_shutdown(uds_stream, async move { + match shutdown_channel.recv().await { + Ok(()) => debug!("Unix Socket: Received shutdown signal"), + Err(e) => error!("Error receiving shutdown signal {:?}", e), + }; + }); + + let socket_path = path.to_path_buf(); + Ok(tokio::spawn(async move { + info!("Listening on {}", socket_path.to_path_buf().display()); + if let Err(e) = serve.await { + eprintln!("Error = {e:?}"); + } + info!( + "Shutdown Unix Handler {}", + socket_path.to_path_buf().display() + ); + })) +} + +fn systemd_unix_stream() -> anyhow::Result { + let listen_fds = libsystemd::activation::receive_descriptors(true)?; + if listen_fds.len() == 1 { + if let Some(fd) = listen_fds.first() { + if !fd.is_unix() { + return Err(anyhow!("Wrong Socket")); + } + let std_listener = + unsafe { std::os::unix::net::UnixListener::from_raw_fd(fd.clone().into_raw_fd()) }; + std_listener.set_nonblocking(true)?; + let tokio_listener = UnixListener::from_std(std_listener)?; + info!("Using a Unix socket from systemd"); + return Ok(UnixListenerStream::new(tokio_listener)); + } + } + + Err(anyhow!("Unable to retrieve fd from systemd")) +} + +async fn std_unix_stream(path: &Path) -> anyhow::Result { + // Listen on Unix socket + if path.exists() { + // Attempt to remove the socket, since bind fails if it exists + remove_file(path)?; + } + + let uds = UnixListener::bind(path)?; + let stream = UnixListenerStream::new(uds); + // Always set the file permissions of our listening socket. + set_file_permissions(path, SOCK_MODE); + + info!("Using default Unix socket"); + Ok(stream) +} diff --git a/bpfman/src/storage.rs b/bpfman-api/src/bin/rpc/storage.rs similarity index 82% rename from bpfman/src/storage.rs rename to bpfman-api/src/bin/rpc/storage.rs index 3d9685637..cd1c49f41 100644 --- a/bpfman/src/storage.rs +++ b/bpfman-api/src/bin/rpc/storage.rs @@ -10,7 +10,11 @@ use std::{ use anyhow::{Context, Result}; use async_trait::async_trait; use aya::maps::MapData; -use bpfman_api::util::directories::{RTDIR_BPFMAN_CSI_FS, RTPATH_BPFMAN_CSI_SOCKET}; +use bpfman::{ + list_programs, + types::ListFilter, + utils::{create_bpffs, set_dir_permissions, set_file_permissions, SOCK_MODE}, +}; use bpfman_csi::v1::{ identity_server::{Identity, IdentityServer}, node_server::{Node, NodeServer}, @@ -23,30 +27,23 @@ use bpfman_csi::v1::{ NodeUnpublishVolumeResponse, NodeUnstageVolumeRequest, NodeUnstageVolumeResponse, ProbeRequest, ProbeResponse, }; -use log::{debug, info, warn}; +use log::{debug, error, info, warn}; use nix::mount::{mount, umount, MsFlags}; -use tokio::{ - net::UnixListener, - sync::{mpsc, mpsc::Sender, oneshot}, -}; +use tokio::{net::UnixListener, sync::broadcast}; use tokio_stream::wrappers::UnixListenerStream; use tonic::{transport::Server, Request, Response, Status}; -use crate::{ - command::Command, - serve::shutdown_handler, - utils::{create_bpffs, set_dir_permissions, set_file_permissions, SOCK_MODE}, -}; - const DRIVER_NAME: &str = "csi.bpfman.io"; const MAPS_KEY: &str = "csi.bpfman.io/maps"; const PROGRAM_KEY: &str = "csi.bpfman.io/program"; const OPERATOR_PROGRAM_KEY: &str = "bpfman.io/ProgramName"; +const RTPATH_BPFMAN_CSI_SOCKET: &str = "/run/bpfman/csi/csi.sock"; +const RTDIR_BPFMAN_CSI_FS: &str = "/run/bpfman/csi/fs"; // Node Publish Volume Error code constant mirrored from: https://github.com/container-storage-interface/spec/blob/master/spec.md#nodepublishvolume-errors const NPV_NOT_FOUND: i32 = 5; const OWNER_READ_WRITE: u32 = 0o0750; -pub(crate) struct StorageManager { +pub struct StorageManager { csi_identity: CsiIdentity, csi_node: CsiNode, } @@ -58,7 +55,6 @@ struct CsiIdentity { struct CsiNode { node_id: String, - tx: Sender, } #[async_trait] @@ -136,48 +132,29 @@ impl Node for CsiNode { (Some(m), Some(program_name)) => { let maps: Vec<&str> = m.split(',').collect(); - // Get the program information from the BpfManager - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::List { responder: resp_tx }; - - // Send the LIST request - self.tx.send(cmd).await.unwrap(); - - // Await the response - let core_map_path = match resp_rx.await { - Ok(res) => match res { - // Find the Program with the specified *Program CRD name - Ok(results) => { - let prog_data = results - .into_iter() - .find(|p| { - if let Ok(data) = p.data() { - data.metadata().get(OPERATOR_PROGRAM_KEY) - == Some(program_name) - } else { - false - } - }) - .ok_or(Status::new( - NPV_NOT_FOUND.into(), - format!("Bpfman Program {program_name} not found"), - ))?; - Ok(prog_data - .data().expect("program data is valid because we just checked it") - .map_pin_path() - .expect("map pin path should be set since the program is already loaded") - .to_owned()) - } - Err(_) => Err(Status::new( - NPV_NOT_FOUND.into(), - format!("Bpfman Program {program_name} not found"), - )), - }, - Err(_) => Err(Status::new( + // Find the Program with the specified *Program CRD name + let prog_data = list_programs(ListFilter::default()) + .await + .map_err(|e| Status::aborted(format!("failed list programs: {e}")))? + .into_iter() + .find(|p| { + p.get_data() + .get_metadata() + .expect("unable to get program metadata") + .get(OPERATOR_PROGRAM_KEY) + == Some(program_name) + }) + .ok_or(Status::new( NPV_NOT_FOUND.into(), - format!("Unable to list bpfman programs {program_name} not found"), - )), - }?; + format!("Bpfman Program {program_name} not found"), + ))?; + + let core_map_path = prog_data + .get_data() + .get_map_pin_path() + .map_err(|e| Status::aborted(format!("failed to get map_pin_path: {e}")))? + .expect("map pin path should be set since the program is already loaded") + .to_owned(); // Create the Target Path if it doesn't exist let target = Path::new(target_path); @@ -188,7 +165,7 @@ impl Node for CsiNode { format!("failed creating target path {target_path:?}: {e}"), ) })?; - set_dir_permissions(target_path, OWNER_READ_WRITE).await; + set_dir_permissions(target_path, OWNER_READ_WRITE); } // Make a new bpf fs specifically for the pod. @@ -214,7 +191,7 @@ impl Node for CsiNode { // TODO(astoycos) all aya calls should be completed by main bpfManager thread. maps.iter().try_for_each(|m| { debug!("Loading map {m} from {core_map_path:?}"); - let mut map = MapData::from_pin(core_map_path.join(m)).map_err(|e| { + let map = MapData::from_pin(core_map_path.join(m)).map_err(|e| { Status::new( NPV_NOT_FOUND.into(), format!("map {m} not found in {program_name}'s pinned maps: {e}"), @@ -342,8 +319,14 @@ impl Node for CsiNode { } } +impl Default for StorageManager { + fn default() -> Self { + Self::new() + } +} + impl StorageManager { - pub fn new(tx: mpsc::Sender) -> Self { + pub fn new() -> Self { const VERSION: &str = env!("CARGO_PKG_VERSION"); let node_id = std::env::var("KUBE_NODE_NAME") .expect("cannot start bpfman csi driver if KUBE_NODE_NAME not set"); @@ -353,7 +336,7 @@ impl StorageManager { version: VERSION.to_string(), }; - let csi_node = CsiNode { node_id, tx }; + let csi_node = CsiNode { node_id }; Self { csi_node, @@ -361,7 +344,11 @@ impl StorageManager { } } - pub async fn run(self) { + pub async fn run(self, mut shutdown_channel: broadcast::Receiver<()>) { + create_dir_all(RTDIR_BPFMAN_CSI_FS) + .context("unable to create CSI socket directory") + .expect("cannot create csi filesystem"); + let path: &Path = Path::new(RTPATH_BPFMAN_CSI_SOCKET); // Listen on Unix socket if path.exists() { @@ -372,14 +359,19 @@ impl StorageManager { let uds = UnixListener::bind(path) .unwrap_or_else(|_| panic!("failed to bind {RTPATH_BPFMAN_CSI_SOCKET}")); let uds_stream = UnixListenerStream::new(uds); - set_file_permissions(RTPATH_BPFMAN_CSI_SOCKET, SOCK_MODE).await; + set_file_permissions(Path::new(RTPATH_BPFMAN_CSI_SOCKET), SOCK_MODE); let node_service = NodeServer::new(self.csi_node); let identity_service = IdentityServer::new(self.csi_identity); let serve = Server::builder() .add_service(node_service) .add_service(identity_service) - .serve_with_incoming_shutdown(uds_stream, shutdown_handler()); + .serve_with_incoming_shutdown(uds_stream, async move { + match shutdown_channel.recv().await { + Ok(()) => debug!("Unix Socket: Received shutdown signal"), + Err(e) => error!("Error receiving shutdown signal {:?}", e), + }; + }); tokio::spawn(async move { info!("CSI Plugin Listening on {}", path.display()); @@ -400,11 +392,7 @@ impl StorageManager { } #[allow(dead_code)] // TODO: Remove this when the storage manager is fully implemented - fn pin_map_to_bpffs( - &self, - source_object: &mut MapData, - dest_bpffs: &Path, - ) -> anyhow::Result<()> { + fn pin_map_to_bpffs(&self, source_object: &MapData, dest_bpffs: &Path) -> anyhow::Result<()> { source_object .pin(dest_bpffs) .map_err(|e| anyhow::anyhow!("unable to pin map to bpffs: {}", e))?; diff --git a/bpfman-api/src/bpfman.v1.rs b/bpfman-api/src/bpfman.v1.rs index 835f8d3ff..56739c132 100644 --- a/bpfman-api/src/bpfman.v1.rs +++ b/bpfman-api/src/bpfman.v1.rs @@ -73,8 +73,10 @@ pub struct ProgramInfo { #[prost(message, optional, tag = "3")] pub attach: ::core::option::Option, #[prost(map = "string, bytes", tag = "4")] - pub global_data: - ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::vec::Vec>, + pub global_data: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::vec::Vec, + >, #[prost(uint32, optional, tag = "5")] pub map_owner_id: ::core::option::Option, #[prost(string, tag = "6")] @@ -82,8 +84,10 @@ pub struct ProgramInfo { #[prost(string, repeated, tag = "7")] pub map_used_by: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, #[prost(map = "string, string", tag = "8")] - pub metadata: - ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + pub metadata: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::string::String, + >, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -126,8 +130,8 @@ pub struct KprobeAttachInfo { pub offset: u64, #[prost(bool, tag = "3")] pub retprobe: bool, - #[prost(string, optional, tag = "4")] - pub namespace: ::core::option::Option<::prost::alloc::string::String>, + #[prost(int32, optional, tag = "4")] + pub container_pid: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -142,13 +146,25 @@ pub struct UprobeAttachInfo { pub retprobe: bool, #[prost(int32, optional, tag = "5")] pub pid: ::core::option::Option, - #[prost(string, optional, tag = "6")] - pub namespace: ::core::option::Option<::prost::alloc::string::String>, + #[prost(int32, optional, tag = "6")] + pub container_pid: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FentryAttachInfo { + #[prost(string, tag = "1")] + pub fn_name: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FexitAttachInfo { + #[prost(string, tag = "1")] + pub fn_name: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AttachInfo { - #[prost(oneof = "attach_info::Info", tags = "2, 3, 4, 5, 6")] + #[prost(oneof = "attach_info::Info", tags = "2, 3, 4, 5, 6, 7, 8")] pub info: ::core::option::Option, } /// Nested message and enum types in `AttachInfo`. @@ -166,6 +182,10 @@ pub mod attach_info { KprobeAttachInfo(super::KprobeAttachInfo), #[prost(message, tag = "6")] UprobeAttachInfo(super::UprobeAttachInfo), + #[prost(message, tag = "7")] + FentryAttachInfo(super::FentryAttachInfo), + #[prost(message, tag = "8")] + FexitAttachInfo(super::FexitAttachInfo), } } #[allow(clippy::derive_partial_eq_without_eq)] @@ -180,11 +200,15 @@ pub struct LoadRequest { #[prost(message, optional, tag = "4")] pub attach: ::core::option::Option, #[prost(map = "string, string", tag = "5")] - pub metadata: - ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + pub metadata: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::string::String, + >, #[prost(map = "string, bytes", tag = "6")] - pub global_data: - ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::vec::Vec>, + pub global_data: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::vec::Vec, + >, #[prost(string, optional, tag = "7")] pub uuid: ::core::option::Option<::prost::alloc::string::String>, #[prost(uint32, optional, tag = "8")] @@ -215,8 +239,10 @@ pub struct ListRequest { #[prost(bool, optional, tag = "2")] pub bpfman_programs_only: ::core::option::Option, #[prost(map = "string, string", tag = "3")] - pub match_metadata: - ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + pub match_metadata: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::string::String, + >, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -261,8 +287,8 @@ pub struct GetResponse { /// Generated client implementations. pub mod bpfman_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] - use tonic::codegen::http::Uri; use tonic::codegen::*; + use tonic::codegen::http::Uri; #[derive(Debug, Clone)] pub struct BpfmanClient { inner: tonic::client::Grpc, @@ -295,8 +321,9 @@ pub mod bpfman_client { >::ResponseBody, >, >, - >>::Error: - Into + Send + Sync, + , + >>::Error: Into + Send + Sync, { BpfmanClient::new(InterceptedService::new(inner, interceptor)) } @@ -335,66 +362,79 @@ pub mod bpfman_client { &mut self, request: impl tonic::IntoRequest, ) -> std::result::Result, tonic::Status> { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static("/bpfman.v1.Bpfman/Load"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("bpfman.v1.Bpfman", "Load")); + req.extensions_mut().insert(GrpcMethod::new("bpfman.v1.Bpfman", "Load")); self.inner.unary(req, path, codec).await } pub async fn unload( &mut self, request: impl tonic::IntoRequest, ) -> std::result::Result, tonic::Status> { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static("/bpfman.v1.Bpfman/Unload"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("bpfman.v1.Bpfman", "Unload")); + req.extensions_mut().insert(GrpcMethod::new("bpfman.v1.Bpfman", "Unload")); self.inner.unary(req, path, codec).await } pub async fn list( &mut self, request: impl tonic::IntoRequest, ) -> std::result::Result, tonic::Status> { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static("/bpfman.v1.Bpfman/List"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("bpfman.v1.Bpfman", "List")); + req.extensions_mut().insert(GrpcMethod::new("bpfman.v1.Bpfman", "List")); self.inner.unary(req, path, codec).await } pub async fn pull_bytecode( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> - { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/bpfman.v1.Bpfman/PullBytecode"); + let path = http::uri::PathAndQuery::from_static( + "/bpfman.v1.Bpfman/PullBytecode", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("bpfman.v1.Bpfman", "PullBytecode")); @@ -404,17 +444,19 @@ pub mod bpfman_client { &mut self, request: impl tonic::IntoRequest, ) -> std::result::Result, tonic::Status> { - self.inner.ready().await.map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static("/bpfman.v1.Bpfman/Get"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("bpfman.v1.Bpfman", "Get")); + req.extensions_mut().insert(GrpcMethod::new("bpfman.v1.Bpfman", "Get")); self.inner.unary(req, path, codec).await } } @@ -441,7 +483,10 @@ pub mod bpfman_server { async fn pull_bytecode( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn get( &self, request: tonic::Request, @@ -470,7 +515,10 @@ pub mod bpfman_server { max_encoding_message_size: None, } } - pub fn with_interceptor(inner: T, interceptor: F) -> InterceptedService + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService where F: tonic::service::Interceptor, { @@ -526,15 +574,21 @@ pub mod bpfman_server { "/bpfman.v1.Bpfman/Load" => { #[allow(non_camel_case_types)] struct LoadSvc(pub Arc); - impl tonic::server::UnaryService for LoadSvc { + impl tonic::server::UnaryService + for LoadSvc { type Response = super::LoadResponse; - type Future = BoxFuture, tonic::Status>; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; fn call( &mut self, request: tonic::Request, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::load(&inner, request).await }; + let fut = async move { + ::load(&inner, request).await + }; Box::pin(fut) } } @@ -564,15 +618,21 @@ pub mod bpfman_server { "/bpfman.v1.Bpfman/Unload" => { #[allow(non_camel_case_types)] struct UnloadSvc(pub Arc); - impl tonic::server::UnaryService for UnloadSvc { + impl tonic::server::UnaryService + for UnloadSvc { type Response = super::UnloadResponse; - type Future = BoxFuture, tonic::Status>; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; fn call( &mut self, request: tonic::Request, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::unload(&inner, request).await }; + let fut = async move { + ::unload(&inner, request).await + }; Box::pin(fut) } } @@ -602,15 +662,21 @@ pub mod bpfman_server { "/bpfman.v1.Bpfman/List" => { #[allow(non_camel_case_types)] struct ListSvc(pub Arc); - impl tonic::server::UnaryService for ListSvc { + impl tonic::server::UnaryService + for ListSvc { type Response = super::ListResponse; - type Future = BoxFuture, tonic::Status>; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; fn call( &mut self, request: tonic::Request, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::list(&inner, request).await }; + let fut = async move { + ::list(&inner, request).await + }; Box::pin(fut) } } @@ -640,16 +706,23 @@ pub mod bpfman_server { "/bpfman.v1.Bpfman/PullBytecode" => { #[allow(non_camel_case_types)] struct PullBytecodeSvc(pub Arc); - impl tonic::server::UnaryService for PullBytecodeSvc { + impl< + T: Bpfman, + > tonic::server::UnaryService + for PullBytecodeSvc { type Response = super::PullBytecodeResponse; - type Future = BoxFuture, tonic::Status>; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; fn call( &mut self, request: tonic::Request, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = - async move { ::pull_bytecode(&inner, request).await }; + let fut = async move { + ::pull_bytecode(&inner, request).await + }; Box::pin(fut) } } @@ -679,15 +752,21 @@ pub mod bpfman_server { "/bpfman.v1.Bpfman/Get" => { #[allow(non_camel_case_types)] struct GetSvc(pub Arc); - impl tonic::server::UnaryService for GetSvc { + impl tonic::server::UnaryService + for GetSvc { type Response = super::GetResponse; - type Future = BoxFuture, tonic::Status>; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; fn call( &mut self, request: tonic::Request, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::get(&inner, request).await }; + let fut = async move { + ::get(&inner, request).await + }; Box::pin(fut) } } @@ -714,14 +793,18 @@ pub mod bpfman_server { }; Box::pin(fut) } - _ => Box::pin(async move { - Ok(http::Response::builder() - .status(200) - .header("grpc-status", "12") - .header("content-type", "application/grpc") - .body(empty_body()) - .unwrap()) - }), + _ => { + Box::pin(async move { + Ok( + http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap(), + ) + }) + } } } } diff --git a/bpfman-api/src/config.rs b/bpfman-api/src/config.rs deleted file mode 100644 index eeb64aa7c..000000000 --- a/bpfman-api/src/config.rs +++ /dev/null @@ -1,273 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use std::{collections::HashMap, str::FromStr}; - -use aya::programs::XdpFlags; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use crate::util::directories::*; - -#[derive(Debug, Deserialize, Default, Clone)] -pub struct Config { - pub interfaces: Option>, - #[serde(default)] - pub grpc: Grpc, - pub signing: Option, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct SigningConfig { - pub allow_unsigned: bool, -} - -impl Default for SigningConfig { - fn default() -> Self { - Self { - // Allow unsigned programs by default - allow_unsigned: true, - } - } -} - -#[derive(Debug, Error)] -pub enum ConfigError { - #[error("Error parsing config file: {0}")] - ParseError(#[from] toml::de::Error), -} - -impl FromStr for Config { - type Err = ConfigError; - - fn from_str(s: &str) -> Result { - toml::from_str(s).map_err(ConfigError::ParseError) - } -} - -#[derive(Debug, Deserialize, Copy, Clone)] -pub struct InterfaceConfig { - pub xdp_mode: XdpMode, -} - -#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)] -#[serde(rename_all = "lowercase")] -pub enum XdpMode { - Skb, - Drv, - Hw, -} - -impl XdpMode { - pub fn as_flags(&self) -> XdpFlags { - match self { - XdpMode::Skb => XdpFlags::SKB_MODE, - XdpMode::Drv => XdpFlags::DRV_MODE, - XdpMode::Hw => XdpFlags::HW_MODE, - } - } -} - -impl ToString for XdpMode { - fn to_string(&self) -> String { - match self { - XdpMode::Skb => "skb".to_string(), - XdpMode::Drv => "drv".to_string(), - XdpMode::Hw => "hw".to_string(), - } - } -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Grpc { - #[serde(default)] - pub endpoints: Vec, -} - -impl Default for Grpc { - fn default() -> Self { - Self { - endpoints: vec![Endpoint::default()], - } - } -} - -#[derive(Debug, Deserialize, Clone)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum Endpoint { - Unix { - #[serde(default = "default_unix")] - path: String, - #[serde(default = "default_enabled")] - enabled: bool, - }, -} - -impl Default for Endpoint { - fn default() -> Self { - Endpoint::Unix { - path: default_unix(), - enabled: default_enabled(), - } - } -} - -fn default_unix() -> String { - RTPATH_BPFMAN_SOCKET.to_string() -} - -fn default_enabled() -> bool { - true -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_config_from_invalid_string() { - assert!(Config::from_str("i am a teapot").is_err()); - } - - #[test] - fn test_config_single_iface() { - let input = r#" - [interfaces] - [interfaces.eth0] - xdp_mode = "drv" - "#; - let config: Config = toml::from_str(input).expect("error parsing toml input"); - match config.interfaces { - Some(i) => { - assert!(i.contains_key("eth0")); - assert_eq!(i.get("eth0").unwrap().xdp_mode, XdpMode::Drv) - } - None => panic!("expected interfaces to be present"), - } - } - - #[test] - fn test_config_multiple_iface() { - let input = r#" - [interfaces] - [interfaces.eth0] - xdp_mode = "drv" - [interfaces.eth1] - xdp_mode = "hw" - [interfaces.eth2] - xdp_mode = "skb" - "#; - let config: Config = toml::from_str(input).expect("error parsing toml input"); - match config.interfaces { - Some(i) => { - assert_eq!(i.len(), 3); - assert!(i.contains_key("eth0")); - assert_eq!(i.get("eth0").unwrap().xdp_mode, XdpMode::Drv); - assert!(i.contains_key("eth1")); - assert_eq!(i.get("eth1").unwrap().xdp_mode, XdpMode::Hw); - assert!(i.contains_key("eth2")); - assert_eq!(i.get("eth2").unwrap().xdp_mode, XdpMode::Skb); - } - None => panic!("expected interfaces to be present"), - } - } - - #[test] - fn test_config_endpoint_default() { - let input = r#" - "#; - - let config: Config = toml::from_str(input).expect("error parsing toml input"); - let endpoints = config.grpc.endpoints; - assert_eq!(endpoints.len(), 1); - - match endpoints.get(0).unwrap() { - Endpoint::Unix { path, enabled } => { - assert_eq!(path, &default_unix()); - assert_eq!(enabled, &true); - } - } - } - - #[test] - fn test_config_endpoint_unix_default() { - let input = r#" - [[grpc.endpoints]] - type = "unix" - "#; - - let config: Config = toml::from_str(input).expect("error parsing toml input"); - let endpoints = config.grpc.endpoints; - assert_eq!(endpoints.len(), 1); - - match endpoints.get(0).unwrap() { - Endpoint::Unix { path, enabled } => { - assert_eq!(path, &default_unix()); - assert!(enabled); - } - } - } - - #[test] - fn test_config_endpoint_unix() { - let input = r#" - [[grpc.endpoints]] - type = "unix" - path = "/tmp/socket" - "#; - - let config: Config = toml::from_str(input).expect("error parsing toml input"); - let endpoints = config.grpc.endpoints; - assert_eq!(endpoints.len(), 1); - - match endpoints.get(0).unwrap() { - Endpoint::Unix { path, enabled } => { - assert_eq!(path, "/tmp/socket"); - assert!(enabled); - } - } - } - - #[test] - fn test_config_endpoint() { - let input = r#" - [[grpc.endpoints]] - type = "unix" - enabled = true - path = "/run/bpfman/bpfman.sock" - - [[grpc.endpoints]] - type = "unix" - enabled = true - path = "/run/bpfman/bpfman2.sock" - "#; - - let expected_endpoints: Vec = vec![ - Endpoint::Unix { - path: String::from("/run/bpfman/bpfman.sock"), - enabled: true, - }, - Endpoint::Unix { - path: String::from("/run/bpfman/bpfman2.sock"), - enabled: true, - }, - ]; - - let config: Config = toml::from_str(input).expect("error parsing toml input"); - let endpoints = config.grpc.endpoints; - assert_eq!(endpoints.len(), 2); - - for (i, endpoint) in endpoints.iter().enumerate() { - match endpoint { - Endpoint::Unix { path, enabled } => { - let Endpoint::Unix { - path: expected_path, - enabled: expected_enabled, - } = expected_endpoints.get(i).unwrap(); - assert_eq!(path, expected_path); - assert_eq!(enabled, expected_enabled); - } - } - } - } -} diff --git a/bpfman-api/src/lib.rs b/bpfman-api/src/lib.rs index c706bbd3a..3cae70666 100644 --- a/bpfman-api/src/lib.rs +++ b/bpfman-api/src/lib.rs @@ -1,585 +1,155 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of bpfman -pub mod config; -pub mod util; +use bpfman::{ + errors::BpfmanError, + types::{BytecodeImage, Location, Program}, +}; + +use crate::v1::{ + attach_info::Info, bytecode_location::Location as V1Location, AttachInfo, + BytecodeImage as V1BytecodeImage, BytecodeLocation, FentryAttachInfo, FexitAttachInfo, + KernelProgramInfo as V1KernelProgramInfo, KprobeAttachInfo, ProgramInfo, + ProgramInfo as V1ProgramInfo, TcAttachInfo, TracepointAttachInfo, UprobeAttachInfo, + XdpAttachInfo, +}; + #[path = "bpfman.v1.rs"] #[rustfmt::skip] #[allow(clippy::all)] pub mod v1; -use clap::ValueEnum; -use serde::{Deserialize, Serialize}; -use thiserror::Error; -use url::ParseError as urlParseError; -use v1::bytecode_location::Location; - -#[derive(Error, Debug)] -pub enum ParseError { - #[error("{program} is not a valid program type")] - InvalidProgramType { program: String }, - #[error("{proceedon} is not a valid proceed-on value")] - InvalidProceedOn { proceedon: String }, - #[error("not a valid direction: {direction}")] - InvalidDirection { direction: String }, - #[error("Failed to Parse bytecode location: {0}")] - BytecodeLocationParseFailure(#[source] urlParseError), - #[error("Invalid bytecode location: {location}")] - InvalidBytecodeLocation { location: String }, - #[error("Invalid bytecode image pull policy: {pull_policy}")] - InvalidBytecodeImagePullPolicy { pull_policy: String }, - #[error("{probe} is not a valid probe type")] - InvalidProbeType { probe: String }, -} - -#[derive(ValueEnum, Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub enum ProgramType { - Unspec, - SocketFilter, - Probe, // kprobe, kretprobe, uprobe, uretprobe - Tc, - SchedAct, - Tracepoint, - Xdp, - PerfEvent, - CgroupSkb, - CgroupSock, - LwtIn, - LwtOut, - LwtXmit, - SockOps, - SkSkb, - CgroupDevice, - SkMsg, - RawTracepoint, - CgroupSockAddr, - LwtSeg6Local, - LircMode2, - SkReuseport, - FlowDissector, - CgroupSysctl, - RawTracepointWritable, - CgroupSockopt, - Tracing, - StructOps, - Ext, - Lsm, - SkLookup, - Syscall, -} - -impl TryFrom for ProgramType { - type Error = ParseError; - - fn try_from(value: String) -> Result { - Ok(match value.as_str() { - "unspec" => ProgramType::Unspec, - "socket_filter" => ProgramType::SocketFilter, - "probe" => ProgramType::Probe, - "tc" => ProgramType::Tc, - "sched_act" => ProgramType::SchedAct, - "tracepoint" => ProgramType::Tracepoint, - "xdp" => ProgramType::Xdp, - "perf_event" => ProgramType::PerfEvent, - "cgroup_skb" => ProgramType::CgroupSkb, - "cgroup_sock" => ProgramType::CgroupSock, - "lwt_in" => ProgramType::LwtIn, - "lwt_out" => ProgramType::LwtOut, - "lwt_xmit" => ProgramType::LwtXmit, - "sock_ops" => ProgramType::SockOps, - "sk_skb" => ProgramType::SkSkb, - "cgroup_device" => ProgramType::CgroupDevice, - "sk_msg" => ProgramType::SkMsg, - "raw_tracepoint" => ProgramType::RawTracepoint, - "cgroup_sock_addr" => ProgramType::CgroupSockAddr, - "lwt_seg6local" => ProgramType::LwtSeg6Local, - "lirc_mode2" => ProgramType::LircMode2, - "sk_reuseport" => ProgramType::SkReuseport, - "flow_dissector" => ProgramType::FlowDissector, - "cgroup_sysctl" => ProgramType::CgroupSysctl, - "raw_tracepoint_writable" => ProgramType::RawTracepointWritable, - "cgroup_sockopt" => ProgramType::CgroupSockopt, - "tracing" => ProgramType::Tracing, - "struct_ops" => ProgramType::StructOps, - "ext" => ProgramType::Ext, - "lsm" => ProgramType::Lsm, - "sk_lookup" => ProgramType::SkLookup, - "syscall" => ProgramType::Syscall, - other => { - return Err(ParseError::InvalidProgramType { - program: other.to_string(), - }) - } - }) - } -} - -impl TryFrom for ProgramType { - type Error = ParseError; - fn try_from(value: u32) -> Result { - Ok(match value { - 0 => ProgramType::Unspec, - 1 => ProgramType::SocketFilter, - 2 => ProgramType::Probe, - 3 => ProgramType::Tc, - 4 => ProgramType::SchedAct, - 5 => ProgramType::Tracepoint, - 6 => ProgramType::Xdp, - 7 => ProgramType::PerfEvent, - 8 => ProgramType::CgroupSkb, - 9 => ProgramType::CgroupSock, - 10 => ProgramType::LwtIn, - 11 => ProgramType::LwtOut, - 12 => ProgramType::LwtXmit, - 13 => ProgramType::SockOps, - 14 => ProgramType::SkSkb, - 15 => ProgramType::CgroupDevice, - 16 => ProgramType::SkMsg, - 17 => ProgramType::RawTracepoint, - 18 => ProgramType::CgroupSockAddr, - 19 => ProgramType::LwtSeg6Local, - 20 => ProgramType::LircMode2, - 21 => ProgramType::SkReuseport, - 22 => ProgramType::FlowDissector, - 23 => ProgramType::CgroupSysctl, - 24 => ProgramType::RawTracepointWritable, - 25 => ProgramType::CgroupSockopt, - 26 => ProgramType::Tracing, - 27 => ProgramType::StructOps, - 28 => ProgramType::Ext, - 29 => ProgramType::Lsm, - 30 => ProgramType::SkLookup, - 31 => ProgramType::Syscall, - other => { - return Err(ParseError::InvalidProgramType { - program: other.to_string(), +impl TryFrom<&Program> for ProgramInfo { + type Error = BpfmanError; + + fn try_from(program: &Program) -> Result { + let data = program.get_data(); + + let bytecode = match data.get_location()? { + Location::Image(m) => { + Some(BytecodeLocation { + location: Some(V1Location::Image(V1BytecodeImage { + url: m.get_url().to_string(), + image_pull_policy: m.get_pull_policy().to_owned() as i32, + // Never dump Plaintext Credentials + username: Some(String::new()), + password: Some(String::new()), + })), }) } - }) - } -} - -impl std::fmt::Display for ProgramType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let v = match self { - ProgramType::Unspec => "unspec", - ProgramType::SocketFilter => "socket_filter", - ProgramType::Probe => "probe", - ProgramType::Tc => "tc", - ProgramType::SchedAct => "sched_act", - ProgramType::Tracepoint => "tracepoint", - ProgramType::Xdp => "xdp", - ProgramType::PerfEvent => "perf_event", - ProgramType::CgroupSkb => "cgroup_skb", - ProgramType::CgroupSock => "cgroup_sock", - ProgramType::LwtIn => "lwt_in", - ProgramType::LwtOut => "lwt_out", - ProgramType::LwtXmit => "lwt_xmit", - ProgramType::SockOps => "sock_ops", - ProgramType::SkSkb => "sk_skb", - ProgramType::CgroupDevice => "cgroup_device", - ProgramType::SkMsg => "sk_msg", - ProgramType::RawTracepoint => "raw_tracepoint", - ProgramType::CgroupSockAddr => "cgroup_sock_addr", - ProgramType::LwtSeg6Local => "lwt_seg6local", - ProgramType::LircMode2 => "lirc_mode2", - ProgramType::SkReuseport => "sk_reuseport", - ProgramType::FlowDissector => "flow_dissector", - ProgramType::CgroupSysctl => "cgroup_sysctl", - ProgramType::RawTracepointWritable => "raw_tracepoint_writable", - ProgramType::CgroupSockopt => "cgroup_sockopt", - ProgramType::Tracing => "tracing", - ProgramType::StructOps => "struct_ops", - ProgramType::Ext => "ext", - ProgramType::Lsm => "lsm", - ProgramType::SkLookup => "sk_lookup", - ProgramType::Syscall => "syscall", + Location::File(m) => Some(BytecodeLocation { + location: Some(V1Location::File(m.to_string())), + }), }; - write!(f, "{v}") - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub enum ProbeType { - Kprobe, - Kretprobe, - Uprobe, - Uretprobe, -} - -impl TryFrom for ProbeType { - type Error = ParseError; - - fn try_from(value: i32) -> Result { - Ok(match value { - 0 => ProbeType::Kprobe, - 1 => ProbeType::Kretprobe, - 2 => ProbeType::Uprobe, - 3 => ProbeType::Uretprobe, - other => { - return Err(ParseError::InvalidProbeType { - probe: other.to_string(), - }) - } - }) - } -} -impl From for ProbeType { - fn from(value: aya::programs::ProbeKind) -> Self { - match value { - aya::programs::ProbeKind::KProbe => ProbeType::Kprobe, - aya::programs::ProbeKind::KRetProbe => ProbeType::Kretprobe, - aya::programs::ProbeKind::UProbe => ProbeType::Uprobe, - aya::programs::ProbeKind::URetProbe => ProbeType::Uretprobe, - } - } -} - -impl std::fmt::Display for ProbeType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let v = match self { - ProbeType::Kprobe => "kprobe", - ProbeType::Kretprobe => "kretprobe", - ProbeType::Uprobe => "uprobe", - ProbeType::Uretprobe => "uretprobe", + let attach_info = AttachInfo { + info: match program.clone() { + Program::Xdp(p) => Some(Info::XdpAttachInfo(XdpAttachInfo { + priority: p.get_priority()?, + iface: p.get_iface()?.to_string(), + position: p.get_current_position()?.unwrap_or(0) as i32, + proceed_on: p.get_proceed_on()?.as_action_vec(), + })), + Program::Tc(p) => Some(Info::TcAttachInfo(TcAttachInfo { + priority: p.get_priority()?, + iface: p.get_iface()?.to_string(), + position: p.get_current_position()?.unwrap_or(0) as i32, + direction: p.get_direction()?.to_string(), + proceed_on: p.get_proceed_on()?.as_action_vec(), + })), + Program::Tracepoint(p) => Some(Info::TracepointAttachInfo(TracepointAttachInfo { + tracepoint: p.get_tracepoint()?.to_string(), + })), + Program::Kprobe(p) => Some(Info::KprobeAttachInfo(KprobeAttachInfo { + fn_name: p.get_fn_name()?.to_string(), + offset: p.get_offset()?, + retprobe: p.get_retprobe()?, + container_pid: p.get_container_pid()?, + })), + Program::Uprobe(p) => Some(Info::UprobeAttachInfo(UprobeAttachInfo { + fn_name: p.get_fn_name()?.map(|v| v.to_string()), + offset: p.get_offset()?, + target: p.get_target()?.to_string(), + retprobe: p.get_retprobe()?, + pid: p.get_pid()?, + container_pid: p.get_container_pid()?, + })), + Program::Fentry(p) => Some(Info::FentryAttachInfo(FentryAttachInfo { + fn_name: p.get_fn_name()?.to_string(), + })), + Program::Fexit(p) => Some(Info::FexitAttachInfo(FexitAttachInfo { + fn_name: p.get_fn_name()?.to_string(), + })), + Program::Unsupported(_) => None, + }, }; - write!(f, "{v}") - } -} - -#[derive(Serialize, Deserialize, Copy, Clone, Debug)] -pub enum XdpProceedOnEntry { - Aborted, - Drop, - Pass, - Tx, - Redirect, - DispatcherReturn = 31, -} -impl TryFrom for XdpProceedOnEntry { - type Error = ParseError; - fn try_from(value: String) -> Result { - Ok(match value.as_str() { - "aborted" => XdpProceedOnEntry::Aborted, - "drop" => XdpProceedOnEntry::Drop, - "pass" => XdpProceedOnEntry::Pass, - "tx" => XdpProceedOnEntry::Tx, - "redirect" => XdpProceedOnEntry::Redirect, - "dispatcher_return" => XdpProceedOnEntry::DispatcherReturn, - proceedon => { - return Err(ParseError::InvalidProceedOn { - proceedon: proceedon.to_string(), - }) - } + // Populate the Program Info with bpfman data + Ok(V1ProgramInfo { + name: data.get_name()?.to_string(), + bytecode, + attach: Some(attach_info), + global_data: data.get_global_data()?, + map_owner_id: data.get_map_owner_id()?, + map_pin_path: data + .get_map_pin_path()? + .map_or(String::new(), |v| v.to_str().unwrap().to_string()), + map_used_by: data + .get_maps_used_by()? + .iter() + .map(|m| m.to_string()) + .collect(), + metadata: data.get_metadata()?, }) } } -impl TryFrom for XdpProceedOnEntry { - type Error = ParseError; - fn try_from(value: i32) -> Result { - Ok(match value { - 0 => XdpProceedOnEntry::Aborted, - 1 => XdpProceedOnEntry::Drop, - 2 => XdpProceedOnEntry::Pass, - 3 => XdpProceedOnEntry::Tx, - 4 => XdpProceedOnEntry::Redirect, - 31 => XdpProceedOnEntry::DispatcherReturn, - proceedon => { - return Err(ParseError::InvalidProceedOn { - proceedon: proceedon.to_string(), - }) - } - }) - } -} - -impl std::fmt::Display for XdpProceedOnEntry { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let v = match self { - XdpProceedOnEntry::Aborted => "aborted", - XdpProceedOnEntry::Drop => "drop", - XdpProceedOnEntry::Pass => "pass", - XdpProceedOnEntry::Tx => "tx", - XdpProceedOnEntry::Redirect => "redirect", - XdpProceedOnEntry::DispatcherReturn => "dispatcher_return", - }; - write!(f, "{v}") - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct XdpProceedOn(Vec); -impl Default for XdpProceedOn { - fn default() -> Self { - XdpProceedOn(vec![ - XdpProceedOnEntry::Pass, - XdpProceedOnEntry::DispatcherReturn, - ]) - } -} - -impl XdpProceedOn { - pub fn from_strings>(values: T) -> Result { - let entries = values.as_ref(); - let mut res = vec![]; - for e in entries { - res.push(e.to_owned().try_into()?) - } - Ok(XdpProceedOn(res)) - } - - pub fn from_int32s>(values: T) -> Result { - let entries = values.as_ref(); - if entries.is_empty() { - return Ok(XdpProceedOn::default()); - } - let mut res = vec![]; - for e in entries { - res.push((*e).try_into()?) - } - Ok(XdpProceedOn(res)) - } - - pub fn mask(&self) -> u32 { - let mut proceed_on_mask: u32 = 0; - for action in self.0.clone().into_iter() { - proceed_on_mask |= 1 << action as u32; - } - proceed_on_mask - } - - pub fn as_action_vec(&self) -> Vec { - let mut res = vec![]; - for entry in &self.0 { - res.push((*entry) as i32) - } - res - } -} - -impl std::fmt::Display for XdpProceedOn { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let res: Vec = self.0.iter().map(|x| x.to_string()).collect(); - write!(f, "{}", res.join(", ")) - } -} - -#[derive(Serialize, Deserialize, Copy, Clone, Debug)] -pub enum TcProceedOnEntry { - Unspec = -1, - Ok = 0, - Reclassify, - Shot, - Pipe, - Stolen, - Queued, - Repeat, - Redirect, - Trap, - DispatcherReturn = 30, -} - -impl TryFrom for TcProceedOnEntry { - type Error = ParseError; - fn try_from(value: String) -> Result { - Ok(match value.as_str() { - "unspec" => TcProceedOnEntry::Unspec, - "ok" => TcProceedOnEntry::Ok, - "reclassify" => TcProceedOnEntry::Reclassify, - "shot" => TcProceedOnEntry::Shot, - "pipe" => TcProceedOnEntry::Pipe, - "stolen" => TcProceedOnEntry::Stolen, - "queued" => TcProceedOnEntry::Queued, - "repeat" => TcProceedOnEntry::Repeat, - "redirect" => TcProceedOnEntry::Redirect, - "trap" => TcProceedOnEntry::Trap, - "dispatcher_return" => TcProceedOnEntry::DispatcherReturn, - proceedon => { - return Err(ParseError::InvalidProceedOn { - proceedon: proceedon.to_string(), - }) - } +impl TryFrom<&Program> for V1KernelProgramInfo { + type Error = BpfmanError; + + fn try_from(program: &Program) -> Result { + // Get the Kernel Info. + let data = program.get_data(); + + // Populate the Kernel Info. + Ok(V1KernelProgramInfo { + id: data.get_id()?, + name: data.get_kernel_name()?.to_string(), + program_type: program.kind() as u32, + loaded_at: data.get_kernel_loaded_at()?.to_string(), + tag: data.get_kernel_tag()?.to_string(), + gpl_compatible: data.get_kernel_gpl_compatible()?, + map_ids: data.get_kernel_map_ids()?, + btf_id: data.get_kernel_btf_id()?, + bytes_xlated: data.get_kernel_bytes_xlated()?, + jited: data.get_kernel_jited()?, + bytes_jited: data.get_kernel_bytes_jited()?, + bytes_memlock: data.get_kernel_bytes_memlock()?, + verified_insns: data.get_kernel_verified_insns()?, }) } } -impl TryFrom for TcProceedOnEntry { - type Error = ParseError; - fn try_from(value: i32) -> Result { - Ok(match value { - -1 => TcProceedOnEntry::Unspec, - 0 => TcProceedOnEntry::Ok, - 1 => TcProceedOnEntry::Reclassify, - 2 => TcProceedOnEntry::Shot, - 3 => TcProceedOnEntry::Pipe, - 4 => TcProceedOnEntry::Stolen, - 5 => TcProceedOnEntry::Queued, - 6 => TcProceedOnEntry::Repeat, - 7 => TcProceedOnEntry::Redirect, - 8 => TcProceedOnEntry::Trap, - 30 => TcProceedOnEntry::DispatcherReturn, - proceedon => { - return Err(ParseError::InvalidProceedOn { - proceedon: proceedon.to_string(), - }) +impl From for BytecodeImage { + fn from(value: V1BytecodeImage) -> Self { + // This function is mapping an empty string to None for + // username and password. + let username = if value.username.is_some() { + match value.username.unwrap().as_ref() { + "" => None, + u => Some(u.to_string()), } - }) - } -} - -impl std::fmt::Display for TcProceedOnEntry { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let v = match self { - TcProceedOnEntry::Unspec => "unspec", - TcProceedOnEntry::Ok => "ok", - TcProceedOnEntry::Reclassify => "reclassify", - TcProceedOnEntry::Shot => "shot", - TcProceedOnEntry::Pipe => "pipe", - TcProceedOnEntry::Stolen => "stolen", - TcProceedOnEntry::Queued => "queued", - TcProceedOnEntry::Repeat => "repeat", - TcProceedOnEntry::Redirect => "redirect", - TcProceedOnEntry::Trap => "trap", - TcProceedOnEntry::DispatcherReturn => "dispatcher_return", + } else { + None }; - write!(f, "{v}") - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct TcProceedOn(pub(crate) Vec); -impl Default for TcProceedOn { - fn default() -> Self { - TcProceedOn(vec![ - TcProceedOnEntry::Pipe, - TcProceedOnEntry::DispatcherReturn, - ]) - } -} - -impl TcProceedOn { - pub fn from_strings>(values: T) -> Result { - let entries = values.as_ref(); - let mut res = vec![]; - for e in entries { - res.push(e.to_owned().try_into()?) - } - Ok(TcProceedOn(res)) - } - - pub fn from_int32s>(values: T) -> Result { - let entries = values.as_ref(); - if entries.is_empty() { - return Ok(TcProceedOn::default()); - } - let mut res = vec![]; - for e in entries { - res.push((*e).try_into()?) - } - Ok(TcProceedOn(res)) - } - - // Valid TC return values range from -1 to 8. Since -1 is not a valid shift value, - // 1 is added to the value to determine the bit to set in the bitmask and, - // correspondingly, The TC dispatcher adds 1 to the return value from the BPF program - // before it compares it to the configured bit mask. - pub fn mask(&self) -> u32 { - let mut proceed_on_mask: u32 = 0; - for action in self.0.clone().into_iter() { - proceed_on_mask |= 1 << ((action as i32) + 1); - } - proceed_on_mask - } - - pub fn as_action_vec(&self) -> Vec { - let mut res = vec![]; - for entry in &self.0 { - res.push((*entry) as i32) - } - res - } -} - -impl std::fmt::Display for TcProceedOn { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let res: Vec = self.0.iter().map(|x| x.to_string()).collect(); - write!(f, "{}", res.join(", ")) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub enum ImagePullPolicy { - Always, - IfNotPresent, - Never, -} - -impl std::fmt::Display for ImagePullPolicy { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let v = match self { - ImagePullPolicy::Always => "Always", - ImagePullPolicy::IfNotPresent => "IfNotPresent", - ImagePullPolicy::Never => "Never", - }; - write!(f, "{v}") - } -} - -impl TryFrom for ImagePullPolicy { - type Error = ParseError; - fn try_from(value: i32) -> Result { - Ok(match value { - 0 => ImagePullPolicy::Always, - 1 => ImagePullPolicy::IfNotPresent, - 2 => ImagePullPolicy::Never, - policy => { - return Err(ParseError::InvalidBytecodeImagePullPolicy { - pull_policy: policy.to_string(), - }) + let password = if value.password.is_some() { + match value.password.unwrap().as_ref() { + "" => None, + u => Some(u.to_string()), } - }) - } -} - -impl TryFrom<&str> for ImagePullPolicy { - type Error = ParseError; - fn try_from(value: &str) -> Result { - Ok(match value { - "Always" => ImagePullPolicy::Always, - "IfNotPresent" => ImagePullPolicy::IfNotPresent, - "Never" => ImagePullPolicy::Never, - policy => { - return Err(ParseError::InvalidBytecodeImagePullPolicy { - pull_policy: policy.to_string(), - }) - } - }) - } -} - -impl From for i32 { - fn from(value: ImagePullPolicy) -> Self { - match value { - ImagePullPolicy::Always => 0, - ImagePullPolicy::IfNotPresent => 1, - ImagePullPolicy::Never => 2, - } - } -} - -impl ToString for Location { - fn to_string(&self) -> String { - match &self { - // Cast imagePullPolicy into it's concrete type so we can easily print. - Location::Image(i) => format!( - "image: {{ url: {}, pullpolicy: {} }}", - i.url, - TryInto::::try_into(i.image_pull_policy).unwrap() - ), - Location::File(p) => format!("file: {{ path: {p} }}"), - } + } else { + None + }; + BytecodeImage::new(value.url, value.image_pull_policy, username, password) } } diff --git a/bpfman-api/src/util.rs b/bpfman-api/src/util.rs deleted file mode 100644 index cf6dcad3a..000000000 --- a/bpfman-api/src/util.rs +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -pub mod directories { - // The following directories are used by bpfman. They should be created by bpfman service - // via the bpfman.service settings. They will be manually created in the case where bpfman - // is not being run as a service. - // - // ConfigurationDirectory: /etc/bpfman/ - pub const CFGDIR_MODE: u32 = 0o6750; - pub const CFGDIR: &str = "/etc/bpfman"; - pub const CFGDIR_STATIC_PROGRAMS: &str = "/etc/bpfman/programs.d"; - pub const CFGPATH_BPFMAN_CONFIG: &str = "/etc/bpfman/bpfman.toml"; - pub const CFGPATH_CA_CERTS_PEM: &str = "/etc/bpfman/certs/ca/ca.pem"; - pub const CFGPATH_CA_CERTS_KEY: &str = "/etc/bpfman/certs/ca/ca.key"; - pub const CFGPATH_BPFMAN_CERTS_PEM: &str = "/etc/bpfman/certs/bpfman/bpfman.pem"; - pub const CFGPATH_BPFMAN_CERTS_KEY: &str = "/etc/bpfman/certs/bpfman/bpfman.key"; - pub const CFGPATH_BPFMAN_CLIENT_CERTS_PEM: &str = - "/etc/bpfman/certs/bpfman-client/bpfman-client.pem"; - pub const CFGPATH_BPFMAN_CLIENT_CERTS_KEY: &str = - "/etc/bpfman/certs/bpfman-client/bpfman-client.key"; - - // RuntimeDirectory: /run/bpfman/ - pub const RTDIR_MODE: u32 = 0o6770; - pub const RTDIR: &str = "/run/bpfman"; - pub const RTDIR_XDP_DISPATCHER: &str = "/run/bpfman/dispatchers/xdp"; - pub const RTDIR_TC_INGRESS_DISPATCHER: &str = "/run/bpfman/dispatchers/tc-ingress"; - pub const RTDIR_TC_EGRESS_DISPATCHER: &str = "/run/bpfman/dispatchers/tc-egress"; - pub const RTDIR_FS: &str = "/run/bpfman/fs"; - pub const RTDIR_FS_TC_INGRESS: &str = "/run/bpfman/fs/tc-ingress"; - pub const RTDIR_FS_TC_EGRESS: &str = "/run/bpfman/fs/tc-egress"; - pub const RTDIR_FS_XDP: &str = "/run/bpfman/fs/xdp"; - pub const RTDIR_FS_MAPS: &str = "/run/bpfman/fs/maps"; - pub const RTDIR_PROGRAMS: &str = "/run/bpfman/programs"; - pub const RTPATH_BPFMAN_SOCKET: &str = "/run/bpfman/bpfman.sock"; - // The CSI socket must be in it's own sub directory so we can easily create a dedicated - // K8s volume mount for it. - pub const RTDIR_BPFMAN_CSI: &str = "/run/bpfman/csi"; - pub const RTPATH_BPFMAN_CSI_SOCKET: &str = "/run/bpfman/csi/csi.sock"; - pub const RTDIR_BPFMAN_CSI_FS: &str = "/run/bpfman/csi/fs"; - - // StateDirectory: /var/lib/bpfman/ - pub const STDIR_MODE: u32 = 0o6770; - pub const STDIR: &str = "/var/lib/bpfman"; - pub const STDIR_BYTECODE_IMAGE_CONTENT_STORE: &str = "/var/lib/bpfman/io.bpfman.image.content"; -} diff --git a/bpfman-ns/Cargo.toml b/bpfman-ns/Cargo.toml new file mode 100644 index 000000000..694d88033 --- /dev/null +++ b/bpfman-ns/Cargo.toml @@ -0,0 +1,27 @@ +[package] +description = "An executable to attach an eBPF program inside a container" +edition.workspace = true +license.workspace = true +name = "bpfman-ns" +repository.workspace = true +version.workspace = true + +[[bin]] +name = "bpfman-ns" +path = "src/main.rs" + +[dependencies] +anyhow = { workspace = true, features = ["std"] } +aya = { workspace = true } +caps = { workspace = true } +clap = { workspace = true, features = [ + "color", + "derive", + "help", + "std", + "suggestions", + "usage", +] } +env_logger = { workspace = true } +log = { workspace = true } +nix = { workspace = true, features = ["sched"] } diff --git a/bpfman-ns/src/main.rs b/bpfman-ns/src/main.rs new file mode 100644 index 000000000..8eeb83367 --- /dev/null +++ b/bpfman-ns/src/main.rs @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: (MIT OR Apache-2.0) +// Copyright Authors of bpfman + +use std::{fs::File, process}; + +use anyhow::{bail, Context}; +use aya::programs::{links::FdLink, uprobe::UProbeLink, ProbeKind, UProbe}; +use clap::{Args, Parser, Subcommand}; +use log::debug; +use nix::sched::{setns, CloneFlags}; + +#[derive(Debug, Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(subcommand)] + command: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + /// Attach a uprobe program in the given container. + Uprobe(UprobeArgs), + // TODO: add additional commands: Kprobe, etc. +} + +#[derive(Debug, Args)] +struct UprobeArgs { + /// Required: path to pinned entry for bpf program on a bpffs. + #[clap(short, long, verbatim_doc_comment)] + program_pin_path: String, + + /// Optional: Function to attach the uprobe to. + #[clap(short, long)] + fn_name: Option, + + /// Required: Offset added to the address of the target function (or + /// beginning of target if no function is identified). Offsets are supported + /// for uretprobes, but use with caution because they can result in + /// unintended side effects. This should be set to zero (0) if no offset + /// is wanted. + #[clap(short, long, verbatim_doc_comment)] + offset: u64, + + /// Required: Library name or the absolute path to a binary or library. + /// Example: --target "libc". + #[clap(short, long, verbatim_doc_comment)] + target: String, + + /// Optional: Whether the program is a uretprobe. + /// [default: false] + #[clap(short, long, verbatim_doc_comment)] + retprobe: bool, + + /// Optional: Only execute uprobe for given process identification number + /// (PID). If PID is not provided, uprobe executes for all PIDs. + #[clap(long, verbatim_doc_comment)] + pid: Option, + + /// Required: Host PID of the container to attach the uprobe in. + #[clap(short, long)] + container_pid: i32, +} + +fn main() -> anyhow::Result<()> { + env_logger::init(); + + has_cap(caps::CapSet::Effective, caps::Capability::CAP_BPF); + has_cap(caps::CapSet::Effective, caps::Capability::CAP_SYS_ADMIN); + has_cap(caps::CapSet::Effective, caps::Capability::CAP_SYS_CHROOT); + + let bpfman_pid = process::id(); + + let cli = Cli::parse(); + match cli.command { + Commands::Uprobe(args) => execute_uprobe_attach(args, bpfman_pid), + } +} + +fn has_cap(cset: caps::CapSet, cap: caps::Capability) { + debug!("Has {}: {}", cap, caps::has_cap(None, cset, cap).unwrap()); +} + +fn execute_uprobe_attach(args: UprobeArgs, bpfman_pid: u32) -> anyhow::Result<()> { + debug!( + "attempting to attach uprobe in container with pid {}", + args.container_pid + ); + + let bpfman_mnt_file = match File::open(format!("/proc/{}/ns/mnt", bpfman_pid)) { + Ok(file) => file, + Err(e) => { + bail!("error opening bpfman file: {e}"); + } + }; + + // First check if the file exists at /proc, which is where it should be + // when running natively on a linux host. + let target_mnt_file = match File::open(format!("/proc/{}/ns/mnt", args.container_pid)) { + Ok(file) => file, + // If that doesn't work, check for it in /host/proc, which is where it should + // be in a kubernetes deployment. + Err(_) => match File::open(format!("/host/proc/{}/ns/mnt", args.container_pid)) { + Ok(file) => file, + Err(e) => { + bail!("error opening target file: {e}"); + } + }, + }; + + let mut uprobe = UProbe::from_pin(args.program_pin_path.clone(), ProbeKind::UProbe) + .context("failed to get UProbe from pin file")?; + + // Set namespace to target namespace + set_ns( + target_mnt_file, + CloneFlags::CLONE_NEWNS, + args.container_pid as u32, + )?; + + let attach_result = uprobe.attach(args.fn_name.as_deref(), args.offset, args.target, args.pid); + + let link_id = match attach_result { + Ok(l) => l, + Err(e) => { + bail!("error attaching uprobe: {e}"); + } + }; + + // Set namespace back to bpfman namespace + set_ns(bpfman_mnt_file, CloneFlags::CLONE_NEWNS, bpfman_pid)?; + + let owned_link: UProbeLink = uprobe + .take_link(link_id) + .expect("take_link failed for uprobe"); + let fd_link: FdLink = owned_link + .try_into() + .expect("unable to get owned uprobe attach link"); + + fd_link.pin(format!("{}_link", args.program_pin_path))?; + + Ok(()) +} + +fn set_ns(file: File, nstype: CloneFlags, pid: u32) -> anyhow::Result<()> { + let setns_result = setns(file, nstype); + match setns_result { + Ok(_) => Ok(()), + Err(e) => { + bail!( + "error setting ns to PID {} {:?} namespace. error: {}", + pid, + nstype, + e + ); + } + } +} diff --git a/bpfman-operator/.gitignore b/bpfman-operator/.gitignore index fe834879f..eef19ba50 100644 --- a/bpfman-operator/.gitignore +++ b/bpfman-operator/.gitignore @@ -25,3 +25,4 @@ Dockerfile.cross *.swp *.swo *~ +apidocs.html diff --git a/bpfman-operator/Containerfile.bpfman-agent b/bpfman-operator/Containerfile.bpfman-agent index 00e26730c..209830332 100644 --- a/bpfman-operator/Containerfile.bpfman-agent +++ b/bpfman-operator/Containerfile.bpfman-agent @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.19 as bpfman-agent-build +FROM golang:1.21 as bpfman-agent-build ARG TARGETOS ARG TARGETARCH @@ -24,14 +24,26 @@ COPY . . WORKDIR /usr/src/bpfman/bpfman-operator RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o bpfman-agent ./cmd/bpfman-agent/main.go -# Use distroless as minimal base image to package the manager binary -# Refer to https://github.com/GoogleContainerTools/distroless for more details -# Uncomment for debug build -# FROM gcr.io/distroless/static:debug +# Use the fedora minimal image to reduce the size of the final image but still +# be able to easily install extra packages. +FROM quay.io/fedora/fedora-minimal +ARG DNF_CMD="microdnf" -FROM gcr.io/distroless/static:nonroot +# The full fedora image can be used for debugging purposes. To use it, comment +# out the FROM and ARG lines above and uncomment the FROM and ARG lines below. +# FROM fedora:38 +# ARG DNF_CMD="dnf" + +ARG TARGETARCH WORKDIR / COPY --from=bpfman-agent-build /usr/src/bpfman/bpfman-operator/bpfman-agent . -USER 65532:65532 + +# Install crictl +RUN ${DNF_CMD} -y install wget tar +ARG VERSION="v1.28.0" +RUN wget https://github.com/kubernetes-sigs/cri-tools/releases/download/${VERSION}/crictl-${VERSION}-linux-${TARGETARCH}.tar.gz +RUN tar zxvf crictl-${VERSION}-linux-${TARGETARCH}.tar.gz -C /usr/local/bin +RUN rm -f crictl-${VERSION}-linux-${TARGETARCH}.tar.gz +RUN ${DNF_CMD} -y clean all ENTRYPOINT ["/bpfman-agent"] diff --git a/bpfman-operator/Containerfile.bpfman-operator b/bpfman-operator/Containerfile.bpfman-operator index 275fbc2d5..db327be54 100644 --- a/bpfman-operator/Containerfile.bpfman-operator +++ b/bpfman-operator/Containerfile.bpfman-operator @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.19 as bpfman-operator-build +FROM golang:1.21 as bpfman-operator-build ARG TARGETOS ARG TARGETARCH @@ -24,12 +24,14 @@ COPY . . WORKDIR /usr/src/bpfman/bpfman-operator RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o bpfman-operator ./cmd/bpfman-operator/main.go -# Use distroless as minimal base image to package the manager binary -# Refer to https://github.com/GoogleContainerTools/distroless for more details -# Uncomment for debug build -# FROM gcr.io/distroless/static:debug +# Use the fedora minimal image to reduce the size of the final image but still +# be able to easily install extra packages. -FROM gcr.io/distroless/static:nonroot +# The full fedora image can be used for debugging purposes, but you may need to +# change "microdnf" to "dnf" below to install extra packages. +# FROM fedora:38 +FROM quay.io/fedora/fedora-minimal +ARG TARGETARCH WORKDIR / COPY --from=bpfman-operator-build /usr/src/bpfman/bpfman-operator/config/bpfman-deployment/daemonset.yaml ./config/bpfman-deployment/daemonset.yaml COPY --from=bpfman-operator-build /usr/src/bpfman/bpfman-operator/config/bpfman-deployment/csidriverinfo.yaml ./config/bpfman-deployment/csidriverinfo.yaml diff --git a/bpfman-operator/Makefile b/bpfman-operator/Makefile index 519f61e62..99cdaead4 100644 --- a/bpfman-operator/Makefile +++ b/bpfman-operator/Makefile @@ -3,7 +3,7 @@ # To re-generate a bundle for another specific version without changing the standard setup, you can: # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) -VERSION ?= 0.3.1 +VERSION ?= 0.4.0 # CHANNELS define the bundle channels used in the bundle. # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") @@ -181,10 +181,7 @@ ifeq ($(VERIFY_CODEGEN), true) VERIFY_FLAG=--verify-only endif -OUTPUT_PKG ?= $(shell pwd)/bpfman-operator/pkg/client -APIS_PKG ?= $(shell pwd)/bpfman-operator -CLIENTSET_NAME ?= versioned -CLIENTSET_PKG_NAME ?= clientset +PKG ?= github.com/bpfman/bpfman/bpfman-operator COMMON_FLAGS ?= ${VERIFY_FLAG} --go-header-file $(shell pwd)/hack/boilerplate.go.txt .PHONY: manifests @@ -199,8 +196,8 @@ generate: manifests generate-register generate-deepcopy generate-typed-clients g .PHONY: generate-register generate-register: register-gen ## Generate register code see all `zz_generated.register.go` files. $(REGISTER_GEN) \ - --input-dirs "${APIS_PKG}/apis/v1alpha1" \ - --output-package "${APIS_PKG}/apis/" \ + --input-dirs "${PKG}/apis/v1alpha1" \ + --output-package "${PKG}/apis/" \ ${COMMON_FLAGS} .PHONY: generate-deepcopy @@ -210,28 +207,28 @@ generate-deepcopy: ## Generate code containing DeepCopy, DeepCopyInto, and DeepC .PHONY: generate-typed-clients generate-typed-clients: client-gen ## Generate typed client code $(CLIENT_GEN) \ - --clientset-name "${CLIENTSET_NAME}" \ + --clientset-name "versioned" \ --input-base "" \ - --input "${APIS_PKG}/apis/v1alpha1" \ - --output-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME}" \ + --input "${PKG}/apis/v1alpha1" \ + --output-package "${PKG}/pkg/client/clientset" \ ${COMMON_FLAGS} .PHONY: generate-typed-listers generate-typed-listers: lister-gen ## Generate typed listers code $(LISTER_GEN) \ - --input-dirs "${APIS_PKG}/apis/v1alpha1" \ - --output-package "${OUTPUT_PKG}/listers" \ + --input-dirs "${PKG}/apis/v1alpha1" \ + --output-package "${PKG}/pkg/client/listers" \ ${COMMON_FLAGS} .PHONY: generate-typed-informers generate-typed-informers: informer-gen ## Generate typed informers code $(INFORMER_GEN) \ - --input-dirs "${APIS_PKG}/apis/v1alpha1" \ - --versioned-clientset-package "${OUTPUT_PKG}/${CLIENTSET_PKG_NAME}/${CLIENTSET_NAME}" \ - --listers-package "${OUTPUT_PKG}/listers" \ - --output-package "${OUTPUT_PKG}/informers" \ + --input-dirs "${PKG}/apis/v1alpha1" \ + --versioned-clientset-package "${PKG}/pkg/client/clientset/versioned" \ + --listers-package "${PKG}/pkg/client/listers" \ + --output-package "${PKG}/pkg/client/informers" \ ${COMMON_FLAGS} .PHONY: fmt @@ -332,7 +329,7 @@ catalog-push: ## Push a catalog image. ##@ CRD Deployment -ignore-not-found ?= false +ignore-not-found ?= true .PHONY: install install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. @@ -357,6 +354,8 @@ deploy: manifests kustomize ## Deploy bpfman-operator to the K8s cluster specifi .PHONY: undeploy undeploy: ## Undeploy bpfman-operator from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + kubectl delete cm bpfman-config -n bpfman + sleep 5 # Wait for the operator to cleanup the daemonset $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - .PHONY: kind-reload-images @@ -378,3 +377,6 @@ deploy-openshift: manifests kustomize ## Deploy bpfman-operator to the Openshift .PHONY: undeploy-openshift undeploy-openshift: ## Undeploy bpfman-operator from the Openshift cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. $(KUSTOMIZE) build config/openshift | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +apidocs.html: + ./hack/api-docs/generate.sh $@ diff --git a/bpfman-operator/PROJECT b/bpfman-operator/PROJECT index 566139902..078c4d4dc 100644 --- a/bpfman-operator/PROJECT +++ b/bpfman-operator/PROJECT @@ -55,4 +55,20 @@ resources: kind: UprobeProgram path: github.com/bpfman/bpfman/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: false + controller: true + domain: bpfman.io + kind: FentryProgram + path: github.com/bpfman/bpfman/api/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: false + controller: true + domain: bpfman.io + kind: FexitProgram + path: github.com/bpfman/bpfman/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/bpfman-operator/apis/v1alpha1/bpfprogram_types.go b/bpfman-operator/apis/v1alpha1/bpfprogram_types.go index e0974c04f..9fa52c644 100644 --- a/bpfman-operator/apis/v1alpha1/bpfprogram_types.go +++ b/bpfman-operator/apis/v1alpha1/bpfprogram_types.go @@ -29,6 +29,9 @@ import ( //+kubebuilder:resource:scope=Cluster // BpfProgram is the Schema for the Bpfprograms API +// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" type BpfProgram struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/bpfman-operator/apis/v1alpha1/fentryProgram_types.go b/bpfman-operator/apis/v1alpha1/fentryProgram_types.go new file mode 100644 index 000000000..11105f8c9 --- /dev/null +++ b/bpfman-operator/apis/v1alpha1/fentryProgram_types.go @@ -0,0 +1,71 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +genclient:nonNamespaced +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Cluster + +// FentryProgram is the Schema for the FentryPrograms API +// +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="FunctionName",type=string,JSONPath=`.spec.func_name`,priority=1 +type FentryProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec FentryProgramSpec `json:"spec"` + // +optional + Status FentryProgramStatus `json:"status,omitempty"` +} + +// FentryProgramSpec defines the desired state of FentryProgram +// +kubebuilder:printcolumn:name="FunctionName",type=string,JSONPath=`.spec.func_name` +type FentryProgramSpec struct { + BpfProgramCommon `json:",inline"` + + // Function to attach the fentry to. + FunctionName string `json:"func_name"` +} + +// FentryProgramStatus defines the observed state of FentryProgram +type FentryProgramStatus struct { + // Conditions houses the global cluster state for the FentryProgram. The explicit + // condition types are defined internally. + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +} + +// +kubebuilder:object:root=true +// FentryProgramList contains a list of FentryPrograms +type FentryProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []FentryProgram `json:"items"` +} diff --git a/bpfman-operator/apis/v1alpha1/fexitProgram_types.go b/bpfman-operator/apis/v1alpha1/fexitProgram_types.go new file mode 100644 index 000000000..d723f2d67 --- /dev/null +++ b/bpfman-operator/apis/v1alpha1/fexitProgram_types.go @@ -0,0 +1,71 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// All fields are required unless explicitly marked optional +// +kubebuilder:validation:Required +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +genclient:nonNamespaced +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Cluster + +// FexitProgram is the Schema for the FexitPrograms API +// +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` +// +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` +// +kubebuilder:printcolumn:name="FunctionName",type=string,JSONPath=`.spec.func_name`,priority=1 +type FexitProgram struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec FexitProgramSpec `json:"spec"` + // +optional + Status FexitProgramStatus `json:"status,omitempty"` +} + +// FexitProgramSpec defines the desired state of FexitProgram +// +kubebuilder:printcolumn:name="FunctionName",type=string,JSONPath=`.spec.func_name` +type FexitProgramSpec struct { + BpfProgramCommon `json:",inline"` + + // Function to attach the fexit to. + FunctionName string `json:"func_name"` +} + +// FexitProgramStatus defines the observed state of FexitProgram +type FexitProgramStatus struct { + // Conditions houses the global cluster state for the FexitProgram. The explicit + // condition types are defined internally. + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +} + +// +kubebuilder:object:root=true +// FexitProgramList contains a list of FexitPrograms +type FexitProgramList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []FexitProgram `json:"items"` +} diff --git a/bpfman-operator/apis/v1alpha1/kprobeProgram_types.go b/bpfman-operator/apis/v1alpha1/kprobeProgram_types.go index 500bb72ee..a72759a52 100644 --- a/bpfman-operator/apis/v1alpha1/kprobeProgram_types.go +++ b/bpfman-operator/apis/v1alpha1/kprobeProgram_types.go @@ -31,6 +31,7 @@ import ( // KprobeProgram is the Schema for the KprobePrograms API // +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` // +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` // +kubebuilder:printcolumn:name="FunctionName",type=string,JSONPath=`.spec.func_name`,priority=1 // +kubebuilder:printcolumn:name="Offset",type=integer,JSONPath=`.spec.offset`,priority=1 // +kubebuilder:printcolumn:name="RetProbe",type=boolean,JSONPath=`.spec.retprobe`,priority=1 @@ -52,7 +53,7 @@ type KprobeProgramSpec struct { BpfProgramCommon `json:",inline"` // Functions to attach the kprobe to. - FunctionNames []string `json:"func_names"` + FunctionName string `json:"func_name"` // Offset added to the address of the function for kprobe. // Not allowed for kretprobes. @@ -65,9 +66,9 @@ type KprobeProgramSpec struct { // +kubebuilder:default:=false RetProbe bool `json:"retprobe"` - // // Namespace to attach the uprobe in. (Not supported yet by bpfman.) + // // Host PID of container to attach the uprobe in. (Not supported yet by bpfman.) // // +optional - // Namespace string `json:"namespace"` + // ContainerPid string `json:"containerpid"` } // KprobeProgramStatus defines the observed state of KprobeProgram diff --git a/bpfman-operator/apis/v1alpha1/shared_types.go b/bpfman-operator/apis/v1alpha1/shared_types.go index ddefc597c..f92bf3e5c 100644 --- a/bpfman-operator/apis/v1alpha1/shared_types.go +++ b/bpfman-operator/apis/v1alpha1/shared_types.go @@ -35,6 +35,24 @@ type InterfaceSelector struct { PrimaryNodeInterface *bool `json:"primarynodeinterface,omitempty"` } +// ContainerSelector identifies a set of containers. For example, this can be +// used to identify a set of containers in which to attach uprobes. +type ContainerSelector struct { + // Target namespaces. + // +optional + // +kubebuilder:default:="" + Namespace string `json:"namespace"` + + // Target pods. This field must be specified, to select all pods use + // standard metav1.LabelSelector semantics and make it empty. + Pods metav1.LabelSelector `json:"pods"` + + // Name(s) of container(s). If none are specified, all containers in the + // pod are selected. + // +optional + ContainerNames *[]string `json:"containernames,omitempty"` +} + // BpfProgramCommon defines the common attributes for all BPF programs type BpfProgramCommon struct { // BpfFunctionName is the name of the function that is the entry point for the BPF @@ -243,6 +261,10 @@ const ( // process the bytecode selector. BpfProgCondBytecodeSelectorError BpfProgramConditionType = "BytecodeSelectorError" + // BpfProgCondNoContainersOnNode indicates that there are no containers on the node + // that match the container selector. + BpfProgCondNoContainersOnNode BpfProgramConditionType = "NoContainersOnNode" + // None of the above conditions apply BpfProgCondNone BpfProgramConditionType = "None" ) @@ -311,6 +333,14 @@ func (b BpfProgramConditionType) Condition() metav1.Condition { Message: "There was an error processing the provided bytecode selector", } + case BpfProgCondNoContainersOnNode: + cond = metav1.Condition{ + Type: string(BpfProgCondNoContainersOnNode), + Status: metav1.ConditionTrue, + Reason: "noContainersOnNode", + Message: "There are no containers on the node that match the container selector", + } + case BpfProgCondNone: cond = metav1.Condition{ Type: string(BpfProgCondNone), diff --git a/bpfman-operator/apis/v1alpha1/tcProgram_types.go b/bpfman-operator/apis/v1alpha1/tcProgram_types.go index be7150d0d..37245d1c6 100644 --- a/bpfman-operator/apis/v1alpha1/tcProgram_types.go +++ b/bpfman-operator/apis/v1alpha1/tcProgram_types.go @@ -31,6 +31,7 @@ import ( // TcProgram is the Schema for the TcProgram API // +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` // +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` // +kubebuilder:printcolumn:name="Priority",type=string,JSONPath=`.spec.priority`,priority=1 // +kubebuilder:printcolumn:name="Direction",type=string,JSONPath=`.spec.direction`,priority=1 // +kubebuilder:printcolumn:name="InterfaceSelector",type=string,JSONPath=`.spec.interfaceselector`,priority=1 diff --git a/bpfman-operator/apis/v1alpha1/tracepointProgram_types.go b/bpfman-operator/apis/v1alpha1/tracepointProgram_types.go index 6ab0c97d1..c388070bb 100644 --- a/bpfman-operator/apis/v1alpha1/tracepointProgram_types.go +++ b/bpfman-operator/apis/v1alpha1/tracepointProgram_types.go @@ -31,6 +31,7 @@ import ( // TracepointProgram is the Schema for the TracepointPrograms API // +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` // +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` // +kubebuilder:printcolumn:name="TracePoint",type=string,JSONPath=`.spec.name`,priority=1 type TracepointProgram struct { metav1.TypeMeta `json:",inline"` diff --git a/bpfman-operator/apis/v1alpha1/uprobeProgram_types.go b/bpfman-operator/apis/v1alpha1/uprobeProgram_types.go index 6afddb6e2..c036e6e44 100644 --- a/bpfman-operator/apis/v1alpha1/uprobeProgram_types.go +++ b/bpfman-operator/apis/v1alpha1/uprobeProgram_types.go @@ -31,6 +31,7 @@ import ( // UprobeProgram is the Schema for the UprobePrograms API // +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` // +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` // +kubebuilder:printcolumn:name="FunctionName",type=string,JSONPath=`.spec.func_name`,priority=1 // +kubebuilder:printcolumn:name="Offset",type=integer,JSONPath=`.spec.offset`,priority=1 // +kubebuilder:printcolumn:name="Target",type=string,JSONPath=`.spec.target`,priority=1 @@ -64,7 +65,7 @@ type UprobeProgramSpec struct { Offset uint64 `json:"offset"` // Library name or the absolute path to a binary or library. - Targets []string `json:"target"` + Target string `json:"target"` // Whether the program is a uretprobe. Default is false // +optional @@ -76,9 +77,14 @@ type UprobeProgramSpec struct { // +optional Pid int32 `json:"pid"` - // // Namespace to attach the uprobe in. (Not supported yet by bpfman.) - // // +optional - // Namespace string `json:"namespace"` + // Containers identifes the set of containers in which to attach the uprobe. + // If Containers is not specified, the uprobe will be attached in the + // bpfman-agent container. The ContainerSelector is very flexible and even + // allows the selection of all containers in a cluster. If an attempt is + // made to attach uprobes to too many containers, it can have a negative + // impact on on the cluster. + // +optional + Containers *ContainerSelector `json:"containers"` } // UprobeProgramStatus defines the observed state of UprobeProgram diff --git a/bpfman-operator/apis/v1alpha1/xdpProgram_types.go b/bpfman-operator/apis/v1alpha1/xdpProgram_types.go index 12b60d822..51347dca2 100644 --- a/bpfman-operator/apis/v1alpha1/xdpProgram_types.go +++ b/bpfman-operator/apis/v1alpha1/xdpProgram_types.go @@ -31,6 +31,7 @@ import ( // XdpProgram is the Schema for the XdpPrograms API // +kubebuilder:printcolumn:name="BpfFunctionName",type=string,JSONPath=`.spec.bpffunctionname` // +kubebuilder:printcolumn:name="NodeSelector",type=string,JSONPath=`.spec.nodeselector` +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[0].reason` // +kubebuilder:printcolumn:name="Priority",type=string,JSONPath=`.spec.priority`,priority=1 // +kubebuilder:printcolumn:name="InterfaceSelector",type=string,JSONPath=`.spec.interfaceselector`,priority=1 // +kubebuilder:printcolumn:name="ProceedOn",type=string,JSONPath=`.spec.proceedon`,priority=1 diff --git a/bpfman-operator/apis/v1alpha1/zz_generated.deepcopy.go b/bpfman-operator/apis/v1alpha1/zz_generated.deepcopy.go index 8cab3d441..13c01a44d 100644 --- a/bpfman-operator/apis/v1alpha1/zz_generated.deepcopy.go +++ b/bpfman-operator/apis/v1alpha1/zz_generated.deepcopy.go @@ -200,6 +200,225 @@ func (in *BytecodeSelector) DeepCopy() *BytecodeSelector { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ContainerSelector) DeepCopyInto(out *ContainerSelector) { + *out = *in + in.Pods.DeepCopyInto(&out.Pods) + if in.ContainerNames != nil { + in, out := &in.ContainerNames, &out.ContainerNames + *out = new([]string) + if **in != nil { + in, out := *in, *out + *out = make([]string, len(*in)) + copy(*out, *in) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerSelector. +func (in *ContainerSelector) DeepCopy() *ContainerSelector { + if in == nil { + return nil + } + out := new(ContainerSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FentryProgram) DeepCopyInto(out *FentryProgram) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FentryProgram. +func (in *FentryProgram) DeepCopy() *FentryProgram { + if in == nil { + return nil + } + out := new(FentryProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FentryProgram) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FentryProgramList) DeepCopyInto(out *FentryProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]FentryProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FentryProgramList. +func (in *FentryProgramList) DeepCopy() *FentryProgramList { + if in == nil { + return nil + } + out := new(FentryProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FentryProgramList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FentryProgramSpec) DeepCopyInto(out *FentryProgramSpec) { + *out = *in + in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FentryProgramSpec. +func (in *FentryProgramSpec) DeepCopy() *FentryProgramSpec { + if in == nil { + return nil + } + out := new(FentryProgramSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FentryProgramStatus) DeepCopyInto(out *FentryProgramStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FentryProgramStatus. +func (in *FentryProgramStatus) DeepCopy() *FentryProgramStatus { + if in == nil { + return nil + } + out := new(FentryProgramStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FexitProgram) DeepCopyInto(out *FexitProgram) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FexitProgram. +func (in *FexitProgram) DeepCopy() *FexitProgram { + if in == nil { + return nil + } + out := new(FexitProgram) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FexitProgram) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FexitProgramList) DeepCopyInto(out *FexitProgramList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]FexitProgram, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FexitProgramList. +func (in *FexitProgramList) DeepCopy() *FexitProgramList { + if in == nil { + return nil + } + out := new(FexitProgramList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FexitProgramList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FexitProgramSpec) DeepCopyInto(out *FexitProgramSpec) { + *out = *in + in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FexitProgramSpec. +func (in *FexitProgramSpec) DeepCopy() *FexitProgramSpec { + if in == nil { + return nil + } + out := new(FexitProgramSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FexitProgramStatus) DeepCopyInto(out *FexitProgramStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FexitProgramStatus. +func (in *FexitProgramStatus) DeepCopy() *FexitProgramStatus { + if in == nil { + return nil + } + out := new(FexitProgramStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImagePullSecretSelector) DeepCopyInto(out *ImagePullSecretSelector) { *out = *in @@ -307,11 +526,6 @@ func (in *KprobeProgramList) DeepCopyObject() runtime.Object { func (in *KprobeProgramSpec) DeepCopyInto(out *KprobeProgramSpec) { *out = *in in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) - if in.FunctionNames != nil { - in, out := &in.FunctionNames, &out.FunctionNames - *out = make([]string, len(*in)) - copy(*out, *in) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KprobeProgramSpec. @@ -614,10 +828,10 @@ func (in *UprobeProgramList) DeepCopyObject() runtime.Object { func (in *UprobeProgramSpec) DeepCopyInto(out *UprobeProgramSpec) { *out = *in in.BpfProgramCommon.DeepCopyInto(&out.BpfProgramCommon) - if in.Targets != nil { - in, out := &in.Targets, &out.Targets - *out = make([]string, len(*in)) - copy(*out, *in) + if in.Containers != nil { + in, out := &in.Containers, &out.Containers + *out = new(ContainerSelector) + (*in).DeepCopyInto(*out) } } diff --git a/bpfman-operator/apis/v1alpha1/zz_generated.register.go b/bpfman-operator/apis/v1alpha1/zz_generated.register.go index 6589989de..522ad7db9 100644 --- a/bpfman-operator/apis/v1alpha1/zz_generated.register.go +++ b/bpfman-operator/apis/v1alpha1/zz_generated.register.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by register-gen. DO NOT EDIT. package v1alpha1 @@ -59,6 +60,10 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &BpfProgram{}, &BpfProgramList{}, + &FentryProgram{}, + &FentryProgramList{}, + &FexitProgram{}, + &FexitProgramList{}, &KprobeProgram{}, &KprobeProgramList{}, &TcProgram{}, diff --git a/bpfman-operator/cmd/bpfman-agent/main.go b/bpfman-operator/cmd/bpfman-agent/main.go index da13b43d9..157b17dc0 100644 --- a/bpfman-operator/cmd/bpfman-agent/main.go +++ b/bpfman-operator/cmd/bpfman-agent/main.go @@ -59,8 +59,8 @@ func main() { var metricsAddr string var probeAddr string var opts zap.Options - flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") - flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8174", "The address the metric endpoint binds to.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8175", "The address the probe endpoint binds to.") flag.Parse() // Get the Log level for bpfman deployment where this pod is running @@ -101,13 +101,9 @@ func main() { os.Exit(1) } - // Setup bpfman Client - configFileData := conn.LoadConfig() - setupLog.Info("Connecting over UNIX socket to bpfman") - // Set up a connection to bpfman, block until bpfman is up. - setupLog.Info("Waiting for active connection to bpfman", "endpoints", configFileData.Grpc.Endpoints) - conn, err := conn.CreateConnection(configFileData.Grpc.Endpoints, context.Background(), insecure.NewCredentials()) + setupLog.Info("Waiting for active connection to bpfman") + conn, err := conn.CreateConnection(context.Background(), insecure.NewCredentials()) if err != nil { setupLog.Error(err, "unable to connect to bpfman") os.Exit(1) @@ -162,10 +158,17 @@ func main() { os.Exit(1) } - if err = (&bpfmanagent.DiscoveredProgramReconciler{ + if err = (&bpfmanagent.FentryProgramReconciler{ + ReconcilerCommon: common, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create fentryProgram controller", "controller", "BpfProgram") + os.Exit(1) + } + + if err = (&bpfmanagent.FexitProgramReconciler{ ReconcilerCommon: common, }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create discoveredProgram controller", "controller", "BpfProgram") + setupLog.Error(err, "unable to create fexitProgram controller", "controller", "BpfProgram") os.Exit(1) } diff --git a/bpfman-operator/cmd/bpfman-operator/main.go b/bpfman-operator/cmd/bpfman-operator/main.go index 3527791d1..829b882ba 100644 --- a/bpfman-operator/cmd/bpfman-operator/main.go +++ b/bpfman-operator/cmd/bpfman-operator/main.go @@ -52,8 +52,8 @@ func main() { var enableLeaderElection bool var probeAddr string var opts zap.Options - flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") - flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8174", "The address the metric endpoint binds to.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8175", "The address the probe endpoint binds to.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") @@ -156,6 +156,20 @@ func main() { os.Exit(1) } + if err = (&bpfmanoperator.FentryProgramReconciler{ + ReconcilerCommon: common, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create fentryProgram controller", "controller", "BpfProgram") + os.Exit(1) + } + + if err = (&bpfmanoperator.FexitProgramReconciler{ + ReconcilerCommon: common, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create fexitProgram controller", "controller", "BpfProgram") + os.Exit(1) + } + //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/bpfman-operator/config/bpfman-deployment/config.yaml b/bpfman-operator/config/bpfman-deployment/config.yaml index 407fec3c8..ac9177f76 100644 --- a/bpfman-operator/config/bpfman-deployment/config.yaml +++ b/bpfman-operator/config/bpfman-deployment/config.yaml @@ -8,12 +8,13 @@ data: bpfman.agent.image: quay.io/bpfman/bpfman-agent:latest bpfman.image: quay.io/bpfman/bpfman:latest ## Can be set to "info", "debug", or "trace" - bpfman.agent.log.level: "info" + bpfman.agent.log.level: info ## See https://docs.rs/env_logger/latest/env_logger/ for configuration options - bpfman.log.level: "info" - ## Must be configured at startup + bpfman.log.level: info + bpfman.agent.healthprobe.addr: :8175 + bpfman.agent.metric.addr: 127.0.0.1:8174 + # Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043 bpfman.toml: | - [[grpc.endpoints]] - type = "unix" - path = "/bpfman-sock/bpfman.sock" - enabled = true + [database] + max_retries = 30 + millisec_delay = 10000 diff --git a/bpfman-operator/config/bpfman-deployment/daemonset.yaml b/bpfman-operator/config/bpfman-deployment/daemonset.yaml index 0a2574b10..9653adc49 100644 --- a/bpfman-operator/config/bpfman-deployment/daemonset.yaml +++ b/bpfman-operator/config/bpfman-deployment/daemonset.yaml @@ -75,14 +75,10 @@ spec: fieldPath: spec.nodeName volumeMounts: - name: bpfman-sock - mountPath: /bpfman-sock + mountPath: /run/bpfman-sock - name: runtime mountPath: /run/bpfman mountPropagation: Bidirectional - - name: bpfman-config - mountPath: /etc/bpfman/bpfman.toml - subPath: bpfman.toml - readOnly: true # This mount is needed to attach tracepoint programs - name: host-debug mountPath: /sys/kernel/debug @@ -101,9 +97,21 @@ spec: name: mountpoint-dir - mountPath: /tmp name: tmp-dir + # Used to attach programs inside of containers + - mountPath: /host/proc + name: host-proc + - name: bpfman-config + mountPath: /etc/bpfman/bpfman.toml + subPath: bpfman.toml + readOnly: true - name: bpfman-agent command: [/bpfman-agent] + args: + - --health-probe-bind-address=:8175 + - --metrics-bind-address=127.0.0.1:8174 image: quay.io/bpfman/bpfman-agent:latest + securityContext: + privileged: true imagePullPolicy: IfNotPresent env: - name: KUBE_NODE_NAME @@ -117,11 +125,19 @@ spec: key: bpfman.agent.log.level volumeMounts: - name: bpfman-sock - mountPath: /bpfman-sock - - name: bpfman-config - mountPath: /etc/bpfman/bpfman.toml - subPath: bpfman.toml - readOnly: true + mountPath: /run/bpfman-sock + ## The following five mounts are used by crictl for attaching + ## uprobes in user containers + - mountPath: /run/containerd/containerd.sock + name: host-containerd + - mountPath: /run/crio/crio.sock + name: host-crio + - mountPath: /var/run/dockershim.sock + name: host-dockershim + - mountPath: /var/run/cri-dockerd.sock + name: host-dockerd + - mountPath: /etc/crictl.yaml + name: host-crictl-config - name: node-driver-registrar image: quay.io/bpfman/csi-node-driver-registrar:v2.9.0 imagePullPolicy: IfNotPresent @@ -190,3 +206,21 @@ spec: path: /tmp type: DirectoryOrCreate name: tmp-dir + - hostPath: + path: /proc + name: host-proc + - hostPath: + path: /run/containerd/containerd.sock + name: host-containerd + - hostPath: + path: /run/crio/crio.sock + name: host-crio + - hostPath: + path: /var/run/dockershim.sock + name: host-dockershim + - hostPath: + path: /var/run/cri-dockerd.sock + name: host-dockerd + - hostPath: + path: /etc/crictl.yaml + name: host-crictl-config diff --git a/bpfman-operator/config/bpfman-operator-deployment/deployment.yaml b/bpfman-operator/config/bpfman-operator-deployment/deployment.yaml index a3e7e7ae0..a8dbc0100 100644 --- a/bpfman-operator/config/bpfman-operator-deployment/deployment.yaml +++ b/bpfman-operator/config/bpfman-operator-deployment/deployment.yaml @@ -74,23 +74,23 @@ spec: imagePullPolicy: IfNotPresent env: - name: GO_LOG - value: "debug" + value: debug name: bpfman-operator securityContext: allowPrivilegeEscalation: false capabilities: drop: - - "ALL" + - ALL livenessProbe: httpGet: path: /healthz - port: 8081 + port: 8175 initialDelaySeconds: 15 periodSeconds: 20 readinessProbe: httpGet: path: /readyz - port: 8081 + port: 8175 initialDelaySeconds: 5 periodSeconds: 10 # TODO(user): Configure the resources accordingly based on the project requirements. diff --git a/bpfman-operator/config/crd/bases/bpfman.io_bpfprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_bpfprograms.yaml index d93d3bb65..2d930330a 100644 --- a/bpfman-operator/config/crd/bases/bpfman.io_bpfprograms.yaml +++ b/bpfman-operator/config/crd/bases/bpfman.io_bpfprograms.yaml @@ -15,7 +15,17 @@ spec: singular: bpfprogram scope: Cluster versions: - - name: v1alpha1 + - additionalPrinterColumns: + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 schema: openAPIV3Schema: description: BpfProgram is the Schema for the Bpfprograms API diff --git a/bpfman-operator/config/crd/bases/bpfman.io_fentryprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_fentryprograms.yaml new file mode 100644 index 000000000..34d6d9482 --- /dev/null +++ b/bpfman-operator/config/crd/bases/bpfman.io_fentryprograms.yaml @@ -0,0 +1,298 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.3 + creationTimestamp: null + name: fentryprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: FentryProgram + listKind: FentryProgramList + plural: fentryprograms + singular: fentryprogram + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.func_name + name: FunctionName + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: FentryProgram is the Schema for the FentryPrograms API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: FentryProgramSpec defines the desired state of FentryProgram + properties: + bpffunctionname: + description: BpfFunctionName is the name of the function that is the + entry point for the BPF program + type: string + bytecode: + description: Bytecode configures where the bpf program's bytecode + should be loaded from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: ImagePullSecret is the name of the secret bpfman + should use to get remote image repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + func_name: + description: Function to attach the fentry to. + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: GlobalData allows the user to to set global variables + when the program is loaded with an array of raw bytes. This is a + very low level primitive. The caller is responsible for formatting + the byte string appropriately considering such things as size, endianness, + alignment and packing of data structures. + type: object + mapownerselector: + description: MapOwnerSelector is used to select the loaded eBPF program + this eBPF program will share a map with. The value is a label applied + to the BpfProgram to select. The selector must resolve to exactly + one instance of a BpfProgram on a given node or the eBPF program + will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nodeselector: + description: NodeSelector allows the user to specify which nodes to + deploy the bpf program to. This field must be specified, to select + all nodes use standard metav1.LabelSelector semantics and make it + empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - bpffunctionname + - bytecode + - func_name + - nodeselector + type: object + status: + description: FentryProgramStatus defines the observed state of FentryProgram + properties: + conditions: + description: Conditions houses the global cluster state for the FentryProgram. + The explicit condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/bpfman-operator/config/crd/bases/bpfman.io_fexitprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_fexitprograms.yaml new file mode 100644 index 000000000..23c8ef208 --- /dev/null +++ b/bpfman-operator/config/crd/bases/bpfman.io_fexitprograms.yaml @@ -0,0 +1,298 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.3 + creationTimestamp: null + name: fexitprograms.bpfman.io +spec: + group: bpfman.io + names: + kind: FexitProgram + listKind: FexitProgramList + plural: fexitprograms + singular: fexitprogram + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.bpffunctionname + name: BpfFunctionName + type: string + - jsonPath: .spec.nodeselector + name: NodeSelector + type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string + - jsonPath: .spec.func_name + name: FunctionName + priority: 1 + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: FexitProgram is the Schema for the FexitPrograms API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: FexitProgramSpec defines the desired state of FexitProgram + properties: + bpffunctionname: + description: BpfFunctionName is the name of the function that is the + entry point for the BPF program + type: string + bytecode: + description: Bytecode configures where the bpf program's bytecode + should be loaded from. + properties: + image: + description: Image used to specify a bytecode container image. + properties: + imagepullpolicy: + default: IfNotPresent + description: PullPolicy describes a policy for if/when to + pull a bytecode image. Defaults to IfNotPresent. + enum: + - Always + - Never + - IfNotPresent + type: string + imagepullsecret: + description: ImagePullSecret is the name of the secret bpfman + should use to get remote image repository secrets. + properties: + name: + description: Name of the secret which contains the credentials + to access the image repository. + type: string + namespace: + description: Namespace of the secret which contains the + credentials to access the image repository. + type: string + required: + - name + - namespace + type: object + url: + description: Valid container image URL used to reference a + remote bytecode image. + type: string + required: + - url + type: object + path: + description: Path is used to specify a bytecode object via filepath. + type: string + type: object + func_name: + description: Function to attach the fexit to. + type: string + globaldata: + additionalProperties: + format: byte + type: string + description: GlobalData allows the user to to set global variables + when the program is loaded with an array of raw bytes. This is a + very low level primitive. The caller is responsible for formatting + the byte string appropriately considering such things as size, endianness, + alignment and packing of data structures. + type: object + mapownerselector: + description: MapOwnerSelector is used to select the loaded eBPF program + this eBPF program will share a map with. The value is a label applied + to the BpfProgram to select. The selector must resolve to exactly + one instance of a BpfProgram on a given node or the eBPF program + will not load. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + nodeselector: + description: NodeSelector allows the user to specify which nodes to + deploy the bpf program to. This field must be specified, to select + all nodes use standard metav1.LabelSelector semantics and make it + empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - bpffunctionname + - bytecode + - func_name + - nodeselector + type: object + status: + description: FexitProgramStatus defines the observed state of FexitProgram + properties: + conditions: + description: Conditions houses the global cluster state for the FexitProgram. + The explicit condition types are defined internally. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/bpfman-operator/config/crd/bases/bpfman.io_kprobeprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_kprobeprograms.yaml index 5ac7ee8f8..550480866 100644 --- a/bpfman-operator/config/crd/bases/bpfman.io_kprobeprograms.yaml +++ b/bpfman-operator/config/crd/bases/bpfman.io_kprobeprograms.yaml @@ -22,6 +22,9 @@ spec: - jsonPath: .spec.nodeselector name: NodeSelector type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string - jsonPath: .spec.func_name name: FunctionName priority: 1 @@ -101,11 +104,9 @@ spec: description: Path is used to specify a bytecode object via filepath. type: string type: object - func_names: + func_name: description: Functions to attach the kprobe to. - items: - type: string - type: array + type: string globaldata: additionalProperties: format: byte @@ -226,7 +227,7 @@ spec: required: - bpffunctionname - bytecode - - func_names + - func_name - nodeselector type: object x-kubernetes-validations: diff --git a/bpfman-operator/config/crd/bases/bpfman.io_tcprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_tcprograms.yaml index a6803ea06..95731e9ea 100644 --- a/bpfman-operator/config/crd/bases/bpfman.io_tcprograms.yaml +++ b/bpfman-operator/config/crd/bases/bpfman.io_tcprograms.yaml @@ -22,6 +22,9 @@ spec: - jsonPath: .spec.nodeselector name: NodeSelector type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string - jsonPath: .spec.priority name: Priority priority: 1 diff --git a/bpfman-operator/config/crd/bases/bpfman.io_tracepointprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_tracepointprograms.yaml index f1d159132..d1d1b5392 100644 --- a/bpfman-operator/config/crd/bases/bpfman.io_tracepointprograms.yaml +++ b/bpfman-operator/config/crd/bases/bpfman.io_tracepointprograms.yaml @@ -22,6 +22,9 @@ spec: - jsonPath: .spec.nodeselector name: NodeSelector type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string - jsonPath: .spec.name name: TracePoint priority: 1 diff --git a/bpfman-operator/config/crd/bases/bpfman.io_uprobeprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_uprobeprograms.yaml index 2eea1d820..409bca4ad 100644 --- a/bpfman-operator/config/crd/bases/bpfman.io_uprobeprograms.yaml +++ b/bpfman-operator/config/crd/bases/bpfman.io_uprobeprograms.yaml @@ -22,6 +22,9 @@ spec: - jsonPath: .spec.nodeselector name: NodeSelector type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string - jsonPath: .spec.func_name name: FunctionName priority: 1 @@ -109,6 +112,74 @@ spec: description: Path is used to specify a bytecode object via filepath. type: string type: object + containers: + description: Containers identifes the set of containers in which to + attach the uprobe. If Containers is not specified, the uprobe will + be attached in the bpfman-agent container. The ContainerSelector + is very flexible and even allows the selection of all containers + in a cluster. If an attempt is made to attach uprobes to too many + containers, it can have a negative impact on on the cluster. + properties: + containernames: + description: Name(s) of container(s). If none are specified, + all containers in the pod are selected. + items: + type: string + type: array + namespace: + default: "" + description: Target namespaces. + type: string + pods: + description: Target pods. This field must be specified, to select + all pods use standard metav1.LabelSelector semantics and make + it empty. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - pods + type: object func_name: description: Function to attach the uprobe to. type: string @@ -235,9 +306,7 @@ spec: type: boolean target: description: Library name or the absolute path to a binary or library. - items: - type: string - type: array + type: string required: - bpffunctionname - bytecode diff --git a/bpfman-operator/config/crd/bases/bpfman.io_xdpprograms.yaml b/bpfman-operator/config/crd/bases/bpfman.io_xdpprograms.yaml index 4e9baa431..920dd3338 100644 --- a/bpfman-operator/config/crd/bases/bpfman.io_xdpprograms.yaml +++ b/bpfman-operator/config/crd/bases/bpfman.io_xdpprograms.yaml @@ -22,6 +22,9 @@ spec: - jsonPath: .spec.nodeselector name: NodeSelector type: string + - jsonPath: .status.conditions[0].reason + name: Status + type: string - jsonPath: .spec.priority name: Priority priority: 1 diff --git a/bpfman-operator/config/crd/kustomization.yaml b/bpfman-operator/config/crd/kustomization.yaml index 4d26488ac..ba17a6bcc 100644 --- a/bpfman-operator/config/crd/kustomization.yaml +++ b/bpfman-operator/config/crd/kustomization.yaml @@ -8,6 +8,8 @@ resources: - bases/bpfman.io_xdpprograms.yaml - bases/bpfman.io_kprobeprograms.yaml - bases/bpfman.io_uprobeprograms.yaml + - bases/bpfman.io_fentryprograms.yaml + - bases/bpfman.io_fexitprograms.yaml #+kubebuilder:scaffold:crdkustomizeresource @@ -20,6 +22,8 @@ patchesStrategicMerge: #- patches/webhook_in_tracepointprograms.yaml #- patches/webhook_in_kprobeprograms.yaml #- patches/webhook_in_uprobeprograms.yaml +#- patches/webhook_in_fentryprograms.yaml +#- patches/webhook_in_fexitprograms.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -30,6 +34,8 @@ patchesStrategicMerge: #- patches/cainjection_in_tracepointprograms.yaml #- patches/cainjection_in_kprobeprograms.yaml #- patches/cainjection_in_uprobeprograms.yaml +#- patches/cainjection_in_fentryprograms.yaml +#- patches/cainjection_in_fentryprograms.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/bpfman-operator/config/crd/patches/cainjection_in_fentryprograms.yaml b/bpfman-operator/config/crd/patches/cainjection_in_fentryprograms.yaml new file mode 100644 index 000000000..c171146d6 --- /dev/null +++ b/bpfman-operator/config/crd/patches/cainjection_in_fentryprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: fentryprograms.bpfman.io diff --git a/bpfman-operator/config/crd/patches/cainjection_in_fexitprograms.yaml b/bpfman-operator/config/crd/patches/cainjection_in_fexitprograms.yaml new file mode 100644 index 000000000..b06844e6e --- /dev/null +++ b/bpfman-operator/config/crd/patches/cainjection_in_fexitprograms.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: fexitprograms.bpfman.io diff --git a/bpfman-operator/config/crd/patches/webhook_in_fentryprograms.yaml b/bpfman-operator/config/crd/patches/webhook_in_fentryprograms.yaml new file mode 100644 index 000000000..39e7827a5 --- /dev/null +++ b/bpfman-operator/config/crd/patches/webhook_in_fentryprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: fentryprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/bpfman-operator/config/crd/patches/webhook_in_fexitprograms.yaml b/bpfman-operator/config/crd/patches/webhook_in_fexitprograms.yaml new file mode 100644 index 000000000..9b09fc690 --- /dev/null +++ b/bpfman-operator/config/crd/patches/webhook_in_fexitprograms.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: fexitprograms.bpfman.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/bpfman-operator/config/default/manager_auth_proxy_patch.yaml b/bpfman-operator/config/default/manager_auth_proxy_patch.yaml index 61acfd03f..db945cf75 100644 --- a/bpfman-operator/config/default/manager_auth_proxy_patch.yaml +++ b/bpfman-operator/config/default/manager_auth_proxy_patch.yaml @@ -30,13 +30,13 @@ spec: allowPrivilegeEscalation: false capabilities: drop: - - "ALL" + - ALL image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0 args: - - "--secure-listen-address=0.0.0.0:8443" - - "--upstream=http://127.0.0.1:8080/" - - "--logtostderr=true" - - "--v=0" + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8174/ + - --logtostderr=true + - --v=0 ports: - containerPort: 8443 protocol: TCP @@ -50,6 +50,6 @@ spec: memory: 64Mi - name: bpfman-operator args: - - "--health-probe-bind-address=:8081" - - "--metrics-bind-address=127.0.0.1:8080" - - "--leader-elect" + - --health-probe-bind-address=:8175 + - --metrics-bind-address=127.0.0.1:8174 + - --leader-elect diff --git a/bpfman-operator/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml b/bpfman-operator/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml index 76bc2de64..a71c2f4af 100644 --- a/bpfman-operator/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml +++ b/bpfman-operator/config/manifests/bases/bpfman-operator.clusterserviceversion.yaml @@ -42,6 +42,16 @@ spec: kind: UprobeProgram name: uprobeprograms.bpfman.io version: v1alpha1 + - description: FentryProgram is the Schema for the Fentryprograms API + displayName: Fentry Program + kind: FentryProgram + name: fentryprograms.bpfman.io + version: v1alpha1 + - description: FexitProgram is the Schema for the Fexitprograms API + displayName: Fexit Program + kind: FexitProgram + name: fexitprograms.bpfman.io + version: v1alpha1 description: "The bpfman Operator is a Kubernetes Operator for deploying [bpfman](https://bpfman.netlify.app/), a system daemon\nfor managing eBPF programs. It deploys bpfman itself along with @@ -55,14 +65,310 @@ spec: `bpfman.agent.image`: The image used for the bpfman-agent, defaults to `quay.io/bpfman/bpfman-agent:latest`\n- `bpfman.image`: The image used for bpfman, defaults to `quay.io/bpfman/bpfman:latest`\n- `bpfman.log.level`: the log level for bpfman, currently supports - `debug`, `info`, `warn`, `error`, and `fatal`, defaults to `info`\n- `bpfman.agent.log.level`: the log level for the bpfman-agent currently supports `info`, `debug`, and `trace` \n-`bpfman.toml`: - bpfman's custom configuration file.\n\n## Deploying eBPF Programs\n\nThe bpfman operator - deploys eBPF programs via CRDs. The following CRDs are currently avaliable, \n\n- - XdpProgram\n- TcProgram\n- TracepointProgram\n- KprobeProgram\n- UprobeProgram\n\n## More information\n\nPlease + `debug`, `info`, `warn`, `error`, and `fatal`, defaults to `info`\n- `bpfman.agent.log.level`: the log level for the bpfman-agent currently supports `info`, `debug`, and `trace` \n\nThe bpfman operator + deploys eBPF programs via CRDs. The following CRDs are currently available, \n\n- + XdpProgram\n- TcProgram\n- TracepointProgram\n- KprobeProgram\n- UprobeProgram\n- FentryProgram\n- FexitProgram\n\n## More information\n\nPlease checkout the [bpfman community website](https://bpfman.io/) for more information." displayName: Bpfman Operator icon: - - base64data: PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB3aWR0aD0iNTEyIgogICBoZWlnaHQ9IjEyOCIKICAgdmlld0JveD0iMCAwIDUxMiAxMjgiCiAgIHZlcnNpb249IjEuMSIKICAgaWQ9InN2ZzUiCiAgIGlua3NjYXBlOnZlcnNpb249IjEuMiAoZGMyYWVkYWYwMywgMjAyMi0wNS0xNSkiCiAgIHNvZGlwb2RpOmRvY25hbWU9ImJwZmQuc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0ibmFtZWR2aWV3NyIKICAgICBwYWdlY29sb3I9IiM1MDUwNTAiCiAgICAgYm9yZGVyY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMSIKICAgICBpbmtzY2FwZTpzaG93cGFnZXNoYWRvdz0iMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMCIKICAgICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSIxIgogICAgIGlua3NjYXBlOmRlc2tjb2xvcj0iIzUwNTA1MCIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGlua3NjYXBlOnpvb209IjEuNTM1IgogICAgIGlua3NjYXBlOmN4PSIxNjkuNzA2ODQiCiAgICAgaW5rc2NhcGU6Y3k9Ii01Mi40NDI5OTciCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxOTIwIgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9IjEwMjYiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjAiCiAgICAgaW5rc2NhcGU6d2luZG93LXk9IjAiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiIC8+CiAgPGRlZnMKICAgICBpZD0iZGVmczIiPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDY0IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjUxMiA6IDY0IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNTYgOiA0Mi42NjY2NjcgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMTc5MjkiIC8+CiAgICA8Y2xpcFBhdGgKICAgICAgIGNsaXBQYXRoVW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICAgICAgaWQ9ImNsaXBQYXRoMjcyNjAiPgogICAgICA8cmVjdAogICAgICAgICBzdHlsZT0iZmlsbDpub25lO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDo0LjM2MjgxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIgogICAgICAgICBpZD0icmVjdDI3MjYyIgogICAgICAgICB3aWR0aD0iMTA3LjE1MTg0IgogICAgICAgICBoZWlnaHQ9IjEwNy4xNTE4NCIKICAgICAgICAgeD0iMjMuMTAzNDE2IgogICAgICAgICB5PSIzMS41MzM0MjgiCiAgICAgICAgIHJ5PSIyMC4xNzk3OSIgLz4KICAgIDwvY2xpcFBhdGg+CiAgPC9kZWZzPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxnCiAgICAgICBpZD0iZzI3MzA0IgogICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMS4wNzAxMDM3LDAsMCwxLjA3MDEwMzcsMTkuMTA3MDU0LC04LjY5MjY1NzcpIj4KICAgICAgPGcKICAgICAgICAgaWQ9ImcyNzI1OCIKICAgICAgICAgY2xpcC1wYXRoPSJ1cmwoI2NsaXBQYXRoMjcyNjApIgogICAgICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtOCwtMTgpIj4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTU2ODAiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yOTk4NTI7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgdHJhbnNmb3JtPSJzY2FsZSgtMSkiCiAgICAgICAgICAgZD0ibSAtNjYuMDgzNjQsMjQuMTYzNTc4IGEgMTIuMzcyNTI5LDMuOTcyMjMyOCAwIDAgMSAtMTIuMzcyNTI5LDMuOTcyMjMzIDEyLjM3MjUyOSwzLjk3MjIzMjggMCAwIDEgLTEyLjM3MjUyOSwtMy45NzIyMzMgMTIuMzcyNTI5LDMuOTcyMjMyOCAwIDAgMSAxMi4zNzI1MjksLTMuOTcyMjMzIDEyLjM3MjUyOSwzLjk3MjIzMjggMCAwIDEgMTIuMzcyNTI5LDMuOTcyMjMzIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0iZWxsaXBzZTE1Njc4IgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmM2I2MjY7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuNDg5NDA3O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoLTEpIgogICAgICAgICAgIGQ9Im0gLTU4LjI2MjIyNCwzMC4xNjM1NzggYSAyMC4xOTM5NDUsNi40ODMzMTg4IDAgMCAxIC0yMC4xOTM5NDUsNi40ODMzMTkgMjAuMTkzOTQ1LDYuNDgzMzE4OCAwIDAgMSAtMjAuMTkzOTQ1LC02LjQ4MzMxOSAyMC4xOTM5NDUsNi40ODMzMTg4IDAgMCAxIDIwLjE5Mzk0NSwtNi40ODMzMTkgMjAuMTkzOTQ1LDYuNDgzMzE4OCAwIDAgMSAyMC4xOTM5NDUsNi40ODMzMTkgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTU2NzYiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC43O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoLTEpIgogICAgICAgICAgIGQ9Im0gLTQ5LjU3MjczMywzOC4xNjM1NzggYSAyOC44ODM0MzYsOS4yNzMxMDI4IDAgMCAxIC0yOC44ODM0MzYsOS4yNzMxMDMgMjguODgzNDM2LDkuMjczMTAyOCAwIDAgMSAtMjguODgzNDQxLC05LjI3MzEwMyAyOC44ODM0MzYsOS4yNzMxMDI4IDAgMCAxIDI4Ljg4MzQ0MSwtOS4yNzMxMDMgMjguODgzNDM2LDkuMjczMTAyOCAwIDAgMSAyOC44ODM0MzYsOS4yNzMxMDMgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTU2NzQiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC43O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIHRyYW5zZm9ybT0ic2NhbGUoLTEpIgogICAgICAgICAgIGQ9Ik0gLTQwLjEyNjQ4LDQ4LjE2MzU4NiBBIDM4LjMyOTY4OSwxMi4zMDU4NDcgMCAwIDEgLTc4LjQ1NjE2OSw2MC40Njk0MzMgMzguMzI5Njg5LDEyLjMwNTg0NyAwIDAgMSAtMTE2Ljc4NTg2LDQ4LjE2MzU4NiAzOC4zMjk2ODksMTIuMzA1ODQ3IDAgMCAxIC03OC40NTYxNjksMzUuODU3NzM4IDM4LjMyOTY4OSwxMi4zMDU4NDcgMCAwIDEgLTQwLjEyNjQ4LDQ4LjE2MzU4NiBaIiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9ImVsbGlwc2UxNTY3MiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZjNiNjI2O3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDowLjc7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgdHJhbnNmb3JtPSJzY2FsZSgtMSkiCiAgICAgICAgICAgZD0iTSAtMzQuOTE0NzUzLDU4LjE2MzU4NiBBIDQzLjU0MTQxNiwxMy45NzkwODYgMCAwIDEgLTc4LjQ1NjE2OSw3Mi4xNDI2NzIgNDMuNTQxNDE2LDEzLjk3OTA4NiAwIDAgMSAtMTIxLjk5NzU5LDU4LjE2MzU4NiA0My41NDE0MTYsMTMuOTc5MDg2IDAgMCAxIC03OC40NTYxNjksNDQuMTg0NSA0My41NDE0MTYsMTMuOTc5MDg2IDAgMCAxIC0zNC45MTQ3NTMsNTguMTYzNTg2IFoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0icmVjdDE1NjcwIgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmM2I2MjY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuNjI1MDE4O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gNDEuNTY4NTc0LC03MS43MDQ1MzEgaCA3My44NTU1MDYgYyAzLjY5ODc5LDAgNi42NzY1MSwyLjk3NzcyNyA2LjY3NjUxLDYuNjc2NTE5IHYgOS4xMTYwODIgYyAtMTAuMTY0MDQsMjEuMDE4ODc1IC04NC43OTIyMzksMTQuOTc1Nzk5IC04Ny4yMDg1MzQsMCB2IC05LjExNjA4MiBjIDAsLTMuNjk4NzkyIDIuOTc3NzI3LC02LjY3NjUxOSA2LjY3NjUxOCwtNi42NzY1MTkgeiIKICAgICAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9InNzc2Njc3MiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0iZWxsaXBzZTE0ODIyIgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmM2I2MjY7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuNztzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJNIDEyMS45OTc1OSwtNzQuMzcwNTE0IEEgNDMuNTQxNDE2LDEzLjk3OTA4NiAwIDAgMSA3OC40NTYxNjksLTYwLjM5MTQyOCA0My41NDE0MTYsMTMuOTc5MDg2IDAgMCAxIDM0LjkxNDc1MywtNzQuMzcwNTE0IDQzLjU0MTQxNiwxMy45NzkwODYgMCAwIDEgNzguNDU2MTY5LC04OC4zNDk2IDQzLjU0MTQxNiwxMy45NzkwODYgMCAwIDEgMTIxLjk5NzU5LC03NC4zNzA1MTQgWiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTQ4MjAiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC43O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMTE2Ljc4NTg2LC04NC4zNzA1MTQgQSAzOC4zMjk2ODksMTIuMzA1ODQ3IDAgMCAxIDc4LjQ1NjE2OSwtNzIuMDY0NjY3IDM4LjMyOTY4OSwxMi4zMDU4NDcgMCAwIDEgNDAuMTI2NDgsLTg0LjM3MDUxNCAzOC4zMjk2ODksMTIuMzA1ODQ3IDAgMCAxIDc4LjQ1NjE2OSwtOTYuNjc2MzYxIDM4LjMyOTY4OSwxMi4zMDU4NDcgMCAwIDEgMTE2Ljc4NTg2LC04NC4zNzA1MTQgWiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJwYXRoMTQ4MTgiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC43O3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gMTA3LjMzOTYxLC05NC4zNzA1MTQgYSAyOC44ODM0MzYsOS4yNzMxMDI4IDAgMCAxIC0yOC44ODM0NDEsOS4yNzMxMDMgMjguODgzNDM2LDkuMjczMTAyOCAwIDAgMSAtMjguODgzNDM2LC05LjI3MzEwMyAyOC44ODM0MzYsOS4yNzMxMDI4IDAgMCAxIDI4Ljg4MzQzNiwtOS4yNzMxMDYgMjguODgzNDM2LDkuMjczMTAyOCAwIDAgMSAyOC44ODM0NDEsOS4yNzMxMDYgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTQ4ODgiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC40ODk0MDc7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0ibSA5OC42NTAxMTQsLTEwMi4zNzA1MSBhIDIwLjE5Mzk0NSw2LjQ4MzMxODggMCAwIDEgLTIwLjE5Mzk0NSw2LjQ4MzMyMyAyMC4xOTM5NDUsNi40ODMzMTg4IDAgMCAxIC0yMC4xOTM5NDUsLTYuNDgzMzIzIDIwLjE5Mzk0NSw2LjQ4MzMxODggMCAwIDEgMjAuMTkzOTQ1LC02LjQ4MzMyIDIwLjE5Mzk0NSw2LjQ4MzMxODggMCAwIDEgMjAuMTkzOTQ1LDYuNDgzMzIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJlbGxpcHNlMTQ4OTAiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2YzYjYyNjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC4yOTk4NTI7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0ibSA5MC44Mjg2OTgsLTEwOC4zNzA1MSBhIDEyLjM3MjUyOSwzLjk3MjIzMjggMCAwIDEgLTEyLjM3MjUyOSwzLjk3MjI0IDEyLjM3MjUyOSwzLjk3MjIzMjggMCAwIDEgLTEyLjM3MjUyOSwtMy45NzIyNCAxMi4zNzI1MjksMy45NzIyMzI4IDAgMCAxIDEyLjM3MjUyOSwtMy45NzIyMyAxMi4zNzI1MjksMy45NzIyMzI4IDAgMCAxIDEyLjM3MjUyOSwzLjk3MjIzIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0icGF0aDE1Njg3IgogICAgICAgICAgIHN0eWxlPSJzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC40OTQ3MDc7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0ibSA4OS4wNDg0ODMsLTUxLjI1ODI0NCBhIDEwLjExODkzMyw3Ljg3OTUxOSAwIDAgMSAtMTAuMTE4OTMzLDcuODc5NTE5IDEwLjExODkzMyw3Ljg3OTUxOSAwIDAgMSAtMTAuMTE4OTMzLC03Ljg3OTUxOSAxMC4xMTg5MzMsNy44Nzk1MTkgMCAwIDEgMTAuMTE4OTMzLC03Ljg3OTUxOSAxMC4xMTg5MzMsNy44Nzk1MTkgMCAwIDEgMTAuMTE4OTMzLDcuODc5NTE5IHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0icGF0aDIxNjgxIgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNlM2I5MDA7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuMDAwMTk7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMC45OTk2MTU3MiwwLDAsMS4wMDAwMTE1LDUuMDczMTUyNiwzLjQ1NDU3MTMpIgogICAgICAgICAgIGQ9Ik0gNDMuOTQ4MjAxLDM4LjI1OTA3OSAyOS45NDI2MzMsNDYuMzQ1MTk4IDE1LjkzNzA2NCwzOC4yNTkwNzkgViAyMi4wODY4NDIgbCAxNC4wMDU1NjksLTguMDg2MTE5IDE0LjAwNTU2OCw4LjA4NjExOSB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InVzZTI0MDg4IgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0ibSA2My41MDQ1NTMsMTY2Ljc1Nzk1IC0xNC4wMDAxODYsOC4wODYyMSAtMTQuMDAwMTg3LC04LjA4NjIxIHYgLTE2LjE3MjQyIGwgMTQuMDAwMTg3LC04LjA4NjIyIDE0LjAwMDE4Niw4LjA4NjIyIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQxMDAiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJtIDkyLjUwNDcyOCwxNjYuNzU3OTUgLTE0LjAwMDE4Niw4LjA4NjIxIC0xNC4wMDAxODcsLTguMDg2MjEgdiAtMTYuMTcyNDIgbCAxNC4wMDAxODcsLTguMDg2MjIgMTQuMDAwMTg2LDguMDg2MjIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA0MiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gNDkuMDA0NDY1LDQxLjcxNDA5IDM1LjAwNDI3OSw0OS44MDAzMDIgMjEuMDA0MDkyLDQxLjcxNDA5IFYgMjUuNTQxNjY3IGwgMTQuMDAwMTg3LC04LjA4NjIxMiAxNC4wMDAxODYsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA0NCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMzQuNTA0Mzc4LDY2LjcyMjg2MiAyMC41MDQxOTIsNzQuODA5MDc0IDYuNTA0MDA1Myw2Ni43MjI4NjIgViA1MC41NTA0MzkgbCAxNC4wMDAxODY3LC04LjA4NjIxMiAxNC4wMDAxODYsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA1NCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZjYzAwO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gNzguMDA0NjQxLDQxLjcxNDA5IDY0LjAwNDQ1NSw0OS44MDAzMDIgNTAuMDA0MjY4LDQxLjcxNDA5IFYgMjUuNTQxNjY3IGwgMTQuMDAwMTg3LC04LjA4NjIxMiAxNC4wMDAxODYsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA1NiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gNjMuNTA0NTUzLDY2LjcyMjg2MiA0OS41MDQzNjcsNzQuODA5MDc0IDM1LjUwNDE4LDY2LjcyMjg2MiBWIDUwLjU1MDQzOSBsIDE0LjAwMDE4NywtOC4wODYyMTIgMTQuMDAwMTg2LDguMDg2MjEyIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQwNTgiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJNIDQ5LjAwNDQ2NSw5MS43MzE2MzQgMzUuMDA0Mjc5LDk5LjgxNzg0NiAyMS4wMDQwOTIsOTEuNzMxNjM0IFYgNzUuNTU5MjExIGwgMTQuMDAwMTg3LC04LjA4NjIxMiAxNC4wMDAxODYsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA2MCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMzQuNTA0Mzc4LDExNi43NDA0MSAyMC41MDQxOTIsMTI0LjgyNjYyIDYuNTA0MDA1MywxMTYuNzQwNDEgViAxMDAuNTY3OTggTCAyMC41MDQxOTIsOTIuNDgxNzcxIDM0LjUwNDM3OCwxMDAuNTY3OTggWiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA2NiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMTA3LjAwNDgyLDQxLjcxNDA5IDkzLjAwNDYzLDQ5LjgwMDMwMiA3OS4wMDQ0NDMsNDEuNzE0MDkgViAyNS41NDE2NjcgbCAxNC4wMDAxODcsLTguMDg2MjEyIDE0LjAwMDE5LDguMDg2MjEyIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQwNjgiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmY2MwMDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJNIDkyLjUwNDcyOCw2Ni43MjI4NjIgNzguNTA0NTQyLDc0LjgwOTA3NCA2NC41MDQzNTUsNjYuNzIyODYyIFYgNTAuNTUwNDM5IGwgMTQuMDAwMTg3LC04LjA4NjIxMiAxNC4wMDAxODYsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA3MCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZjYzAwO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gNzguMDA0NjQxLDkxLjczMTYzNCA2NC4wMDQ0NTUsOTkuODE3ODQ2IDUwLjAwNDI2OCw5MS43MzE2MzQgViA3NS41NTkyMTEgbCAxNC4wMDAxODcsLTguMDg2MjEyIDE0LjAwMDE4Niw4LjA4NjIxMiB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InVzZTI0MDcyIgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNjODM3Mzc7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0ibSA2My41MDQ1NTMsMTE2Ljc0MDQxIC0xNC4wMDAxODYsOC4wODYyMSAtMTQuMDAwMTg3LC04LjA4NjIxIHYgLTE2LjE3MjQzIGwgMTQuMDAwMTg3LC04LjA4NjIwOSAxNC4wMDAxODYsOC4wODYyMDkgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA3NCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gNDkuMDA0NDY1LDE0MS43NDkxOCAtMTQuMDAwMTg2LDguMDg2MjEgLTE0LjAwMDE4NywtOC4wODYyMSB2IC0xNi4xNzI0MiBsIDE0LjAwMDE4NywtOC4wODYyMiAxNC4wMDAxODYsOC4wODYyMiB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InVzZTI0MDc4IgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNjODM3Mzc7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0iTSAxMzYuMDA0OTksNDEuNzE0MDkgMTIyLjAwNDgsNDkuODAwMzAyIDEwOC4wMDQ2Miw0MS43MTQwOSBWIDI1LjU0MTY2NyBsIDE0LjAwMDE4LC04LjA4NjIxMiAxNC4wMDAxOSw4LjA4NjIxMiB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InVzZTI0MDgwIgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0iTSAxMjEuNTA0OSw2Ni43MjI4NjIgMTA3LjUwNDcyLDc0LjgwOTA3NCA5My41MDQ1Myw2Ni43MjI4NjIgViA1MC41NTA0MzkgbCAxNC4wMDAxOSwtOC4wODYyMTIgMTQuMDAwMTgsOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA4MiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojYzgzNzM3O3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMTA3LjAwNDgyLDkxLjczMTYzNCA5My4wMDQ2Myw5OS44MTc4NDYgNzkuMDA0NDQzLDkxLjczMTYzNCBWIDc1LjU1OTIxMSBsIDE0LjAwMDE4NywtOC4wODYyMTIgMTQuMDAwMTksOC4wODYyMTIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA4NCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gOTIuNTA0NzI4LDExNi43NDA0MSAtMTQuMDAwMTg2LDguMDg2MjEgLTE0LjAwMDE4NywtOC4wODYyMSB2IC0xNi4xNzI0MyBsIDE0LjAwMDE4NywtOC4wODYyMDkgMTQuMDAwMTg2LDguMDg2MjA5IHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQwODYiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmY2MwMDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJtIDc4LjAwNDY0MSwxNDEuNzQ5MTggLTE0LjAwMDE4Niw4LjA4NjIxIC0xNC4wMDAxODcsLTguMDg2MjEgdiAtMTYuMTcyNDIgbCAxNC4wMDAxODcsLTguMDg2MjIgMTQuMDAwMTg2LDguMDg2MjIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA5MiIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZjYzAwO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Ik0gMTUwLjUwNTA3LDY2LjcyMjg2MiAxMzYuNTA0ODksNzQuODA5MDc0IDEyMi41MDQ3LDY2LjcyMjg2MiBWIDUwLjU1MDQzOSBsIDE0LjAwMDE5LC04LjA4NjIxMiAxNC4wMDAxOCw4LjA4NjIxMiB6IiAvPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgaWQ9InVzZTI0MDk0IgogICAgICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEuNDE0MjEiCiAgICAgICAgICAgZD0iTSAxMzYuMDA0OTksOTEuNzMxNjM0IDEyMi4wMDQ4LDk5LjgxNzg0NiAxMDguMDA0NjIsOTEuNzMxNjM0IFYgNzUuNTU5MjExIGwgMTQuMDAwMTgsLTguMDg2MjEyIDE0LjAwMDE5LDguMDg2MjEyIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQwOTYiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmY2MwMDtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJtIDEyMS41MDQ5LDExNi43NDA0MSAtMTQuMDAwMTgsOC4wODYyMSAtMTQuMDAwMTksLTguMDg2MjEgdiAtMTYuMTcyNDMgbCAxNC4wMDAxOSwtOC4wODYyMDkgMTQuMDAwMTgsOC4wODYyMDkgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDA5OCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojYzgzNzM3O3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gMTA3LjAwNDgyLDE0MS43NDkxOCAtMTQuMDAwMTksOC4wODYyMSAtMTQuMDAwMTg3LC04LjA4NjIxIHYgLTE2LjE3MjQyIGwgMTQuMDAwMTg3LC04LjA4NjIyIDE0LjAwMDE5LDguMDg2MjIgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDEwOCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gMTUwLjUwNTA3LDExNi43NDA0MSAtMTQuMDAwMTgsOC4wODYyMSAtMTQuMDAwMTksLTguMDg2MjEgdiAtMTYuMTcyNDMgbCAxNC4wMDAxOSwtOC4wODYyMDkgMTQuMDAwMTgsOC4wODYyMDkgeiIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGlkPSJ1c2UyNDExMCIKICAgICAgICAgICBzdHlsZT0iZmlsbDojZmZmZmZmO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxLjQxNDIxIgogICAgICAgICAgIGQ9Im0gMTM2LjAwNDk5LDE0MS43NDkxOCAtMTQuMDAwMTksOC4wODYyMSAtMTQuMDAwMTgsLTguMDg2MjEgdiAtMTYuMTcyNDIgbCAxNC4wMDAxOCwtOC4wODYyMiAxNC4wMDAxOSw4LjA4NjIyIHoiIC8+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBpZD0idXNlMjQxMTIiCiAgICAgICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMSIKICAgICAgICAgICBkPSJtIDEyMS41MDQ5LDE2Ni43NTc5NSAtMTQuMDAwMTgsOC4wODYyMSAtMTQuMDAwMTksLTguMDg2MjEgdiAtMTYuMTcyNDIgbCAxNC4wMDAxOSwtOC4wODYyMiAxNC4wMDAxOCw4LjA4NjIyIHoiIC8+CiAgICAgIDwvZz4KICAgICAgPHJlY3QKICAgICAgICAgc3R5bGU9ImZpbGw6bm9uZTtmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6NC4zNjI4MTtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MS40MTQyMTtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgICAgaWQ9InJlY3QyNDU4MiIKICAgICAgICAgd2lkdGg9IjEwNy4xNTE4NCIKICAgICAgICAgaGVpZ2h0PSIxMDcuMTUxODQiCiAgICAgICAgIHg9IjE2LjIxOTkxIgogICAgICAgICB5PSIxMy4zNjI4ODMiCiAgICAgICAgIHJ5PSIyMC4xNzk3OSIgLz4KICAgIDwvZz4KICAgIDx0ZXh0CiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIgogICAgICAgc3R5bGU9ImZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXNpemU6MTEzLjM4M3B4O2xpbmUtaGVpZ2h0OjEuMjU7Zm9udC1mYW1pbHk6c2Fucy1zZXJpZjtmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWRhc2hhcnJheTpub25lIgogICAgICAgeD0iMTc3LjcxMDM5IgogICAgICAgeT0iOTUuODk5MTc4IgogICAgICAgaWQ9InRleHQ2MyI+PHRzcGFuCiAgICAgICAgIHNvZGlwb2RpOnJvbGU9ImxpbmUiCiAgICAgICAgIGlkPSJ0c3BhbjYxIgogICAgICAgICB4PSIxNzcuNzEwMzkiCiAgICAgICAgIHk9Ijk1Ljg5OTE3OCIKICAgICAgICAgc3R5bGU9ImZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7Zm9udC1zaXplOjExMy4zODNweDtmb250LWZhbWlseTonTm90byBTYW5zIE1vbm8nOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246J05vdG8gU2FucyBNb25vJztmaWxsOiMwMDAwMDA7c3Ryb2tlOiNmZmZmZmY7c3Ryb2tlLXdpZHRoOjE7c3Ryb2tlLWRhc2hhcnJheTpub25lIj5icGY8dHNwYW4KICAgc3R5bGU9ImZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7Zm9udC1mYW1pbHk6J05vdG8gU2FucyBNb25vJzstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOidOb3RvIFNhbnMgTW9ubyc7ZmlsbDojMDAwMDAwO3N0cm9rZTojZmZmZmZmO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZSIKICAgaWQ9InRzcGFuMTI5NzUiPmQ8dHNwYW4KICAgc3R5bGU9ImZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7Zm9udC1mYW1pbHk6J05vdG8gU2FucyBNb25vJzstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOidOb3RvIFNhbnMgTW9ubyc7ZmlsbDojMDAwMDAwO3N0cm9rZS13aWR0aDoxO3N0cm9rZS1kYXNoYXJyYXk6bm9uZSIKICAgaWQ9InRzcGFuMTMwMzEiIC8+PC90c3Bhbj48L3RzcGFuPjwvdGV4dD4KICA8L2c+Cjwvc3ZnPgo= + - base64data: | + PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjwh + RE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cu + dzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjwhLS0gQ3JlYXRlZCB3aXRo + IFZlY3Rvcm5hdG9yIChodHRwOi8vdmVjdG9ybmF0b3IuaW8vKSAtLT4KPHN2ZyBoZWlnaHQ9IjEw + MCUiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3R5bGU9ImZpbGwtcnVsZTpub256ZXJvO2NsaXAt + cnVsZTpldmVub2RkO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDsi + IHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgd2lkdGg9IjEwMCUiIHhtbDpz + cGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6 + dmVjdG9ybmF0b3I9Imh0dHA6Ly92ZWN0b3JuYXRvci5pbyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93 + d3cudzMub3JnLzE5OTkveGxpbmsiPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGdyYWRpZW50VHJh + bnNmb3JtPSJtYXRyaXgoMS40NTY4MyAxLjQ1NjgzIC0xLjQ1NjgzIDEuNDU2ODMgNzkxLjI0NCAt + NzA0LjEzMykiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBpZD0iTGluZWFyR3JhZGll + bnQiIHgxPSIzNjguNDYyIiB4Mj0iMjQyLjMzMSIgeTE9IjQyNy4wNTMiIHkyPSI1NTMuNjE0Ij4K + PHN0b3Agb2Zmc2V0PSIwLjQyMjU5MiIgc3RvcC1jb2xvcj0iIzMzMzEyYyIvPgo8c3RvcCBvZmZz + ZXQ9IjEiIHN0b3AtY29sb3I9IiNmM2M2MjIiLz4KPC9saW5lYXJHcmFkaWVudD4KPHBhdGggZD0i + TTQzNS4xMTMgNDQ4LjQxM0M0MzUuMTEzIDM3NS43MjMgNDkzLjc5MyAyNjguNjkyIDUxMS4yMDkg + MjY4LjY5MkM1MjguNjI1IDI2OC42OTIgNTg4Ljg3MyAzNzAuOTU3IDU4OC44NzMgNDQzLjY0NkM1 + ODguODczIDUxNi4zMzYgNTMyLjc4NSA2MDAuNzc3IDUxMS4yMDkgNjAwLjc3N0M0ODkuNjMzIDYw + MC43NzcgNDM1LjExMyA1MjEuMTAzIDQzNS4xMTMgNDQ4LjQxM1oiIGlkPSJGaWxsIi8+CjxwYXRo + IGQ9Ik00MzUuMTEzIDQ0OC40MTNDNDM1LjExMyAzNzUuNzIzIDQ5My43OTMgMjY4LjY5MiA1MTEu + MjA5IDI2OC42OTJDNTI4LjYyNSAyNjguNjkyIDU4OC44NzMgMzcwLjk1NyA1ODguODczIDQ0My42 + NDZDNTg4Ljg3MyA1MTYuMzM2IDUzMi43ODUgNjAwLjc3NyA1MTEuMjA5IDYwMC43NzdDNDg5LjYz + MyA2MDAuNzc3IDQzNS4xMTMgNTIxLjEwMyA0MzUuMTEzIDQ0OC40MTNaIiBpZD0iRmlsbF8yIi8+ + CjxwYXRoIGQ9Ik00MzUuMTEzIDQ0OC40MTNDNDM1LjExMyAzNzUuNzIzIDQ5My43OTMgMjY4LjY5 + MiA1MTEuMjA5IDI2OC42OTJDNTI4LjYyNSAyNjguNjkyIDU4OC44NzMgMzcwLjk1NyA1ODguODcz + IDQ0My42NDZDNTg4Ljg3MyA1MTYuMzM2IDUzMi43ODUgNjAwLjc3NyA1MTEuMjA5IDYwMC43NzdD + NDg5LjYzMyA2MDAuNzc3IDQzNS4xMTMgNTIxLjEwMyA0MzUuMTEzIDQ0OC40MTNaIiBpZD0iRmls + bF8zIi8+CjxwYXRoIGQ9Ik00MzUuMTIgMzY4LjgzOEM0MzUuMTIgMzY4LjgzOCA0NzQuMjE5IDM3 + OC4yNjMgNTEyLjAwNyAzNzguMzM1QzU0OS43OTYgMzc4LjQwNyA1ODguODggMzY5LjM4NSA1ODgu + ODggMzY5LjM4NSIgaWQ9IkZpbGxfNCIvPgo8L2RlZnM+CjxnIGlkPSJMYXllci0xIiB2ZWN0b3Ju + YXRvcjpsYXllck5hbWU9IkxheWVyIDEiPgo8ZyBvcGFjaXR5PSIxIiB2ZWN0b3JuYXRvcjpsYXll + ck5hbWU9Ikdyb3VwIDEyIj4KPHBhdGggZD0iTTE4Ni4yMzMgMzg3LjI3OEMxODYuMjMzIDIwNy4z + NjQgMzMyLjA4NCA2MS41MTM5IDUxMS45OTggNjEuNTEzOUM2OTEuOTE2IDYxLjUxMzkgODM3Ljc2 + NyAyMDcuMzY0IDgzNy43NjcgMzg3LjI3OEM4MzcuNzY3IDU2Ny4xOTMgNjkxLjkxNiA3MTMuMDQz + IDUxMS45OTggNzEzLjA0M0MzMzIuMDg0IDcxMy4wNDMgMTg2LjIzMyA1NjcuMTkzIDE4Ni4yMzMg + Mzg3LjI3OCIgZmlsbD0iI2YyOGYyMiIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIxIiBz + dHJva2U9Im5vbmUiIHZlY3Rvcm5hdG9yOmxheWVyTmFtZT0icGF0aCIvPgo8ZyBvcGFjaXR5PSIx + IiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ikdyb3VwIDEzIj4KPHBhdGggZD0iTTIwMS4zMzEgNDg1 + LjQ4NUMyMDQuODQ3IDQ4MS41MzQgMjA4LjE4MiA0NzcuNTkzIDIxMS4yOTQgNDczLjY2QzI3MS4z + ODUgMzk3LjYzNiAyNDkuMTUgMzQzLjc2OSAxOTguMTU5IDI5OS45OTFDMTkwLjQ0MyAzMjcuNzg0 + IDE4Ni4yMzUgMzU3LjAzIDE4Ni4yMzUgMzg3LjI3N0MxODYuMjM1IDQyMS41MDkgMTkxLjU0NCA0 + NTQuNDkgMjAxLjMzMSA0ODUuNDg1IiBmaWxsPSIjZTBmMjIyIiBmaWxsLXJ1bGU9Im5vbnplcm8i + IG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJwYXRoIi8+ + CjxwYXRoIGQ9Ik00MjEuMTYzIDQwNS4wOThDNDQzLjY2IDMyMy4yNzkgMzQxLjE0MSAyNTIuNjQx + IDI0Ni4xNzkgMTk5LjA4QzIzOSAyMDkuMTk4IDIzMi4zNDIgMjE5LjcwMyAyMjYuMzM4IDIzMC42 + MjlDMzA5LjI2NSAyNzguMTY5IDM5MC43NjkgMzQyLjM1OSAzNTYuNjU0IDQyMy4zMDFDMzM0LjIy + NSA0NzYuNTA1IDI3OC43MTMgNTEzLjMwNyAyMzIuNzA4IDU1NS4wMDFDMjM4LjYxNCA1NjQuODE4 + IDI0NS4wMjcgNTc0LjI5MiAyNTEuOTA0IDU4My4zOTZDMzA5Ljk0NCA1MjQuODAxIDM5OS4zNTMg + NDg0LjQyNyA0MjEuMTYzIDQwNS4wOTgiIGZpbGw9IiNlMGYyMjIiIGZpbGwtcnVsZT0ibm9uemVy + byIgb3BhY2l0eT0iMSIgc3Ryb2tlPSJub25lIiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9InBhdGgi + Lz4KPHBhdGggZD0iTTU5Ny45MjMgMzIzLjgwN0M2MjkuMjkyIDQ0Mi45OTkgNDU0LjQwMiA1MTku + MzQxIDM4OC45NiA1OTIuMzA1QzM2Ni4zMDQgNjE3LjU2NiAzNDguODAxIDYzOS4xODcgMzM5LjMw + NiA2NjMuNDY0QzM0OC43NTggNjY5LjM4NyAzNTguNTE1IDY3NC44NTggMzY4LjU4NiA2NzkuODAx + QzM3My43NzQgNjU2LjU2NSAzODQuNTUgNjMyLjg1MSA0MDIuODQgNjA4LjIzNUM0NzEuODAyIDUx + NS40MTcgNjUwLjg2NSA0NTUuNTg1IDYyNi4zMTMgMzE4LjkwM0M2MDcuNzE4IDIxNS4zODYgNDk4 + LjE3NiAxNjEuODQyIDQ2MS45MjQgNjUuMzQ5N0M0MzguOTk3IDY4Ljg4NzIgNDE2Ljg5NSA3NC44 + NzQ4IDM5NS44MDggODIuOTI5OEM0NDcuMzAxIDE3My44MTMgNTcwLjgyNiAyMjAuODQxIDU5Ny45 + MjMgMzIzLjgwNyIgZmlsbD0iI2UwZjIyMiIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIx + IiBzdHJva2U9Im5vbmUiIHZlY3Rvcm5hdG9yOmxheWVyTmFtZT0icGF0aCIvPgo8cGF0aCBkPSJN + NzA1LjQ4MSAzMDQuMzA1Qzc0MC4zNzkgNDYwLjIwOSA1MTkuNjMxIDUyMy4yNjUgNDQ2LjAyNiA2 + MzAuMzQ1QzQzMC40ODQgNjUyLjk0OSA0MjMuMDc4IDY3Ni4zNzEgNDIxLjI1OSA3MDAuMTQxQzQz + NC4zMjYgNzAzLjkyMyA0NDcuNjk4IDcwNi45NzUgNDYxLjM4NCA3MDkuMTExQzQ2Mi43NzIgNjg0 + LjQ2OSA0NjkuOCA2NjAuMjI2IDQ4NS4xMTUgNjM2Ljg4N0M1NTguODAxIDUyNC41ODUgNzg2Ljk3 + MiA0NjguMDg4IDc2MC4xMDggMzA4LjUxM0M3NDcuMzg1IDIzMi45MjcgNzAwLjQ0MyAxNzQuMzU0 + IDY3NS4zNDEgMTA1LjQ2NUM2NTAuMDI4IDkwLjc2MDggNjIyLjU5NiA3OS4yOTMgNTkzLjU0OSA3 + MS44MDUzQzYxOC4xODYgMTU1Ljg1OSA2ODUuNzY0IDIxNi4yMzcgNzA1LjQ4MSAzMDQuMzA1IiBm + aWxsPSIjZTBmMjIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9u + ZSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJwYXRoIi8+CjxwYXRoIGQ9Ik0zMjQuMzM5IDYxMS4y + MjNDMzgxLjUzNyA1MzUuNzk2IDU2My4wOCA0NjMuODc3IDU1Mi43MyAzNTIuNzQ4QzU0My41OTIg + MjU0LjY0OCA0MDIuNjYzIDE5NC4wNzYgMzE2LjE1NSAxMjYuOTYyQzMwNi40NDEgMTM0LjI4MiAy + OTcuMiAxNDIuMTc0IDI4OC4zNzUgMTUwLjUxM0MzODUuNjg1IDIxMS44NTQgNTE1Ljk3MSAyNzcu + NDc2IDUxMy40MzUgMzY1LjIzOUM1MTAuMDk5IDQ4MC42MzIgMzQ3LjM3NCA1MzMuNDMyIDI4NC44 + ODEgNjIwLjcxM0MyOTEuNzU0IDYyNy40MDIgMjk4Ljg5MyA2MzMuODEgMzA2LjMzNCA2MzkuODc1 + QzMxMS4xNzQgNjMwLjI4MSAzMTcuMTMxIDYyMC43MjYgMzI0LjMzOSA2MTEuMjIzIiBmaWxsPSIj + ZTBmMjIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIgdmVj + dG9ybmF0b3I6bGF5ZXJOYW1lPSJwYXRoIi8+CjxwYXRoIGQ9Ik02MzguNDgxIDYyOS41MjRDNjc3 + LjY2OCA1NjEuMTY0IDc1MS4wODggNTI2LjczOSA4MjEuNTI0IDQ4OC44NjZDODIzLjkxIDQ4MS41 + ODkgODI2LjA4NCA0NzQuMjE4IDgyNy45NjMgNDY2LjcyMUM3NTkuNDc4IDUxNC4zNjQgNjc2LjQy + NiA1NDcuODQzIDYyNC42NzkgNjE3LjQzM0M2MDQuOTcxIDY0My45MzYgNTk3Ljc2NyA2NzIuMTkz + IDU5OC41MTEgNzAxLjM0QzYwNi43MTYgNjk5LjA4NCA2MTQuODE0IDY5Ni41NzMgNjIyLjc0NCA2 + OTMuNzA2QzYyMi4yNzYgNjcxLjQ0NiA2MjYuNzkzIDY0OS45MDcgNjM4LjQ4MSA2MjkuNTI0IiBm + aWxsPSIjZTBmMjIyIiBmaWxsLXJ1bGU9Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9u + ZSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJwYXRoIi8+CjxwYXRoIGQ9Ik02ODYuNDcxIDY0NC43 + NjRDNjgzLjU3OCA2NTEuNzQ0IDY4MS43IDY1OC44NjIgNjgwLjYxMiA2NjYuMDYyQzczMS43MzYg + NjM1LjA3NiA3NzMuNTgxIDU5MC4zODIgODAxLjIyNyA1MzcuMTI2Qzc0OS41NDkgNTY0LjQzMyA3 + MDYuMTc0IDU5Ny4yMjUgNjg2LjQ3MSA2NDQuNzY0IiBmaWxsPSIjZTBmMjIyIiBmaWxsLXJ1bGU9 + Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1l + PSJwYXRoIi8+CjxwYXRoIGQ9Ik04MzYuNjY2IDQxMi45MTlDODM3LjMyOCA0MDQuNDQ3IDgzNy43 + NjcgMzk1LjkxNSA4MzcuNzY3IDM4Ny4yOEM4MzcuNzY3IDM3MC42OCA4MzYuNTA3IDM1NC4zODEg + ODM0LjExMyAzMzguNDUxQzgyMi42OCA0NzIuMzY1IDYzNC41MjEgNTIwLjkyMyA1NjMuMzkzIDYy + MC4wNjhDNTQwLjY1OSA2NTEuNzU5IDUzMC41NjIgNjgyLjM3NiA1MjguNjE5IDcxMi42MjNDNTM4 + LjA0MSA3MTIuMTUgNTQ3LjMzNCA3MTEuMTk2IDU1Ni41MzMgNzA5LjkzN0M1NTcuNDc4IDY4Mi44 + MjMgNTY0Ljg1OCA2NTUuNTggNTgxLjg3MSA2MjguMDY3QzYzNy45ODEgNTM3LjMyNSA3NzQuNjg5 + IDQ5OC43MDUgODM2LjY2NiA0MTIuOTE5IiBmaWxsPSIjZTBmMjIyIiBmaWxsLXJ1bGU9Im5vbnpl + cm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJwYXRo + Ii8+CjwvZz4KPC9nPgo8ZyBvcGFjaXR5PSIxIiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ikdyb3Vw + IDEzIj4KPHBhdGggZD0iTTQzNS4xMTMgNDQ4LjQxM0M0MzUuMTEzIDM3NS43MjMgNDkzLjc5MyAy + NjguNjkyIDUxMS4yMDkgMjY4LjY5MkM1MjguNjI1IDI2OC42OTIgNTg4Ljg3MyAzNzAuOTU3IDU4 + OC44NzMgNDQzLjY0NkM1ODguODczIDUxNi4zMzYgNTMyLjc4NSA2MDAuNzc3IDUxMS4yMDkgNjAw + Ljc3N0M0ODkuNjMzIDYwMC43NzcgNDM1LjExMyA1MjEuMTAzIDQzNS4xMTMgNDQ4LjQxM1oiIGZp + bGw9InVybCgjTGluZWFyR3JhZGllbnQpIiBmaWxsLXJ1bGU9Im5vbnplcm8iIG9wYWNpdHk9IjEi + IHN0cm9rZT0iIzMzMzEyYyIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0i + cm91bmQiIHN0cm9rZS13aWR0aD0iMjQuNDgxOSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJPdmFs + IDEiLz4KPHBhdGggZD0iTTQzOS4yMzMgMjY4LjY5MkM0MzkuMjMzIDI2OC42OTIgNDQxLjE1MiAx + OTUuOTMzIDUxMS45OTMgMTk1LjkzM0M1ODIuODM0IDE5NS45MzMgNTg0Ljc1MiAyNjguNjkyIDU4 + NC43NTIgMjY4LjY5Mkw0MzkuMjMzIDI2OC42OTJaIiBmaWxsPSIjZjNjNjIyIiBmaWxsLXJ1bGU9 + Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0iIzMzMzEyYyIgc3Ryb2tlLWxpbmVjYXA9ImJ1 + dHQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMjQuNDgxOSIgdmVjdG9y + bmF0b3I6bGF5ZXJOYW1lPSJPdmFsIDIiLz4KPHBhdGggZD0iTTQ4MC4xODggMTk1LjkzM0M0ODAu + MTg4IDE5NS45MzMgNDY3LjUxOCAxNjYuMzQ1IDQ0Mi4zMjQgMTc1LjU1OCIgZmlsbD0ibm9uZSIg + b3BhY2l0eT0iMSIgc3Ryb2tlPSIjMzMzMTJjIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9r + ZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMjQuNDgxOSIgdmVjdG9ybmF0b3I6bGF5 + ZXJOYW1lPSJDdXJ2ZSAyIi8+CjxwYXRoIGQ9Ik01NDUuMzM3IDE5NS45MzNDNTQ1LjMzNyAxOTUu + OTMzIDU1OC4wMDYgMTY2LjM0NSA1ODMuMjAxIDE3NS41NTgiIGZpbGw9Im5vbmUiIG9wYWNpdHk9 + IjEiIHN0cm9rZT0iIzMzMzEyYyIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpv + aW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjI0LjQ4MTkiIHZlY3Rvcm5hdG9yOmxheWVyTmFtZT0i + Q3VydmUgMyIvPgo8ZyBvcGFjaXR5PSIxIiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ikdyb3VwIDIi + Pgo8dXNlIGZpbGw9Im5vbmUiIG9wYWNpdHk9IjEiIHN0cm9rZT0iI2YyYWMyMiIgc3Ryb2tlLWxp + bmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMjQuNDgx + OSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJPdmFsIDUiIHhsaW5rOmhyZWY9IiNGaWxsIi8+Cjxj + bGlwUGF0aCBjbGlwLXJ1bGU9Im5vbnplcm8iIGlkPSJDbGlwUGF0aCI+Cjx1c2UgeGxpbms6aHJl + Zj0iI0ZpbGwiLz4KPC9jbGlwUGF0aD4KPGcgY2xpcC1wYXRoPSJ1cmwoI0NsaXBQYXRoKSI+Cjxw + YXRoIGQ9Ik00MzUuMTEzIDQyNS4yMzhDNDM1LjExMyA0MjUuMjM4IDQ3NC4yMTIgNDM0LjY2MyA1 + MTIgNDM0LjczNUM1NDkuNzg4IDQzNC44MDcgNTg4Ljg3MyA0MjUuNzg1IDU4OC44NzMgNDI1Ljc4 + NSIgZmlsbD0ibm9uZSIgb3BhY2l0eT0iMSIgc3Ryb2tlPSIjZjJkZTIyIiBzdHJva2UtbGluZWNh + cD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIyNC40ODE5IiB2 + ZWN0b3JuYXRvcjpsYXllck5hbWU9IkN1cnZlIDQiLz4KPC9nPgo8L2c+CjxnIG9wYWNpdHk9IjEi + IHZlY3Rvcm5hdG9yOmxheWVyTmFtZT0iR3JvdXAgMyI+Cjx1c2UgZmlsbD0ibm9uZSIgb3BhY2l0 + eT0iMSIgc3Ryb2tlPSIjZjNjNjIyIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVq + b2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyNC40ODE5IiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9 + Ik92YWwgNCIgeGxpbms6aHJlZj0iI0ZpbGxfMiIvPgo8Y2xpcFBhdGggY2xpcC1ydWxlPSJub256 + ZXJvIiBpZD0iQ2xpcFBhdGhfMiI+Cjx1c2UgeGxpbms6aHJlZj0iI0ZpbGxfMiIvPgo8L2NsaXBQ + YXRoPgo8ZyBjbGlwLXBhdGg9InVybCgjQ2xpcFBhdGhfMikiPgo8cGF0aCBkPSJNNDM5LjIzMyA0 + ODEuNjUzQzQzOS4yMzMgNDgxLjY1MyA0NzUuNjIgNDkzLjg5MiA1MTIgNDkzLjg5MkM1NDguMzgg + NDkzLjg5MiA1ODQuNzUyIDQ4MS42NTMgNTg0Ljc1MiA0ODEuNjUzIiBmaWxsPSJub25lIiBvcGFj + aXR5PSIxIiBzdHJva2U9IiNmM2M2MjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxp + bmVqb2luPSJtaXRlciIgc3Ryb2tlLXdpZHRoPSIyNC40ODE5IiB2ZWN0b3JuYXRvcjpsYXllck5h + bWU9IkN1cnZlIDUiLz4KPC9nPgo8L2c+CjxnIG9wYWNpdHk9IjEiIHZlY3Rvcm5hdG9yOmxheWVy + TmFtZT0iR3JvdXAgNCI+Cjx1c2UgZmlsbD0ibm9uZSIgb3BhY2l0eT0iMSIgc3Ryb2tlPSIjZjJk + ZTIyIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tl + LXdpZHRoPSIyNC40ODE5IiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ik92YWwgMyIgeGxpbms6aHJl + Zj0iI0ZpbGxfMyIvPgo8Y2xpcFBhdGggY2xpcC1ydWxlPSJub256ZXJvIiBpZD0iQ2xpcFBhdGhf + MyI+Cjx1c2UgeGxpbms6aHJlZj0iI0ZpbGxfMyIvPgo8L2NsaXBQYXRoPgo8ZyBjbGlwLXBhdGg9 + InVybCgjQ2xpcFBhdGhfMykiPgo8cGF0aCBkPSJNNDYxLjI1NiA1NDAuMTc0QzQ2MS4yNTYgNTQw + LjE3NCA0ODcuNDU5IDU0OS41ODYgNTExLjk5MyA1NDkuNTIzQzUzNi41MjcgNTQ5LjQ2MSA1NTku + MzkxIDUzOS45MjUgNTU5LjM5MSA1MzkuOTI1IiBmaWxsPSJub25lIiBvcGFjaXR5PSIxIiBzdHJv + a2U9IiNmMmRlMjIiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIHN0cm9rZS1saW5lam9pbj0ibWl0 + ZXIiIHN0cm9rZS13aWR0aD0iMjQuNDgxOSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJDdXJ2ZSA2 + Ii8+CjwvZz4KPC9nPgo8ZyBvcGFjaXR5PSIxIiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ikdyb3Vw + IDEiPgo8dXNlIGZpbGw9Im5vbmUiIG9wYWNpdHk9IjEiIHN0cm9rZT0iI2YzYzYyMiIgc3Ryb2tl + LWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS13aWR0aD0iMjQu + NDgxOSIgdmVjdG9ybmF0b3I6bGF5ZXJOYW1lPSJDdXJ2ZSA3IiB4bGluazpocmVmPSIjRmlsbF80 + Ii8+CjxjbGlwUGF0aCBjbGlwLXJ1bGU9Im5vbnplcm8iIGlkPSJDbGlwUGF0aF80Ij4KPHVzZSB4 + bGluazpocmVmPSIjRmlsbF80Ii8+CjwvY2xpcFBhdGg+CjxnIGNsaXAtcGF0aD0idXJsKCNDbGlw + UGF0aF80KSI+CjxwYXRoIGQ9Ik00MzUuMTEzIDQ0OC40MTNDNDM1LjExMyAzNzUuNzIzIDQ5My43 + OTMgMjY4LjY5MiA1MTEuMjA5IDI2OC42OTJDNTI4LjYyNSAyNjguNjkyIDU4OC44NzMgMzcwLjk1 + NyA1ODguODczIDQ0My42NDZDNTg4Ljg3MyA1MTYuMzM2IDUzMi43ODUgNjAwLjc3NyA1MTEuMjA5 + IDYwMC43NzdDNDg5LjYzMyA2MDAuNzc3IDQzNS4xMTMgNTIxLjEwMyA0MzUuMTEzIDQ0OC40MTNa + IiBmaWxsPSJub25lIiBvcGFjaXR5PSIxIiBzdHJva2U9IiNmM2M2MjIiIHN0cm9rZS1saW5lY2Fw + PSJidXR0IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjI0LjQ4MTkiIHZl + Y3Rvcm5hdG9yOmxheWVyTmFtZT0iT3ZhbCA0Ii8+CjwvZz4KPC9nPgo8cGF0aCBkPSJNNDM1LjEx + MyA0NDguNDEzQzQzNS4xMTMgMzc1LjcyMyA0OTMuNzkzIDI2OC42OTIgNTExLjIwOSAyNjguNjky + QzUyOC42MjUgMjY4LjY5MiA1ODguODczIDM3MC45NTcgNTg4Ljg3MyA0NDMuNjQ2QzU4OC44NzMg + NTE2LjMzNiA1MzIuNzg1IDYwMC43NzcgNTExLjIwOSA2MDAuNzc3QzQ4OS42MzMgNjAwLjc3NyA0 + MzUuMTEzIDUyMS4xMDMgNDM1LjExMyA0NDguNDEzWiIgZmlsbD0ibm9uZSIgb3BhY2l0eT0iMSIg + c3Ryb2tlPSIjMzMzMTJjIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJy + b3VuZCIgc3Ryb2tlLXdpZHRoPSIyNC40ODE5IiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ik92YWwg + MyIvPgo8cGF0aCBkPSJNNDkwLjQ1MyAyNjkuNDQ2QzQ1MC4xMjUgMjY5LjgwMyAzNjEuNTgxIDI4 + NS40MjggMjk4LjI2OSAzMjUuNTU2QzE5NS4wNDcgMzkwLjk3OCAyNjQuMjI0IDQ2OS4wODkgMzI2 + LjIxMSA0NjguMjI5QzQyMi4xODYgNDY2Ljg5NyA1MTEuOTg5IDMwMC4yNjIgNTExLjk4OSAyNzMu + ODU2QzUxMS45ODkgMjcyLjExMiA1MDkuMTIgMjcwLjgzOSA1MDMuOTYxIDI3MC4xMkM1MDAuNDU1 + IDI2OS42MzEgNDk1Ljg5MiAyNjkuMzk4IDQ5MC40NTMgMjY5LjQ0NlpNNTExLjk4OSAyNzMuODU2 + QzUxMS45ODkgMzAwLjI2MiA2MDEuNzkyIDQ2Ni44OTcgNjk3Ljc2NyA0NjguMjI5Qzc1OS43NTQg + NDY5LjA4OSA4MjguOTYzIDM5MC45NzggNzI1Ljc0MSAzMjUuNTU2QzY1NC4yMzkgMjgwLjIzNyA1 + NTAuNTA5IDI2Ni4xOTIgNTIwLjQ0NSAyNzAuMDc2QzUxNS4wMTYgMjcwLjc3NyA1MTEuOTg5IDI3 + Mi4wNjQgNTExLjk4OSAyNzMuODU2WiIgZmlsbD0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJub256ZXJv + IiBvcGFjaXR5PSIxIiBzdHJva2U9IiMzMzMxMmMiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJv + a2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjI0LjQ4MTkiIHZlY3Rvcm5hdG9yOmxh + eWVyTmFtZT0iQ3VydmUgMSIvPgo8L2c+CjxnIG9wYWNpdHk9IjEiIHZlY3Rvcm5hdG9yOmxheWVy + TmFtZT0iR3JvdXAgMTQiPgo8cGF0aCBkPSJNMTU0LjkxOSA5MjQuNTMzQzE0NS4zOTQgOTI0LjUz + MyAxMzcuMTYxIDkyMi41MTcgMTMwLjIyMSA5MTguNDg1QzEyMy4yOCA5MTQuNDU0IDExNy45Njkg + OTA4LjI2NCAxMTQuMjg2IDg5OS45MThDMTEwLjYwMyA4OTEuNTcxIDEwOC43NjEgODgwLjk5MiAx + MDguNzYxIDg2OC4xODJDMTA4Ljc2MSA4NTUuMzQzIDExMC42ODQgODQ0Ljc4NCAxMTQuNTMxIDgz + Ni41MDZDMTE4LjM3NyA4MjguMjI4IDEyMy43ODQgODIyLjA2IDEzMC43NTEgODE4LjAwMkMxMzcu + NzE4IDgxMy45NDQgMTQ1Ljc3NCA4MTEuOTE0IDE1NC45MTkgODExLjkxNEMxNjUuMjY1IDgxMS45 + MTQgMTc0LjU1MSA4MTQuMjIyIDE4Mi43NzggODE4LjgzN0MxOTEuMDA1IDgyMy40NTIgMTk3LjUy + IDgyOS45NjcgMjAyLjMyNCA4MzguMzgxQzIwNy4xMjggODQ2Ljc5NiAyMDkuNTMgODU2LjczIDIw + OS41MyA4NjguMTgyQzIwOS41MyA4NzkuNjM1IDIwNy4xMjggODg5LjU2OSAyMDIuMzI0IDg5Ny45 + ODNDMTk3LjUyIDkwNi4zOTggMTkxLjAwNSA5MTIuOTI3IDE4Mi43NzggOTE3LjU2OUMxNzQuNTUx + IDkyMi4yMTIgMTY1LjI2NSA5MjQuNTMzIDE1NC45MTkgOTI0LjUzM1pNOTAuMzA5NyA5MjIuOTg3 + TDkwLjMwOTcgNzcxLjkxM0wxMjIuMDcyIDc3MS45MTNMMTIyLjA3MiA4MzUuNTcxTDEyMC4wMzYg + ODY4LjA1OEwxMjAuNTcgOTAwLjU0OUwxMjAuNTcgOTIyLjk4N0w5MC4zMDk3IDkyMi45ODdaTTE0 + OS40NTQgODk4LjUyNkMxNTQuNzMgODk4LjUyNiAxNTkuNDYzIDg5Ny4zMjIgMTYzLjY1MiA4OTQu + OTE1QzE2Ny44NDEgODkyLjUwOCAxNzEuMTc5IDg4OS4wMDcgMTczLjY2OCA4ODQuNDEyQzE3Ni4x + NTcgODc5LjgxNyAxNzcuNDAxIDg3NC40MDcgMTc3LjQwMSA4NjguMTgyQzE3Ny40MDEgODYxLjgy + MiAxNzYuMTU3IDg1Ni4zOTEgMTczLjY2OCA4NTEuODkxQzE3MS4xNzkgODQ3LjM5IDE2Ny44NDEg + ODQzLjkzNyAxNjMuNjUyIDg0MS41MjlDMTU5LjQ2MyA4MzkuMTIyIDE1NC43MyA4MzcuOTE5IDE0 + OS40NTQgODM3LjkxOUMxNDQuMTc3IDgzNy45MTkgMTM5LjQ0NCA4MzkuMTIyIDEzNS4yNTQgODQx + LjUyOUMxMzEuMDY0IDg0My45MzcgMTI3LjcyNSA4NDcuMzkgMTI1LjIzNiA4NTEuODkxQzEyMi43 + NDcgODU2LjM5MSAxMjEuNTAzIDg2MS44MjIgMTIxLjUwMyA4NjguMTgyQzEyMS41MDMgODc0LjQw + NyAxMjIuNzQ3IDg3OS44MTcgMTI1LjIzNiA4ODQuNDEyQzEyNy43MjUgODg5LjAwNyAxMzEuMDY0 + IDg5Mi41MDggMTM1LjI1NCA4OTQuOTE1QzEzOS40NDQgODk3LjMyMiAxNDQuMTc3IDg5OC41MjYg + MTQ5LjQ1NCA4OTguNTI2WiIgZmlsbD0iI2YzYzYyMiIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFj + aXR5PSIxIiBzdHJva2U9Im5vbmUiLz4KPHBhdGggZD0iTTI5NS40NzYgOTI0LjUzM0MyODYuMzMx + IDkyNC41MzMgMjc4LjI3NSA5MjIuNTA0IDI3MS4zMDggOTE4LjQ0NkMyNjQuMzQxIDkxNC4zODcg + MjU4LjkzNCA5MDguMTk4IDI1NS4wODggODk5Ljg3OEMyNTEuMjQyIDg5MS41NTggMjQ5LjMxOSA4 + ODEuMDE5IDI0OS4zMTkgODY4LjI2MkMyNDkuMzE5IDg1NS4zMTYgMjUxLjE2IDg0NC43MDQgMjU0 + Ljg0MyA4MzYuNDI1QzI1OC41MjYgODI4LjE0NiAyNjMuODM4IDgyMS45OTEgMjcwLjc3OCA4MTcu + OTYxQzI3Ny43MTkgODEzLjkzIDI4NS45NTEgODExLjkxNCAyOTUuNDc2IDgxMS45MTRDMzA1Ljgy + MiA4MTEuOTE0IDMxNS4xMDggODE0LjIzNSAzMjMuMzM1IDgxOC44NzdDMzMxLjU2MiA4MjMuNTE4 + IDMzOC4wNzcgODMwLjA0NiAzNDIuODgxIDgzOC40NjFDMzQ3LjY4NSA4NDYuODc2IDM1MC4wODcg + ODU2LjgwOSAzNTAuMDg3IDg2OC4yNjJDMzUwLjA4NyA4NzkuNzE3IDM0Ny42ODUgODg5LjY1MSAz + NDIuODgxIDg5OC4wNjZDMzM4LjA3NyA5MDYuNDgxIDMzMS41NjIgOTEyLjk5NSAzMjMuMzM1IDkx + Ny42MUMzMTUuMTA4IDkyMi4yMjUgMzA1LjgyMiA5MjQuNTMzIDI5NS40NzYgOTI0LjUzM1pNMjMw + Ljg2NyA5NjIuNDg2TDIzMC44NjcgODEzLjQ1N0wyNjEuMTI4IDgxMy40NTdMMjYxLjEyOCA4MzUu + ODk1TDI2MC41OTMgODY4LjM4NkwyNjIuNjI5IDkwMC44NzdMMjYyLjYyOSA5NjIuNDg2TDIzMC44 + NjcgOTYyLjQ4NlpNMjkwLjAxMSA4OTguNTI2QzI5NS4yODggODk4LjUyNiAzMDAuMDIgODk3LjMy + MiAzMDQuMjA5IDg5NC45MTVDMzA4LjM5OCA4OTIuNTA4IDMxMS43MzYgODg5LjAyIDMxNC4yMjUg + ODg0LjQ1MkMzMTYuNzE0IDg3OS44ODMgMzE3Ljk1OSA4NzQuNDg3IDMxNy45NTkgODY4LjI2MkMz + MTcuOTU5IDg2MS45MDEgMzE2LjcxNCA4NTYuNDU4IDMxNC4yMjUgODUxLjkzMUMzMTEuNzM2IDg0 + Ny40MDQgMzA4LjM5OCA4NDMuOTM3IDMwNC4yMDkgODQxLjUyOUMzMDAuMDIgODM5LjEyMiAyOTUu + Mjg4IDgzNy45MTkgMjkwLjAxMSA4MzcuOTE5QzI4NC43MzQgODM3LjkxOSAyODAuMDAxIDgzOS4x + MjIgMjc1LjgxMSA4NDEuNTI5QzI3MS42MjEgODQzLjkzNyAyNjguMjgyIDg0Ny40MDQgMjY1Ljc5 + MyA4NTEuOTMxQzI2My4zMDQgODU2LjQ1OCAyNjIuMDYgODYxLjkwMSAyNjIuMDYgODY4LjI2MkMy + NjIuMDYgODc0LjQ4NyAyNjMuMzA0IDg3OS44ODMgMjY1Ljc5MyA4ODQuNDUyQzI2OC4yODIgODg5 + LjAyIDI3MS42MjEgODkyLjUwOCAyNzUuODExIDg5NC45MTVDMjgwLjAwMSA4OTcuMzIyIDI4NC43 + MzQgODk4LjUyNiAyOTAuMDExIDg5OC41MjZaIiBmaWxsPSIjZjNjNjIyIiBmaWxsLXJ1bGU9Im5v + bnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIvPgo8cGF0aCBkPSJNMzc1LjI4NiA5MjIu + OTg3TDM3NS4yODYgODEwLjk3QzM3NS4yODYgNzk4LjYzIDM3OC45NDEgNzg4Ljc3OCAzODYuMjQ5 + IDc4MS40MTRDMzkzLjU1OCA3NzQuMDQ5IDQwNC4wMDYgNzcwLjM2NiA0MTcuNTk1IDc3MC4zNjZD + NDIyLjE1MiA3NzAuMzY2IDQyNi41ODEgNzcwLjgyOCA0MzAuODggNzcxLjc1MkM0MzUuMTc5IDc3 + Mi42NzYgNDM4LjgyIDc3NC4xMjkgNDQxLjgwNCA3NzYuMTEyTDQzMy41MTQgNzk5LjExQzQzMS43 + NzUgNzk3LjkzOSA0MjkuNzk4IDc5NyA0MjcuNTgyIDc5Ni4yOTNDNDI1LjM2NyA3OTUuNTg1IDQy + My4wNjMgNzk1LjIzMiA0MjAuNjcxIDc5NS4yMzJDNDE2LjAyMiA3OTUuMjMyIDQxMi40MzMgNzk2 + LjU1NyA0MDkuOTA1IDc5OS4yMDhDNDA3LjM3NyA4MDEuODU5IDQwNi4xMTMgODA1Ljg3NCA0MDYu + MTEzIDgxMS4yNTNMNDA2LjExMyA4MjEuNDYyTDQwNy4wNDkgODM1LjExNkw0MDcuMDQ5IDkyMi45 + ODdMMzc1LjI4NiA5MjIuOTg3Wk0zNTguMjk1IDg0MC4yNkwzNTguMjk1IDgxNS44ODJMNDM0LjI3 + NCA4MTUuODgyTDQzNC4yNzQgODQwLjI2TDM1OC4yOTUgODQwLjI2WiIgZmlsbD0iI2YzYzYyMiIg + ZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIxIiBzdHJva2U9Im5vbmUiLz4KPC9nPgo8ZyBv + cGFjaXR5PSIxIiB2ZWN0b3JuYXRvcjpsYXllck5hbWU9Ikdyb3VwIDE1Ij4KPHBhdGggZD0iTTYy + Mi45MDYgODExLjkxNEM2MzEuNTY4IDgxMS45MTQgNjM5LjI1OCA4MTMuNjI0IDY0NS45NzcgODE3 + LjA0NEM2NTIuNjk2IDgyMC40NjQgNjU3Ljk2OSA4MjUuNzM3IDY2MS43OTYgODMyLjg2MkM2NjUu + NjIzIDgzOS45ODcgNjY3LjUzNyA4NDkuMTQgNjY3LjUzNyA4NjAuMzIxTDY2Ny41MzcgOTIyLjk4 + N0w2MzUuNzc1IDkyMi45ODdMNjM1Ljc3NSA4NjUuMDY4QzYzNS43NzUgODU2LjI2MiA2MzMuOTQ1 + IDg0OS43NjUgNjMwLjI4NyA4NDUuNTc4QzYyNi42MjggODQxLjM5IDYyMS41MzMgODM5LjI5NiA2 + MTUuMDAxIDgzOS4yOTZDNjEwLjMyIDgzOS4yOTYgNjA2LjE1NyA4NDAuMzQzIDYwMi41MTEgODQy + LjQzOEM1OTguODY1IDg0NC41MzIgNTk2LjAyOSA4NDcuNjczIDU5NC4wMDMgODUxLjg2MUM1OTEu + OTc2IDg1Ni4wNDggNTkwLjk2MyA4NjEuNDMyIDU5MC45NjMgODY4LjAxMUw1OTAuOTYzIDkyMi45 + ODdMNTU5LjE5NyA5MjIuOTg3TDU1OS4xOTcgODY1LjA2OEM1NTkuMTk3IDg1Ni4yNjIgNTU3LjM5 + NSA4NDkuNzY1IDU1My43OTEgODQ1LjU3OEM1NTAuMTg2IDg0MS4zOSA1NDUuMDY0IDgzOS4yOTYg + NTM4LjQyMyA4MzkuMjk2QzUzMy43NDUgODM5LjI5NiA1MjkuNTgyIDg0MC4zNDMgNTI1LjkzNSA4 + NDIuNDM4QzUyMi4yODggODQ0LjUzMiA1MTkuNDUyIDg0Ny42NzMgNTE3LjQyNSA4NTEuODYxQzUx + NS4zOTkgODU2LjA0OCA1MTQuMzg1IDg2MS40MzIgNTE0LjM4NSA4NjguMDExTDUxNC4zODUgOTIy + Ljk4N0w0ODIuNjIzIDkyMi45ODdMNDgyLjYyMyA4MTMuNDU3TDUxMi44ODQgODEzLjQ1N0w1MTIu + ODg0IDg0My4zMzZMNTA3LjExOSA4MzQuNjIzQzUxMC45MTggODI3LjE4OSA1MTYuMzI1IDgyMS41 + NDYgNTIzLjM0MiA4MTcuNjkzQzUzMC4zNTkgODEzLjg0MSA1MzguMzM0IDgxMS45MTQgNTQ3LjI2 + NyA4MTEuOTE0QzU1Ny4yODYgODExLjkxNCA1NjYuMDYzIDgxNC40MzkgNTczLjU5NiA4MTkuNDg4 + QzU4MS4xMjkgODI0LjUzNiA1ODYuMTQ0IDgzMi4zMTIgNTg4LjY0IDg0Mi44MTVMNTc3LjQyIDgz + OS43MTZDNTgxLjA4MSA4MzEuMjI0IDU4Ni45MzYgODI0LjQ2NyA1OTQuOTg3IDgxOS40NDZDNjAz + LjAzOCA4MTQuNDI1IDYxMi4zNDQgODExLjkxNCA2MjIuOTA2IDgxMS45MTRaIiBmaWxsPSIjOTk5 + OTk5IiBmaWxsLXJ1bGU9Im5vbnplcm8iIG9wYWNpdHk9IjEiIHN0cm9rZT0ibm9uZSIvPgo8cGF0 + aCBkPSJNNzYzLjIzOCA5MjIuOTg3TDc2My4yMzggOTAxLjY0TDc2MS4zMzMgODk2LjgzNkw3NjEu + MzMzIDg1OC42MzhDNzYxLjMzMyA4NTEuODE2IDc1OS4yNiA4NDYuNTI5IDc1NS4xMTUgODQyLjc3 + OEM3NTAuOTY5IDgzOS4wMjcgNzQ0LjU5MSA4MzcuMTUyIDczNS45ODEgODM3LjE1MkM3MzAuMjIg + ODM3LjE1MiA3MjQuNTE2IDgzOC4wNTUgNzE4Ljg2NyA4MzkuODYxQzcxMy4yMTggODQxLjY2NyA3 + MDguMzk3IDg0NC4xMzIgNzA0LjQwMyA4NDcuMjU2TDY5My4wNjIgODI1LjE1MkM2OTkuMDg1IDgy + MC44MzQgNzA2LjI5NiA4MTcuNTQ4IDcxNC42OTQgODE1LjI5NUM3MjMuMDkzIDgxMy4wNDEgNzMx + LjYzNiA4MTEuOTE0IDc0MC4zMjMgODExLjkxNEM3NTcuMDg3IDgxMS45MTQgNzcwLjA3MyA4MTUu + ODQyIDc3OS4yODIgODIzLjY5NkM3ODguNDkgODMxLjU1MSA3OTMuMDk1IDg0My43OTMgNzkzLjA5 + NSA4NjAuNDIzTDc5My4wOTUgOTIyLjk4N0w3NjMuMjM4IDkyMi45ODdaTTcyOS45NTggOTI0LjUz + M0M3MjEuNDM5IDkyNC41MzMgNzE0LjEyNSA5MjMuMDk1IDcwOC4wMTcgOTIwLjIxOEM3MDEuOTA5 + IDkxNy4zNDEgNjk3LjIzMiA5MTMuMzkyIDY5My45ODcgOTA4LjM3QzY5MC43NDIgOTAzLjM0OSA2 + ODkuMTIgODk3LjY5IDY4OS4xMiA4OTEuMzkzQzY4OS4xMiA4ODQuOTM3IDY5MC43MDYgODc5LjI1 + OSA2OTMuODc5IDg3NC4zNTlDNjk3LjA1MiA4NjkuNDU5IDcwMi4wOTggODY1LjYxNyA3MDkuMDE3 + IDg2Mi44MzNDNzE1LjkzNyA4NjAuMDQ5IDcyNC45NjEgODU4LjY1OCA3MzYuMDg5IDg1OC42NThM + NzY1LjA3NCA4NTguNjU4TDc2NS4wNzQgODc3LjE2Nkw3MzkuNjExIDg3Ny4xNjZDNzMyLjEwMyA4 + NzcuMTY2IDcyNi45NjMgODc4LjM3NyA3MjQuMTkxIDg4MC43OThDNzIxLjQxOSA4ODMuMjE5IDcy + MC4wMzMgODg2LjMxOCA3MjAuMDMzIDg5MC4wOTVDNzIwLjAzMyA4OTQuMDY2IDcyMS42MTEgODk3 + LjI0NyA3MjQuNzY3IDg5OS42NDFDNzI3LjkyMiA5MDIuMDM0IDczMi4yNjIgOTAzLjIzMSA3Mzcu + Nzg0IDkwMy4yMzFDNzQzLjExOCA5MDMuMjMxIDc0Ny45MDcgOTAxLjk5MyA3NTIuMTUxIDg5OS41 + MThDNzU2LjM5NSA4OTcuMDQzIDc1OS40NTYgODkzLjMxOCA3NjEuMzMzIDg4OC4zNDJMNzY2LjEz + IDkwMy4wOTFDNzYzLjg3NSA5MTAuMDYgNzU5LjY5MyA5MTUuMzc2IDc1My41ODEgOTE5LjAzOUM3 + NDcuNDcgOTIyLjcwMiA3MzkuNTk2IDkyNC41MzMgNzI5Ljk1OCA5MjQuNTMzWiIgZmlsbD0iIzk5 + OTk5OSIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIxIiBzdHJva2U9Im5vbmUiLz4KPHBh + dGggZD0iTTg4OC4yMDcgODExLjkxNEM4OTYuOTIyIDgxMS45MTQgOTA0LjY5OSA4MTMuNjI0IDkx + MS41NCA4MTcuMDQ0QzkxOC4zODEgODIwLjQ2NCA5MjMuNzgzIDgyNS43MzcgOTI3Ljc0NiA4MzIu + ODYyQzkzMS43MDkgODM5Ljk4NyA5MzMuNjkgODQ5LjE0IDkzMy42OSA4NjAuMzIxTDkzMy42OSA5 + MjIuOTg3TDkwMS45MjggOTIyLjk4N0w5MDEuOTI4IDg2NS4wNjhDOTAxLjkyOCA4NTYuMjYyIDg5 + OS45OSA4NDkuNzY1IDg5Ni4xMTMgODQ1LjU3OEM4OTIuMjM2IDg0MS4zOSA4ODYuODAxIDgzOS4y + OTYgODc5LjgwOCA4MzkuMjk2Qzg3NC43MiA4MzkuMjk2IDg3MC4xNzcgODQwLjM1NiA4NjYuMTgg + ODQyLjQ3N0M4NjIuMTgyIDg0NC41OTggODU5LjA3NSA4NDcuODIgODU2Ljg1OCA4NTIuMTQyQzg1 + NC42NDIgODU2LjQ2NSA4NTMuNTM0IDg2Mi4wMjMgODUzLjUzNCA4NjguODE5TDg1My41MzQgOTIy + Ljk4N0w4MjEuNzcyIDkyMi45ODdMODIxLjc3MiA4MTMuNDU3TDg1Mi4wMzIgODEzLjQ1N0w4NTIu + MDMyIDg0My44MjNMODQ2LjM0NyA4MzQuNzAyQzg1MC4yODEgODI3LjI3MSA4NTUuOTA3IDgyMS42 + MTUgODYzLjIyMyA4MTcuNzM1Qzg3MC41MzkgODEzLjg1NSA4NzguODY3IDgxMS45MTQgODg4LjIw + NyA4MTEuOTE0WiIgZmlsbD0iIzk5OTk5OSIgZmlsbC1ydWxlPSJub256ZXJvIiBvcGFjaXR5PSIx + IiBzdHJva2U9Im5vbmUiLz4KPC9nPgo8L2c+Cjwvc3ZnPgo= mediatype: image/svg+xml install: spec: diff --git a/bpfman-operator/config/openshift/manager_auth_proxy_patch.yaml b/bpfman-operator/config/openshift/manager_auth_proxy_patch.yaml index 61acfd03f..db945cf75 100644 --- a/bpfman-operator/config/openshift/manager_auth_proxy_patch.yaml +++ b/bpfman-operator/config/openshift/manager_auth_proxy_patch.yaml @@ -30,13 +30,13 @@ spec: allowPrivilegeEscalation: false capabilities: drop: - - "ALL" + - ALL image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0 args: - - "--secure-listen-address=0.0.0.0:8443" - - "--upstream=http://127.0.0.1:8080/" - - "--logtostderr=true" - - "--v=0" + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8174/ + - --logtostderr=true + - --v=0 ports: - containerPort: 8443 protocol: TCP @@ -50,6 +50,6 @@ spec: memory: 64Mi - name: bpfman-operator args: - - "--health-probe-bind-address=:8081" - - "--metrics-bind-address=127.0.0.1:8080" - - "--leader-elect" + - --health-probe-bind-address=:8175 + - --metrics-bind-address=127.0.0.1:8174 + - --leader-elect diff --git a/bpfman-operator/config/openshift/patch.yaml b/bpfman-operator/config/openshift/patch.yaml index 61b8be2ab..cda171a2d 100644 --- a/bpfman-operator/config/openshift/patch.yaml +++ b/bpfman-operator/config/openshift/patch.yaml @@ -8,8 +8,8 @@ metadata: pod-security.kubernetes.io/warn: privileged annotations: openshift.io/node-selector: "" - openshift.io/description: "Openshift bpfman components" - workload.openshift.io/allowed: "management" + openshift.io/description: Openshift bpfman components + workload.openshift.io/allowed: management name: system --- apiVersion: v1 @@ -19,5 +19,5 @@ metadata: namespace: kube-system data: ## Can be configured at runtime - bpfman.log.level: "info" - bpfman.agent.log.level: "info" + bpfman.log.level: info + bpfman.agent.log.level: info diff --git a/bpfman-operator/config/rbac/bpfman-agent/role.yaml b/bpfman-operator/config/rbac/bpfman-agent/role.yaml index db7f89e55..0ca66a515 100644 --- a/bpfman-operator/config/rbac/bpfman-agent/role.yaml +++ b/bpfman-operator/config/rbac/bpfman-agent/role.yaml @@ -31,6 +31,34 @@ rules: - get - patch - update +- apiGroups: + - bpfman.io + resources: + - fentryprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - fentryprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - fexitprograms + verbs: + - get + - list + - watch +- apiGroups: + - bpfman.io + resources: + - fexityprograms/finalizers + verbs: + - update - apiGroups: - bpfman.io resources: @@ -109,6 +137,14 @@ rules: - get - list - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch - apiGroups: - "" resources: diff --git a/bpfman-operator/config/rbac/bpfman-operator/role.yaml b/bpfman-operator/config/rbac/bpfman-operator/role.yaml index 270feceeb..790bf6e0c 100644 --- a/bpfman-operator/config/rbac/bpfman-operator/role.yaml +++ b/bpfman-operator/config/rbac/bpfman-operator/role.yaml @@ -31,6 +31,58 @@ rules: - configmaps/finalizers verbs: - update +- apiGroups: + - bpfman.io + resources: + - fentryprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - fentryprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - fentryprograms/status + verbs: + - get + - patch + - update +- apiGroups: + - bpfman.io + resources: + - fexitprograms + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - bpfman.io + resources: + - fexitprograms/finalizers + verbs: + - update +- apiGroups: + - bpfman.io + resources: + - fexitprograms/status + verbs: + - get + - patch + - update - apiGroups: - bpfman.io resources: diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_fentry_fentryprogram.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_fentry_fentryprogram.yaml new file mode 100644 index 000000000..48f8231a4 --- /dev/null +++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_fentry_fentryprogram.yaml @@ -0,0 +1,14 @@ +apiVersion: bpfman.io/v1alpha1 +kind: FentryProgram +metadata: + labels: + app.kubernetes.io/name: fentryprogram + name: fentry-example +spec: + bpffunctionname: test_fentry + # Select all nodes + nodeselector: {} + func_name: do_unlinkat + bytecode: + image: + url: quay.io/bpfman-bytecode/fentry:latest diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_fexit_fexitprogram.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_fexit_fexitprogram.yaml new file mode 100644 index 000000000..2e7be6a88 --- /dev/null +++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_fexit_fexitprogram.yaml @@ -0,0 +1,14 @@ +apiVersion: bpfman.io/v1alpha1 +kind: FexitProgram +metadata: + labels: + app.kubernetes.io/name: fexitprogram + name: fexit-example +spec: + bpffunctionname: test_fexit + # Select all nodes + nodeselector: {} + func_name: do_unlinkat + bytecode: + image: + url: quay.io/bpfman-bytecode/fexit:latest diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml index 3c605b8b4..7bd978925 100644 --- a/bpfman-operator/config/samples/bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml +++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml @@ -8,8 +8,7 @@ spec: bpffunctionname: my_kprobe # Select all nodes nodeselector: {} - func_names: - - try_to_wake_up + func_name: try_to_wake_up offset: 0 retprobe: false bytecode: diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml index 7e1ad1b43..d4f113519 100644 --- a/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml +++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml @@ -5,13 +5,12 @@ metadata: app.kubernetes.io/name: uprobeprogram name: uprobe-example spec: - bpffunctionname: my_uprobe # Select all nodes nodeselector: {} + bpffunctionname: my_uprobe func_name: syscall # offset: 0 # optional offset w/in function - target: - - /bpfman + target: bpfman retprobe: false # pid: 0 # optional pid to execute uprobe for bytecode: diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers.yaml new file mode 100644 index 000000000..3e43ef67d --- /dev/null +++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers.yaml @@ -0,0 +1,34 @@ +apiVersion: bpfman.io/v1alpha1 +kind: UprobeProgram +metadata: + labels: + app.kubernetes.io/name: uprobeprogram + name: uprobe-example-containers +spec: + # Select all nodes + nodeselector: {} + bpffunctionname: my_uprobe + func_name: malloc + # offset: 0 # optional offset w/in function + target: libc + retprobe: false + # pid: 0 # optional pid to execute uprobe for + bytecode: + image: + url: quay.io/bpfman-bytecode/uprobe:latest + globaldata: + GLOBAL_u8: + - 0x01 + GLOBAL_u32: + - 0x0D + - 0x0C + - 0x0B + - 0x0A + containers: + namespace: bpfman + pods: + matchLabels: + name: bpfman-daemon + containernames: + - bpfman + - bpfman-agent diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers_all.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers_all.yaml new file mode 100644 index 000000000..70e83df8c --- /dev/null +++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers_all.yaml @@ -0,0 +1,31 @@ +apiVersion: bpfman.io/v1alpha1 +kind: UprobeProgram +metadata: + labels: + app.kubernetes.io/name: uprobeprogram + name: uprobe-example-containers-all +spec: + bpffunctionname: my_uprobe + # Select all nodes + nodeselector: {} + func_name: malloc + # offset: 0 # optional offset w/in function + target: libc + retprobe: false + # pid: 0 # optional pid to execute uprobe for + bytecode: + image: + url: quay.io/bpfman-bytecode/uprobe:latest + globaldata: + GLOBAL_u8: + - 0x01 + GLOBAL_u32: + - 0x0D + - 0x0C + - 0x0B + - 0x0A + # This is only a test. Don't ever do this in production. It will try to + # install the uprobe in every container on every node and probably crash the + # cluster. + containers: + pods: {} diff --git a/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_nginx.yaml b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_nginx.yaml new file mode 100644 index 000000000..0f4d61d39 --- /dev/null +++ b/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_nginx.yaml @@ -0,0 +1,33 @@ +# CRD can be used with the nginx-deployment.yaml in this directory +apiVersion: bpfman.io/v1alpha1 +kind: UprobeProgram +metadata: + labels: + app.kubernetes.io/name: uprobeprogram + name: uprobe-example-nginx +spec: + # Select all nodes + nodeselector: {} + bpffunctionname: my_uprobe + func_name: malloc + # offset: 0 # optional offset w/in function + target: libc + retprobe: false + # pid: 0 # optional pid to execute uprobe for + bytecode: + image: + url: quay.io/bpfman-bytecode/uprobe:latest + globaldata: + GLOBAL_u8: + - 0x01 + GLOBAL_u32: + - 0x0D + - 0x0C + - 0x0B + - 0x0A + containers: + namespace: default + pods: + matchLabels: {} + containernames: + - nginx diff --git a/bpfman-operator/config/samples/kustomization.yaml b/bpfman-operator/config/samples/kustomization.yaml index 9748e1a05..e6f2d9428 100644 --- a/bpfman-operator/config/samples/kustomization.yaml +++ b/bpfman-operator/config/samples/kustomization.yaml @@ -5,4 +5,6 @@ resources: - bpfman.io_v1alpha1_tc_pass_tcprogram.yaml - bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml - bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml + - bpfman.io_v1alpha1_fentry_fentryprogram.yaml + - bpfman.io_v1alpha1_fexit_fexitprogram.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/bpfman-operator/config/test/manager_auth_proxy_patch.yaml b/bpfman-operator/config/test/manager_auth_proxy_patch.yaml index 61acfd03f..db945cf75 100644 --- a/bpfman-operator/config/test/manager_auth_proxy_patch.yaml +++ b/bpfman-operator/config/test/manager_auth_proxy_patch.yaml @@ -30,13 +30,13 @@ spec: allowPrivilegeEscalation: false capabilities: drop: - - "ALL" + - ALL image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0 args: - - "--secure-listen-address=0.0.0.0:8443" - - "--upstream=http://127.0.0.1:8080/" - - "--logtostderr=true" - - "--v=0" + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8174/ + - --logtostderr=true + - --v=0 ports: - containerPort: 8443 protocol: TCP @@ -50,6 +50,6 @@ spec: memory: 64Mi - name: bpfman-operator args: - - "--health-probe-bind-address=:8081" - - "--metrics-bind-address=127.0.0.1:8080" - - "--leader-elect" + - --health-probe-bind-address=:8175 + - --metrics-bind-address=127.0.0.1:8174 + - --leader-elect diff --git a/bpfman-operator/config/test/patch.yaml b/bpfman-operator/config/test/patch.yaml index a63067e24..2d06f90a5 100644 --- a/bpfman-operator/config/test/patch.yaml +++ b/bpfman-operator/config/test/patch.yaml @@ -6,4 +6,5 @@ metadata: data: bpfman.agent.image: quay.io/bpfman/bpfman-agent:int-test bpfman.image: quay.io/bpfman/bpfman:int-test - bpfman.log.level: debug + bpfman.log.level: bpfman=debug + bpfman.agent.log.level: debug diff --git a/bpfman-operator/controllers/bpfman-agent/common.go b/bpfman-operator/controllers/bpfman-agent/common.go index 14636e3e3..a26957ef2 100644 --- a/bpfman-operator/controllers/bpfman-agent/common.go +++ b/bpfman-operator/controllers/bpfman-agent/common.go @@ -28,6 +28,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -50,6 +52,9 @@ import ( //+kubebuilder:rbac:groups=bpfman.io,resources=tracepointprograms/finalizers,verbs=update //+kubebuilder:rbac:groups=bpfman.io,resources=kprobeprograms/finalizers,verbs=update //+kubebuilder:rbac:groups=bpfman.io,resources=uprobeprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=bpfman.io,resources=fexityprograms/finalizers,verbs=update +//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch //+kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch //+kubebuilder:rbac:groups=core,resources=secrets,verbs=get @@ -102,6 +107,29 @@ func nodePredicate(nodeName string) predicate.Funcs { } } +// Predicate to watch for Pod events on a specific node which checks if the +// event's Pod is scheduled on the given node. +func podOnNodePredicate(nodeName string) predicate.Funcs { + return predicate.Funcs{ + GenericFunc: func(e event.GenericEvent) bool { + pod, ok := e.Object.(*v1.Pod) + return ok && pod.Spec.NodeName == nodeName + }, + CreateFunc: func(e event.CreateEvent) bool { + pod, ok := e.Object.(*v1.Pod) + return ok && pod.Spec.NodeName == nodeName && pod.Status.Phase == v1.PodRunning + }, + UpdateFunc: func(e event.UpdateEvent) bool { + pod, ok := e.ObjectNew.(*v1.Pod) + return ok && pod.Spec.NodeName == nodeName && pod.Status.Phase == v1.PodRunning + }, + DeleteFunc: func(e event.DeleteEvent) bool { + pod, ok := e.Object.(*v1.Pod) + return ok && pod.Spec.NodeName == nodeName + }, + } +} + func isNodeSelected(selector *metav1.LabelSelector, nodeLabels map[string]string) (bool, error) { // Logic to check if this node is selected by the *Program object selectorTool, err := metav1.LabelSelectorAsSelector(selector) @@ -248,6 +276,172 @@ func (r *ReconcilerCommon) createBpfProgram(ctx context.Context, return bpfProg, nil } +// Programs may be deleted for one of two reasons. The first is that the global +// *Program object is being deleted. The second is that the something has +// changed on the node that is causing the need to remove individual +// bpfPrograms. Typically this happens when containers that used to match a +// container selector are deleted and the eBPF programs that were installed in +// them need to be removed. This function handles both of these cases. +// +// For the first case, deletion of a *Program takes a few steps if there are +// existing bpfPrograms: +// 1. Reconcile the bpfProgram (take bpfman cleanup steps). +// 2. Remove any finalizers from the bpfProgram Object. +// 3. Update the condition on the bpfProgram to BpfProgCondUnloaded so the +// operator knows it's safe to remove the parent Program Object, which +// is when the bpfProgram is automatically deleted by the owner-reference. +// +// For the second case, we need to do the first 2 steps, and then explicitly +// delete the bpfPrograms that are no longer needed. +func handleProgDelete( + ctx context.Context, + r *ReconcilerCommon, + common *bpfmaniov1alpha1.BpfProgramCommon, + rec bpfmanReconciler, + existingPrograms map[string]bpfmaniov1alpha1.BpfProgram, + programMap map[string]*gobpfman.ListResponse_ListResult, + isNodeSelected bool, + isBeingDeleted bool, + mapOwnerStatus *MapOwnerParamStatus, +) (internal.ReconcileResult, error) { + for _, prog := range existingPrograms { + // Reconcile the bpfProgram if error write condition and exit with + // retry. + cond, err := rec.reconcileBpfmanProgram(ctx, + programMap, + &common.ByteCode, + &prog, + isNodeSelected, + true, // delete program + mapOwnerStatus, + ) + if err != nil { + r.updateStatus(ctx, &prog, cond) + return internal.Requeue, fmt.Errorf("failed to delete bpfman program: %v", err) + } + + if r.removeFinalizer(ctx, &prog, rec.getFinalizer()) { + return internal.Updated, nil + } + + if isBeingDeleted { + // We're deleting these programs because the *Program is being + // deleted, so update the status and the program will be deleted + // when the owner is deleted. + if r.updateStatus(ctx, &prog, cond) { + return internal.Updated, nil + } + } else { + // We're deleting these programs because they were not expected due + // to changes that caused the containers to not be selected anymore. + // So, explicitly delete them. + opts := client.DeleteOptions{} + r.Logger.Info("Deleting bpfProgram", "Name", prog.Name, "Owner", prog.GetName()) + if err := r.Delete(ctx, &prog, &opts); err != nil { + return internal.Requeue, fmt.Errorf("failed to create bpfProgram object: %v", err) + } + return internal.Updated, nil + } + } + + // We're done reconciling. + r.Logger.Info("Nothing to do for this program") + return internal.Unchanged, nil +} + +// handleProgCreateOrUpdate compares the expected bpfPrograms to the existing +// bpfPrograms. If a bpfProgram is expected but doesn't exist, it is created. +// If an expected bpfProgram exists, it is reconciled. If a bpfProgram exists +// but is not expected, it is deleted. +func handleProgCreateOrUpdate( + ctx context.Context, + r *ReconcilerCommon, + common *bpfmaniov1alpha1.BpfProgramCommon, + rec bpfmanReconciler, + program client.Object, + existingPrograms map[string]bpfmaniov1alpha1.BpfProgram, + expectedPrograms *bpfmaniov1alpha1.BpfProgramList, + programMap map[string]*gobpfman.ListResponse_ListResult, + isNodeSelected bool, + isBeingDeleted bool, + mapOwnerStatus *MapOwnerParamStatus, +) (internal.ReconcileResult, error) { + // If the *Program isn't being deleted ALWAYS create the bpfPrograms + // even if the node isn't selected + for _, expectedProg := range expectedPrograms.Items { + prog, exists := existingPrograms[expectedProg.Name] + if exists { + // Remove the bpfProgram from the existingPrograms map so we know + // not to delete it below. + delete(existingPrograms, expectedProg.Name) + } else { + // Create a new bpfProgram Object for this program. + opts := client.CreateOptions{} + r.Logger.Info("Creating bpfProgram", "Name", expectedProg.Name, "Owner", program.GetName()) + if err := r.Create(ctx, &expectedProg, &opts); err != nil { + return internal.Requeue, fmt.Errorf("failed to create bpfProgram object: %v", err) + } + existingPrograms[expectedProg.Name] = prog + return internal.Updated, nil + } + + // bpfProgram Object exists go ahead and reconcile it, if there is + // an error write condition and exit with retry. + cond, err := rec.reconcileBpfmanProgram(ctx, + programMap, + &common.ByteCode, + &prog, + isNodeSelected, + isBeingDeleted, + mapOwnerStatus, + ) + if err != nil { + if r.updateStatus(ctx, &prog, cond) { + // Return an error the first time. + return internal.Updated, fmt.Errorf("failed to reconcile bpfman program: %v", err) + } + } else { + // Make sure if we're not selected exit and write correct condition + if cond == bpfmaniov1alpha1.BpfProgCondNotSelected || + cond == bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound || + cond == bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded || + cond == bpfmaniov1alpha1.BpfProgCondNoContainersOnNode { + // Write NodeNodeSelected status + if r.updateStatus(ctx, &prog, cond) { + r.Logger.V(1).Info("Update condition from bpfman reconcile", "condition", cond) + return internal.Updated, nil + } else { + continue + } + } + + existingId, err := bpfmanagentinternal.GetID(&prog) + if err != nil { + return internal.Requeue, fmt.Errorf("failed to get kernel id from bpfProgram: %v", err) + } + + // If bpfProgram Maps OR the program ID annotation isn't up to date just update it and return + if !reflect.DeepEqual(existingId, r.progId) { + r.Logger.Info("Updating bpfProgram Object", "Id", r.progId, "bpfProgram", prog.Name) + // annotations should be populate on create + prog.Annotations[internal.IdAnnotation] = strconv.FormatUint(uint64(*r.progId), 10) + if err := r.Update(ctx, &prog, &client.UpdateOptions{}); err != nil { + return internal.Requeue, fmt.Errorf("failed to update bpfProgram's Programs: %v", err) + } + return internal.Updated, nil + } + + if r.updateStatus(ctx, &prog, cond) { + return internal.Updated, nil + } + } + } + + // We're done reconciling the expected programs. If any unexpected programs + // exist, delete them and return the result. + return handleProgDelete(ctx, r, common, rec, existingPrograms, programMap, isNodeSelected, isBeingDeleted, mapOwnerStatus) +} + // reconcileProgram is called by ALL *Program controllers, and contains much of // the core logic for taking *Program objects, turning them into bpfProgram // object(s), and ultimately telling the custom controller types to load real @@ -282,14 +476,6 @@ func reconcileProgram(ctx context.Context, return internal.Requeue, fmt.Errorf("failed to get existing bpfPrograms: %v", err) } - // Generate the list of BpfPrograms for this *Program. This handles the one - // *Program to many BpfPrograms (i.e. One *Program maps to multiple - // interfaces because of PodSelector) - expectedPrograms, err := rec.expectedBpfPrograms(ctx) - if err != nil { - return internal.Requeue, fmt.Errorf("failed to get expected bpfPrograms: %v", err) - } - // Determine if the MapOwnerSelector was set, and if so, see if the MapOwner // ID can be found. mapOwnerStatus, err := ProcessMapOwnerParam(ctx, &common.MapOwnerSelector, r) @@ -302,106 +488,24 @@ func reconcileProgram(ctx context.Context, "isLoaded", mapOwnerStatus.isLoaded, "mapOwnerid", mapOwnerStatus.mapOwnerId) - // multiplex signals into kubernetes API actions switch isBeingDeleted { - // Deletion of a *Program takes a few steps if there's existing bpfPrograms: - // 1. Reconcile the bpfProgram (take bpfman cleanup steps). - // 2. Remove any finalizers from the bpfProgram Object. - // 3. Update the condition on the bpfProgram to BpfProgCondUnloaded so the - // operator knows it's safe to remove the parent Program Object, which - // is when the bpfProgram is automatically deleted by the owner-reference. case true: - for _, prog := range existingPrograms { - // Reconcile the bpfProgram if error write condition and exit with - // retry. - cond, err := rec.reconcileBpfmanProgram(ctx, - programMap, - &common.ByteCode, - &prog, - isNodeSelected, - isBeingDeleted, - mapOwnerStatus, - ) - if err != nil { - r.updateStatus(ctx, &prog, cond) - return internal.Requeue, fmt.Errorf("failed to delete bpfman program: %v", err) - } - - if r.removeFinalizer(ctx, &prog, rec.getFinalizer()) { - return internal.Updated, nil - } - - if r.updateStatus(ctx, &prog, cond) { - return internal.Updated, nil - } - } - // If the *Program isn't being deleted ALWAYS create the bpfPrograms - // even if the node isn't selected + return handleProgDelete(ctx, r, common, rec, existingPrograms, + programMap, isNodeSelected, isBeingDeleted, mapOwnerStatus) case false: - for _, expectedProg := range expectedPrograms.Items { - prog, exists := existingPrograms[expectedProg.Name] - if !exists { - opts := client.CreateOptions{} - r.Logger.Info("Creating bpfProgram", "Name", expectedProg.Name, "Owner", program.GetName()) - if err := r.Create(ctx, &expectedProg, &opts); err != nil { - return internal.Requeue, fmt.Errorf("failed to create bpfProgram object: %v", err) - } - existingPrograms[expectedProg.Name] = prog - return internal.Updated, nil - } - - // bpfProgram Object exists go ahead and reconcile it, if there is - // an error write condition and exit with retry. - cond, err := rec.reconcileBpfmanProgram(ctx, - programMap, - &common.ByteCode, - &prog, - isNodeSelected, - isBeingDeleted, - mapOwnerStatus, - ) - if err != nil { - if r.updateStatus(ctx, &prog, cond) { - // Return an error the first time. - return internal.Updated, fmt.Errorf("failed to reconcile bpfman program: %v", err) - } - } else { - // Make sure if we're not selected exit and write correct condition - if cond == bpfmaniov1alpha1.BpfProgCondNotSelected || - cond == bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound || - cond == bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded { - // Write NodeNodeSelected status - if r.updateStatus(ctx, &prog, cond) { - r.Logger.V(1).Info("Update condition from bpfman reconcile", "condition", cond) - return internal.Updated, nil - } - } - - existingId, err := bpfmanagentinternal.GetID(&prog) - if err != nil { - return internal.Requeue, fmt.Errorf("failed to get kernel id from bpfProgram: %v", err) - } - - // If bpfProgram Maps OR the program ID annotation isn't up to date just update it and return - if !reflect.DeepEqual(existingId, r.progId) { - r.Logger.Info("Updating bpfProgram Object", "Id", r.progId, "bpfProgram", prog.Name) - // annotations should be populate on create - prog.Annotations[internal.IdAnnotation] = strconv.FormatUint(uint64(*r.progId), 10) - if err := r.Update(ctx, &prog, &client.UpdateOptions{}); err != nil { - return internal.Requeue, fmt.Errorf("failed to update bpfProgram's Programs: %v", err) - } - return internal.Updated, nil - } - - if r.updateStatus(ctx, &prog, cond) { - return internal.Updated, nil - } - } + // Generate the list of BpfPrograms for this *Program. This handles the + // one *Program to many BpfPrograms (e.g., One *Program maps to multiple + // interfaces because of PodSelector, or one *Program needs to be + // installed in multiple containers because of ContainerSelector). + expectedPrograms, err := rec.expectedBpfPrograms(ctx) + if err != nil { + return internal.Requeue, fmt.Errorf("failed to get expected bpfPrograms: %v", err) } + return handleProgCreateOrUpdate(ctx, r, common, rec, program, existingPrograms, + expectedPrograms, programMap, isNodeSelected, isBeingDeleted, mapOwnerStatus) } - // We didn't already return something else, so there's nothing to do - r.Logger.Info("Nothing to do for this program") + // This return should never be reached, but it's here to satisfy the compiler. return internal.Unchanged, nil } @@ -485,3 +589,20 @@ func ProcessMapOwnerParam(ctx context.Context, } } } + +// get Clientset returns a kubernetes clientset. +func getClientset() (*kubernetes.Clientset, error) { + + // get the in-cluster config + config, err := rest.InClusterConfig() + if err != nil { + return nil, fmt.Errorf("error getting config: %v", err) + } + // create the clientset + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("error creating clientset: %v", err) + } + + return clientset, nil +} diff --git a/bpfman-operator/controllers/bpfman-agent/containers.go b/bpfman-operator/controllers/bpfman-agent/containers.go new file mode 100644 index 000000000..fe7ca0167 --- /dev/null +++ b/bpfman-operator/controllers/bpfman-agent/containers.go @@ -0,0 +1,196 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +package bpfmanagent + +import ( + "context" + "fmt" + "os/exec" + "slices" + "strconv" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman/bpfman-operator/internal" + "github.com/buger/jsonparser" + "github.com/go-logr/logr" +) + +// getPodsForNode returns a list of pods on the given node that match the given +// container selector. +func getPodsForNode(ctx context.Context, clientset kubernetes.Interface, + containerSelector *bpfmaniov1alpha1.ContainerSelector, nodeName string) (*v1.PodList, error) { + + selectorString := metav1.FormatLabelSelector(&containerSelector.Pods) + + if selectorString == "" { + return nil, fmt.Errorf("error parsing selector: %v", selectorString) + } + + listOptions := metav1.ListOptions{ + FieldSelector: "spec.nodeName=" + nodeName, + } + + if selectorString != "" { + listOptions.LabelSelector = selectorString + } + + podList, err := clientset.CoreV1().Pods(containerSelector.Namespace).List(ctx, listOptions) + if err != nil { + return nil, fmt.Errorf("error getting pod list: %v", err) + } + + return podList, nil +} + +type containerInfo struct { + podName string + containerName string + pid int64 +} + +// getContainerInfo returns a list of containerInfo for the given pod list and container names. +func getContainerInfo(podList *v1.PodList, containerNames *[]string, logger logr.Logger) (*[]containerInfo, error) { + + crictl := "/usr/local/bin/crictl" + + containers := []containerInfo{} + + for i, pod := range podList.Items { + logger.V(1).Info("Pod", "index", i, "Name", pod.Name, "Namespace", pod.Namespace, "NodeName", pod.Spec.NodeName) + + // Find the unique Pod ID of the given pod. + cmd := exec.Command(crictl, "pods", "--name", pod.Name, "-o", "json") + podInfo, err := cmd.Output() + if err != nil { + logger.Info("Failed to get pod info", "error", err) + return nil, err + } + + // The crictl --name option works like a grep on the names of pods. + // Since we are using the unique name of the pod generated by k8s, we + // will most likely only get one pod. Though very unlikely, it is + // technically possible that this unique name is a substring of another + // pod name. If that happens, we would get multiple pods, so we handle + // that possibility with the following for loop. + var podId string + podFound := false + for podIndex := 0; ; podIndex++ { + indexString := "[" + strconv.Itoa(podIndex) + "]" + podId, err = jsonparser.GetString(podInfo, "items", indexString, "id") + if err != nil { + // We hit the end of the list of pods and didn't find it. This + // should only happen if the pod was deleted between the time we + // got the list of pods and the time we got the info about the + // pod. + break + } + podName, err := jsonparser.GetString(podInfo, "items", indexString, "metadata", "name") + if err != nil { + // We shouldn't get an error here if we didn't get an error + // above, but just in case... + logger.Error(err, "Error getting pod name") + break + } + + if podName == pod.Name { + podFound = true + break + } + } + + if !podFound { + return nil, fmt.Errorf("pod %s not found in crictl pod list", pod.Name) + } + + logger.V(1).Info("podFound", "podId", podId, "err", err) + + // Get info about the containers in the pod so we can get their unique IDs. + cmd = exec.Command(crictl, "ps", "--pod", podId, "-o", "json") + containerData, err := cmd.Output() + if err != nil { + logger.Info("Failed to get container info", "error", err) + return nil, err + } + + // For each container in the pod... + for containerIndex := 0; ; containerIndex++ { + + indexString := "[" + strconv.Itoa(containerIndex) + "]" + + // Make sure the container name is in the list of containers we want. + containerName, err := jsonparser.GetString(containerData, "containers", indexString, "metadata", "name") + if err != nil { + break + } + + if containerNames != nil && + len(*containerNames) > 0 && + !slices.Contains((*containerNames), containerName) { + continue + } + + // If it is in the list, get the container ID. + containerId, err := jsonparser.GetString(containerData, "containers", indexString, "id") + if err != nil { + break + } + + // Now use the container ID to get more info about the container so + // we can get the PID. + cmd = exec.Command(crictl, "inspect", "-o", "json", containerId) + containerData, err := cmd.Output() + if err != nil { + logger.Info("Failed to get container data", "error", err) + continue + } + containerPid, err := jsonparser.GetInt(containerData, "info", "pid") + if err != nil { + logger.Info("Failed to get container PID", "error", err) + continue + } + + container := containerInfo{ + podName: pod.Name, + containerName: containerName, + pid: containerPid, + } + + containers = append(containers, container) + } + + } + + return &containers, nil +} + +// Check if the annotation is set to indicate that no containers on this node +// matched the container selector. +func noContainersOnNode(bpfProgram *bpfmaniov1alpha1.BpfProgram) bool { + if bpfProgram == nil { + return false + } + + noContainersOnNode, ok := bpfProgram.Annotations[internal.UprobeNoContainersOnNode] + if ok && noContainersOnNode == "true" { + return true + } + + return false +} diff --git a/bpfman-operator/controllers/bpfman-agent/discovered_program.go b/bpfman-operator/controllers/bpfman-agent/discovered_program.go deleted file mode 100644 index 8f4383cb9..000000000 --- a/bpfman-operator/controllers/bpfman-agent/discovered_program.go +++ /dev/null @@ -1,179 +0,0 @@ -/* -Copyright 2023. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package bpfmanagent - -import ( - "context" - "fmt" - "reflect" - "strings" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/source" - - bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal" - "github.com/bpfman/bpfman/bpfman-operator/internal" - v1 "k8s.io/api/core/v1" -) - -const ( - syncDurationDiscoveredController = 30 * time.Second -) - -type DiscoveredProgramReconciler struct { - ReconcilerCommon -} - -// SetupWithManager sets up the controller with the Manager. -func (r *DiscoveredProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - Named("DiscoveredProgramController"). - // Only trigger reconciliation if node object changed. Additionally always - // exit the end of a reconcile with a requeue so that the discovered programs - // are kept up to date. - Watches( - &source.Kind{Type: &v1.Node{}}, - &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And(predicate.ResourceVersionChangedPredicate{}, nodePredicate(r.NodeName))), - ). - Watches( - &source.Kind{Type: &bpfmaniov1alpha1.BpfProgram{}}, - &handler.EnqueueRequestForObject{}, - builder.WithPredicates(predicate.And( - internal.BpfProgramNodePredicate(r.NodeName)), - internal.DiscoveredBpfProgramPredicate(), - ), - ). - Complete(r) -} - -// Reconcile ALL discovered bpf programs on the system whenever an event is received. -func (r *DiscoveredProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - r.Logger = ctrl.Log.WithName("discvr") - - // The Discovery Reconciler gets called more than others so this tends to be a noisy log, - // so moved to a Trace log. - ctxLogger := log.FromContext(ctx) - ctxLogger.V(2).Info("Reconcile DiscoveredProgs: Enter", "ReconcileKey", req) - - // Get existing ebpf state from bpfman. - programs, err := bpfmanagentinternal.ListAllPrograms(ctx, r.BpfmanClient) - if err != nil { - r.Logger.Error(err, "failed to list loaded bpf programs") - return ctrl.Result{Requeue: true, RequeueAfter: retryDurationAgent}, nil - } - - // get all existing "discovered" bpfProgram Objects for this node - opts := []client.ListOption{ - client.MatchingLabels{internal.DiscoveredLabel: "", internal.K8sHostLabel: r.NodeName}, - } - - existingPrograms := bpfmaniov1alpha1.BpfProgramList{} - err = r.Client.List(ctx, &existingPrograms, opts...) - if err != nil { - r.Logger.Error(err, "failed to list existing discovered bpfProgram objects") - } - - // build an indexable map of existing programs based on bpfProgram name - existingProgramIndex := map[string]bpfmaniov1alpha1.BpfProgram{} - for _, p := range existingPrograms.Items { - existingProgramIndex[p.Name] = p - } - - for _, p := range programs { - kernelInfo := p.GetKernelInfo() - if kernelInfo == nil { - continue - } - - programInfo := p.GetInfo() - if programInfo != nil { - // skip bpf programs loaded by bpfman, their corresponding bpfProgram object - // will be managed by another controller. - metadata := programInfo.GetMetadata() - if _, ok := metadata[internal.UuidMetadataKey]; ok { - continue - } - } - - // TODO(astoycos) across the agent we need a better way to validate that - // the object name we're building is a valid k8's object name i.e meets the - // regex: '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*' - // laid out here -> https://github.com/kubernetes/apimachinery/blob/v0.27.4/pkg/util/validation/validation.go#L43C6-L43C21 - bpfProgName := "" - if len(kernelInfo.Name) == 0 { - bpfProgName = fmt.Sprintf("%d-%s", kernelInfo.Id, r.NodeName) - } else { - bpfProgName = fmt.Sprintf("%s-%d-%s", strings.ReplaceAll(kernelInfo.Name, "_", "-"), kernelInfo.Id, r.NodeName) - } - - expectedBpfProg := &bpfmaniov1alpha1.BpfProgram{ - ObjectMeta: metav1.ObjectMeta{ - Name: bpfProgName, - Labels: map[string]string{internal.DiscoveredLabel: "", - internal.K8sHostLabel: r.NodeName}, - Annotations: bpfmanagentinternal.Build_kernel_info_annotations(p), - }, - Spec: bpfmaniov1alpha1.BpfProgramSpec{ - Type: internal.ProgramType(kernelInfo.ProgramType).String(), - }, - Status: bpfmaniov1alpha1.BpfProgramStatus{Conditions: []metav1.Condition{}}, - } - - existingBpfProg, ok := existingProgramIndex[bpfProgName] - // If the bpfProgram object doesn't exist create it. - if !ok { - r.Logger.Info("Creating discovered bpfProgram object", "name", expectedBpfProg.Name) - err = r.Create(ctx, expectedBpfProg) - if err != nil { - r.Logger.Error(err, "failed to create bpfProgram object") - } - - return ctrl.Result{Requeue: false}, nil - } - - // If the bpfProgram object does exist but is stale update it. - if !reflect.DeepEqual(expectedBpfProg.Annotations, existingBpfProg.Annotations) { - if err := r.Update(ctx, expectedBpfProg); err != nil { - r.Logger.Error(err, "failed to update discovered bpfProgram object", "name", expectedBpfProg.Name) - } - - return ctrl.Result{Requeue: false}, nil - } - - delete(existingProgramIndex, bpfProgName) - } - - // Delete any stale discovered programs - for _, prog := range existingProgramIndex { - r.Logger.Info("Deleting stale discovered bpfProgram object", "name", prog.Name) - if err := r.Delete(ctx, &prog, &client.DeleteOptions{}); err != nil { - r.Logger.Error(err, "failed to delete stale discoverd bpfProgram object", "name", prog.Name) - } - - return ctrl.Result{Requeue: false}, nil - } - - // If we've finished reconciling everything, make sure to exit with a retry - // so that we resync on a 30 second interval. - return ctrl.Result{Requeue: true, RequeueAfter: syncDurationDiscoveredController}, nil -} diff --git a/bpfman-operator/controllers/bpfman-agent/discovered_program_test.go b/bpfman-operator/controllers/bpfman-agent/discovered_program_test.go deleted file mode 100644 index a0624fca0..000000000 --- a/bpfman-operator/controllers/bpfman-agent/discovered_program_test.go +++ /dev/null @@ -1,424 +0,0 @@ -/* -Copyright 2023. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package bpfmanagent - -import ( - "context" - "fmt" - "testing" - - bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" - bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal" - agenttestutils "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" - "github.com/bpfman/bpfman/bpfman-operator/internal" - testutils "github.com/bpfman/bpfman/bpfman-operator/internal/test-utils" - - gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -func TestDiscoveredProgramControllerCreate(t *testing.T) { - var ( - namespace = "bpfman" - fakeNode = testutils.NewNode("fake-control-plane") - ctx = context.TODO() - bpfProgName0 = fmt.Sprintf("%s-%d-%s", "dump-bpf-map", 693, fakeNode.Name) - bpfProgName1 = fmt.Sprintf("%s-%d-%s", "dump-bpf-prog", 694, fakeNode.Name) - bpfProgName2 = fmt.Sprintf("%d-%s", 695, fakeNode.Name) - bpfProg = &bpfmaniov1alpha1.BpfProgram{} - fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" - programs = map[int]*gobpfman.ListResponse_ListResult{ - 693: { - KernelInfo: &gobpfman.KernelProgramInfo{ - Name: "dump_bpf_map", - ProgramType: 26, - Id: 693, - LoadedAt: "2023-03-02T18:15:06+0000", - Tag: "749172daffada61f", - GplCompatible: true, - MapIds: []uint32{45}, - BtfId: 154, - BytesXlated: 264, - Jited: true, - BytesJited: 287, - BytesMemlock: 4096, - VerifiedInsns: 34, - }, - }, - 694: { - KernelInfo: &gobpfman.KernelProgramInfo{ - Name: "dump_bpf_prog", - ProgramType: 26, - Id: 694, - LoadedAt: "2023-03-02T18:15:06+0000", - Tag: "bc36dd738319ea32", - GplCompatible: true, - MapIds: []uint32{45}, - BtfId: 154, - BytesXlated: 528, - Jited: true, - BytesJited: 715, - BytesMemlock: 4096, - VerifiedInsns: 84, - }, - }, - // test program with no name - 695: { - KernelInfo: &gobpfman.KernelProgramInfo{ - ProgramType: 8, - Id: 695, - LoadedAt: "2023-07-20T19:11:11+0000", - Tag: "6deef7357e7b4530", - GplCompatible: true, - BytesXlated: 64, - Jited: true, - BytesJited: 55, - BytesMemlock: 4096, - VerifiedInsns: 8, - }, - }, - // skip program loaded by bpfman - 696: { - Info: &gobpfman.ProgramInfo{ - Metadata: map[string]string{internal.UuidMetadataKey: fakeUID}, - }, - KernelInfo: &gobpfman.KernelProgramInfo{ - ProgramType: 8, - Id: 696, - LoadedAt: "2023-07-20T19:11:11+0000", - Tag: "6deef7357e7b4530", - GplCompatible: true, - BytesXlated: 64, - Jited: true, - BytesJited: 55, - BytesMemlock: 4096, - VerifiedInsns: 8, - }, - }, - } - ) - - // Objects to track in the fake client. - objs := []runtime.Object{fakeNode} - - // Register operator types with the runtime scheme. - s := scheme.Scheme - s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{}) - s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{}) - - // Create a fake client to mock API calls. - cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() - - cli := agenttestutils.NewBpfmanClientFakeWithPrograms(programs) - - rc := ReconcilerCommon{ - Client: cl, - Scheme: s, - BpfmanClient: cli, - NodeName: fakeNode.Name, - } - - // Set development Logger so we can see all logs in tests. - logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) - - // Create a ReconcileMemcached object with the scheme and fake client. - r := &DiscoveredProgramReconciler{ReconcilerCommon: rc} - - // Mock request to simulate Reconcile() being called on an event for a - // watched resource . - req := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: "fake-control-plane", - Namespace: namespace, - }, - } - - // Three reconciles should create three bpf program objects - res, err := r.Reconcile(ctx, req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } - - require.False(t, res.Requeue) - - res, err = r.Reconcile(ctx, req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } - - require.False(t, res.Requeue) - - res, err = r.Reconcile(ctx, req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } - - require.False(t, res.Requeue) - - // Check the first discovered BpfProgram Object was created successfully - err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName0, Namespace: metav1.NamespaceAll}, bpfProg) - require.NoError(t, err) - - require.NotEmpty(t, bpfProg) - // discovered Label is written - require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel) - // node Label was correctly set - require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) - // ensure annotations were correct - require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[693])) - - // Check the second discovered BpfProgram Object was created successfully - err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName1, Namespace: metav1.NamespaceAll}, bpfProg) - require.NoError(t, err) - - require.NotEmpty(t, bpfProg) - // discovered Label is written - require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel) - // node Label was correctly set - require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) - // ensure annotations were correct - require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[694])) - - // Check the third discovered BpfProgram Object was created successfully - err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName2, Namespace: metav1.NamespaceAll}, bpfProg) - require.NoError(t, err) - - require.NotEmpty(t, bpfProg) - // discovered Label is written - require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel) - // node Label was correctly set - require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) - // ensure annotations were correct - require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[695])) - - // The fourth reconcile will end up exiting with a 30 second requeue - res, err = r.Reconcile(ctx, req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } - - // Require requeue - require.True(t, res.Requeue) -} - -func TestDiscoveredProgramControllerCreateAndDeleteStale(t *testing.T) { - var ( - namespace = "bpfman" - fakeNode = testutils.NewNode("fake-control-plane") - ctx = context.TODO() - bpfProgName0 = fmt.Sprintf("%s-%d-%s", "dump-bpf-map", 693, fakeNode.Name) - bpfProgName1 = fmt.Sprintf("%s-%d-%s", "dump-bpf-prog", 694, fakeNode.Name) - bpfProgName2 = fmt.Sprintf("%d-%s", 695, fakeNode.Name) - bpfProg = &bpfmaniov1alpha1.BpfProgram{} - fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" - programs = map[int]*gobpfman.ListResponse_ListResult{ - 693: { - KernelInfo: &gobpfman.KernelProgramInfo{ - Name: "dump_bpf_map", - ProgramType: 26, - Id: 693, - LoadedAt: "2023-03-02T18:15:06+0000", - Tag: "749172daffada61f", - GplCompatible: true, - MapIds: []uint32{45}, - BtfId: 154, - BytesXlated: 264, - Jited: true, - BytesJited: 287, - BytesMemlock: 4096, - VerifiedInsns: 34, - }, - }, - 694: { - KernelInfo: &gobpfman.KernelProgramInfo{ - Name: "dump_bpf_prog", - ProgramType: 26, - Id: 694, - LoadedAt: "2023-03-02T18:15:06+0000", - Tag: "bc36dd738319ea32", - GplCompatible: true, - MapIds: []uint32{45}, - BtfId: 154, - BytesXlated: 528, - Jited: true, - BytesJited: 715, - BytesMemlock: 4096, - VerifiedInsns: 84, - }, - }, - // test program with no name - 695: { - KernelInfo: &gobpfman.KernelProgramInfo{ - ProgramType: 8, - Id: 695, - LoadedAt: "2023-07-20T19:11:11+0000", - Tag: "6deef7357e7b4530", - GplCompatible: true, - BytesXlated: 64, - Jited: true, - BytesJited: 55, - BytesMemlock: 4096, - VerifiedInsns: 8, - }, - }, - // skip program loaded by bpfman - 696: { - Info: &gobpfman.ProgramInfo{ - Metadata: map[string]string{internal.UuidMetadataKey: fakeUID}, - }, - KernelInfo: &gobpfman.KernelProgramInfo{ - ProgramType: 8, - Id: 696, - LoadedAt: "2023-07-20T19:11:11+0000", - Tag: "6deef7357e7b4530", - GplCompatible: true, - BytesXlated: 64, - Jited: true, - BytesJited: 55, - BytesMemlock: 4096, - VerifiedInsns: 8, - }, - }, - } - ) - - // Objects to track in the fake client. - objs := []runtime.Object{fakeNode} - - // Register operator types with the runtime scheme. - s := scheme.Scheme - s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{}) - s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{}) - - // Create a fake client to mock API calls. - cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() - - cli := agenttestutils.NewBpfmanClientFakeWithPrograms(programs) - - rc := ReconcilerCommon{ - Client: cl, - Scheme: s, - BpfmanClient: cli, - NodeName: fakeNode.Name, - } - - // Set development Logger so we can see all logs in tests. - logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) - - // Create a ReconcileMemcached object with the scheme and fake client. - r := &DiscoveredProgramReconciler{ReconcilerCommon: rc} - - // Mock request to simulate Reconcile() being called on an event for a - // watched resource . - req := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: "fake-control-plane", - Namespace: namespace, - }, - } - - // Three reconciles should create three bpf program objects - res, err := r.Reconcile(ctx, req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } - - require.False(t, res.Requeue) - - res, err = r.Reconcile(ctx, req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } - - require.False(t, res.Requeue) - - res, err = r.Reconcile(ctx, req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } - - require.False(t, res.Requeue) - - // Check the first discovered BpfProgram Object was created successfully - err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName0, Namespace: metav1.NamespaceAll}, bpfProg) - require.NoError(t, err) - - require.NotEmpty(t, bpfProg) - // discovered Label is written - require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel) - // node Label was correctly set - require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) - // ensure annotations were correct - require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[693])) - - // Check the second discovered BpfProgram Object was created successfully - err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName1, Namespace: metav1.NamespaceAll}, bpfProg) - require.NoError(t, err) - - require.NotEmpty(t, bpfProg) - // discovered Label is written - require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel) - // node Label was correctly set - require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) - // ensure annotations were correct - require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[694])) - - // Check the third discovered BpfProgram Object was created successfully - err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName2, Namespace: metav1.NamespaceAll}, bpfProg) - require.NoError(t, err) - - require.NotEmpty(t, bpfProg) - // discovered Label is written - require.Contains(t, bpfProg.Labels, internal.DiscoveredLabel) - // node Label was correctly set - require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) - // ensure annotations were correct - require.Equal(t, bpfProg.Annotations, bpfmanagentinternal.Build_kernel_info_annotations(programs[695])) - - // delete program - _, err = rc.BpfmanClient.Unload(ctx, &gobpfman.UnloadRequest{Id: 693}) - require.NoError(t, err) - - // The fourth reconcile will end up deleting the extra bpfProgram - res, err = r.Reconcile(ctx, req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } - - require.False(t, res.Requeue) - - // Check the first discovered BpfProgram Object was deleted successfully - err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName0, Namespace: metav1.NamespaceAll}, bpfProg) - require.Error(t, err) - - // When all work is done make sure we will reconcile again soon. - res, err = r.Reconcile(ctx, req) - if err != nil { - t.Fatalf("reconcile: (%v)", err) - } - - require.True(t, res.Requeue) -} diff --git a/bpfman-operator/controllers/bpfman-agent/fentry-program.go b/bpfman-operator/controllers/bpfman-agent/fentry-program.go new file mode 100644 index 000000000..2f270a7d8 --- /dev/null +++ b/bpfman-operator/controllers/bpfman-agent/fentry-program.go @@ -0,0 +1,329 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanagent + +import ( + "context" + "fmt" + "strings" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + "github.com/bpfman/bpfman/bpfman-operator/internal" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms,verbs=get;list;watch + +// BpfProgramReconciler reconciles a BpfProgram object +type FentryProgramReconciler struct { + ReconcilerCommon + currentFentryProgram *bpfmaniov1alpha1.FentryProgram + ourNode *v1.Node +} + +func (r *FentryProgramReconciler) getRecCommon() *ReconcilerCommon { + return &r.ReconcilerCommon +} + +func (r *FentryProgramReconciler) getFinalizer() string { + return internal.FentryProgramControllerFinalizer +} + +func (r *FentryProgramReconciler) getRecType() string { + return internal.Tracing.String() +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a FentryProgram is updated, +// load the program to the node via bpfman, and then create a bpfProgram object +// to reflect per node state information. +func (r *FentryProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.FentryProgram{}, builder.WithPredicates(predicate.And(predicate.GenerationChangedPredicate{}, predicate.ResourceVersionChangedPredicate{}))). + Owns(&bpfmaniov1alpha1.BpfProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfProgramTypePredicate(internal.Tracing.String()), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Only trigger reconciliation if node labels change since that could + // make the FentryProgram no longer select the Node. Additionally only + // care about node events specific to our node + Watches( + &source.Kind{Type: &v1.Node{}}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + Complete(r) +} + +func (r *FentryProgramReconciler) expectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) { + progs := &bpfmaniov1alpha1.BpfProgramList{} + + // sanitize fentry name to work in a bpfProgram name + sanatizedFentry := strings.Replace(strings.Replace(r.currentFentryProgram.Spec.FunctionName, "/", "-", -1), "_", "-", -1) + bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentFentryProgram.Name, r.NodeName, sanatizedFentry) + + annotations := map[string]string{internal.FentryProgramFunction: r.currentFentryProgram.Spec.FunctionName} + + prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentFentryProgram, r.getRecType(), annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) + } + + progs.Items = append(progs.Items, *prog) + + return progs, nil +} + +func (r *FentryProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentFentryProgram = &bpfmaniov1alpha1.FentryProgram{} + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("fentry") + + ctxLogger := log.FromContext(ctx) + ctxLogger.Info("Reconcile Fentry: Enter", "ReconcileKey", req) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + fentryPrograms := &bpfmaniov1alpha1.FentryProgramList{} + + opts := []client.ListOption{} + + if err := r.List(ctx, fentryPrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting FentryPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(fentryPrograms.Items) == 0 { + r.Logger.Info("FentryProgramController found no Fentry Programs") + return ctrl.Result{Requeue: false}, nil + } + + // Get existing ebpf state from bpfman. + programMap, err := bpfmanagentinternal.ListBpfmanPrograms(ctx, r.BpfmanClient, internal.Tracing) + if err != nil { + r.Logger.Error(err, "failed to list loaded bpfman programs") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationAgent}, nil + } + + // Reconcile each FentryProgram. Don't return error here because it will trigger an infinite reconcile loop, instead + // report the error to user and retry if specified. For some errors the controller may not decide to retry. + // Note: This only results in grpc calls to bpfman if we need to change something + requeue := false // initialize requeue to false + for _, fentryProgram := range fentryPrograms.Items { + r.Logger.Info("FentryProgramController is reconciling", "currentFentryProgram", fentryProgram.Name) + r.currentFentryProgram = &fentryProgram + result, err := reconcileProgram(ctx, r, r.currentFentryProgram, &r.currentFentryProgram.Spec.BpfProgramCommon, r.ourNode, programMap) + if err != nil { + r.Logger.Error(err, "Reconciling FentryProgram Failed", "FentryProgramName", r.currentFentryProgram.Name, "ReconcileResult", result.String()) + } + + switch result { + case internal.Unchanged: + // continue with next program + case internal.Updated: + // return + return ctrl.Result{Requeue: false}, nil + case internal.Requeue: + // remember to do a requeue when we're done and continue with next program + requeue = true + } + } + + if requeue { + // A requeue has been requested + return ctrl.Result{RequeueAfter: retryDurationAgent}, nil + } else { + // We've made it through all the programs in the list without anything being + // updated and a reque has not been requested. + return ctrl.Result{Requeue: false}, nil + } +} + +func (r *FentryProgramReconciler) buildFentryLoadRequest( + bytecode *gobpfman.BytecodeLocation, + uuid string, + bpfProgram *bpfmaniov1alpha1.BpfProgram, + mapOwnerId *uint32) *gobpfman.LoadRequest { + + return &gobpfman.LoadRequest{ + Bytecode: bytecode, + Name: r.currentFentryProgram.Spec.BpfFunctionName, + ProgramType: uint32(internal.Tracing), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_FentryAttachInfo{ + FentryAttachInfo: &gobpfman.FentryAttachInfo{ + FnName: bpfProgram.Annotations[internal.FentryProgramFunction], + }, + }, + }, + Metadata: map[string]string{internal.UuidMetadataKey: uuid, internal.ProgramNameKey: r.currentFentryProgram.Name}, + GlobalData: r.currentFentryProgram.Spec.GlobalData, + MapOwnerId: mapOwnerId, + } +} + +// reconcileBpfmanPrograms ONLY reconciles the bpfman state for a single BpfProgram. +// It does not interact with the k8s API in any way. +func (r *FentryProgramReconciler) reconcileBpfmanProgram(ctx context.Context, + existingBpfPrograms map[string]*gobpfman.ListResponse_ListResult, + bytecodeSelector *bpfmaniov1alpha1.BytecodeSelector, + bpfProgram *bpfmaniov1alpha1.BpfProgram, + isNodeSelected bool, + isBeingDeleted bool, + mapOwnerStatus *MapOwnerParamStatus) (bpfmaniov1alpha1.BpfProgramConditionType, error) { + + r.Logger.V(1).Info("Existing bpfProgram", "UUID", bpfProgram.UID, "Name", bpfProgram.Name, "CurrentFentryProgram", r.currentFentryProgram.Name) + + uuid := bpfProgram.UID + + getLoadRequest := func() (*gobpfman.LoadRequest, bpfmaniov1alpha1.BpfProgramConditionType, error) { + bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, bytecodeSelector) + if err != nil { + return nil, bpfmaniov1alpha1.BpfProgCondBytecodeSelectorError, fmt.Errorf("failed to process bytecode selector: %v", err) + } + loadRequest := r.buildFentryLoadRequest(bytecode, string(uuid), bpfProgram, mapOwnerStatus.mapOwnerId) + return loadRequest, bpfmaniov1alpha1.BpfProgCondNone, nil + } + + existingProgram, doesProgramExist := existingBpfPrograms[string(uuid)] + if !doesProgramExist { + r.Logger.V(1).Info("FentryProgram doesn't exist on node") + + // If FentryProgram is being deleted just exit + if isBeingDeleted { + return bpfmaniov1alpha1.BpfProgCondUnloaded, nil + } + + // Make sure if we're not selected just exit + if !isNodeSelected { + return bpfmaniov1alpha1.BpfProgCondNotSelected, nil + } + + // Make sure if the Map Owner is set but not found then just exit + if mapOwnerStatus.isSet && !mapOwnerStatus.isFound { + return bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound, nil + } + + // Make sure if the Map Owner is set but not loaded then just exit + if mapOwnerStatus.isSet && !mapOwnerStatus.isLoaded { + return bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded, nil + } + + // otherwise load it + loadRequest, condition, err := getLoadRequest() + if err != nil { + return condition, err + } + + r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest) + if err != nil { + r.Logger.Error(err, "Failed to load FentryProgram") + return bpfmaniov1alpha1.BpfProgCondNotLoaded, nil + } + + r.Logger.Info("bpfman called to load FentryProgram on Node", "Name", bpfProgram.Name, "UUID", uuid) + return bpfmaniov1alpha1.BpfProgCondLoaded, nil + } + + // prog ID should already have been set if program exists + id, err := bpfmanagentinternal.GetID(bpfProgram) + if err != nil { + r.Logger.Error(err, "Failed to get program ID") + return bpfmaniov1alpha1.BpfProgCondNotLoaded, nil + } + + // BpfProgram exists but either FentryProgram is being deleted, node is no + // longer selected, or map is not available....unload program + if isBeingDeleted || !isNodeSelected || + (mapOwnerStatus.isSet && (!mapOwnerStatus.isFound || !mapOwnerStatus.isLoaded)) { + r.Logger.V(1).Info("FentryProgram exists on Node but is scheduled for deletion, not selected, or map not available", + "isDeleted", isBeingDeleted, "isSelected", isNodeSelected, "mapIsSet", mapOwnerStatus.isSet, + "mapIsFound", mapOwnerStatus.isFound, "mapIsLoaded", mapOwnerStatus.isLoaded, "id", id) + + if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil { + r.Logger.Error(err, "Failed to unload FentryProgram") + return bpfmaniov1alpha1.BpfProgCondNotUnloaded, nil + } + + r.Logger.Info("bpfman called to unload FentryProgram on Node", "Name", bpfProgram.Name, "UUID", uuid) + + if isBeingDeleted { + return bpfmaniov1alpha1.BpfProgCondUnloaded, nil + } + + if !isNodeSelected { + return bpfmaniov1alpha1.BpfProgCondNotSelected, nil + } + + if mapOwnerStatus.isSet && !mapOwnerStatus.isFound { + return bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound, nil + } + + if mapOwnerStatus.isSet && !mapOwnerStatus.isLoaded { + return bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded, nil + } + } + + // BpfProgram exists but is not correct state, unload and recreate + loadRequest, condition, err := getLoadRequest() + if err != nil { + return condition, err + } + + r.Logger.V(1).WithValues("expectedProgram", loadRequest).WithValues("existingProgram", existingProgram).Info("StateMatch") + + isSame, reasons := bpfmanagentinternal.DoesProgExist(existingProgram, loadRequest) + if !isSame { + r.Logger.V(1).Info("FentryProgram is in wrong state, unloading and reloading", "Reason", reasons) + if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil { + r.Logger.Error(err, "Failed to unload FentryProgram") + return bpfmaniov1alpha1.BpfProgCondNotUnloaded, nil + } + + r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest) + if err != nil { + r.Logger.Error(err, "Failed to load FentryProgram") + return bpfmaniov1alpha1.BpfProgCondNotLoaded, err + } + + r.Logger.Info("bpfman called to reload FentryProgram on Node", "Name", bpfProgram.Name, "UUID", uuid) + } else { + // Program exists and bpfProgram K8s Object is up to date + r.Logger.V(1).Info("Ignoring Object Change nothing to do in bpfman") + r.progId = id + } + + return bpfmaniov1alpha1.BpfProgCondLoaded, nil +} diff --git a/bpfman-operator/controllers/bpfman-agent/fentry-program_test.go b/bpfman-operator/controllers/bpfman-agent/fentry-program_test.go new file mode 100644 index 000000000..b443b21f9 --- /dev/null +++ b/bpfman-operator/controllers/bpfman-agent/fentry-program_test.go @@ -0,0 +1,197 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanagent + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + agenttestutils "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + internal "github.com/bpfman/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman/bpfman-operator/internal/test-utils" + + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestFentryProgramControllerCreate(t *testing.T) { + var ( + name = "fakeFentryProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test_fentry" + functionName = "do_unlinkat" + fakeNode = testutils.NewNode("fake-control-plane") + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s-%s", name, fakeNode.Name, "do-unlinkat") + bpfProg = &bpfmaniov1alpha1.BpfProgram{} + fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" + ) + // A FentryProgram object with metadata and spec. + Fentry := &bpfmaniov1alpha1.FentryProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.FentryProgramSpec{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + FunctionName: functionName, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, Fentry} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Fentry) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.FentryProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + rc := ReconcilerCommon{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &FentryProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the BpfProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, bpfProg) + require.NoError(t, err) + + require.NotEmpty(t, bpfProg) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProg.Finalizers[0]) + // owningConfig Label was correctly set + require.Equal(t, bpfProg.Labels[internal.BpfProgramOwnerLabel], name) + // node Label was correctly set + require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) + // fentry function Annotation was correctly set + require.Equal(t, bpfProg.Annotations[internal.FentryProgramFunction], functionName) + // Type is set + require.Equal(t, r.getRecType(), bpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of bpfProgram with Fake UID since the fake API server won't + bpfProg.UID = types.UID(fakeUID) + err = cl.Update(ctx, bpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfProgram object's maps field and id annotation. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + expectedLoadReq := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tracing.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProg.UID), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_FentryAttachInfo{ + FentryAttachInfo: &gobpfman.FentryAttachInfo{ + FnName: functionName, + }, + }, + }, + } + + // Check that the bpfProgram's programs was correctly updated + err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, bpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err := bpfmanagentinternal.GetID(bpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly Built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should set the status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the bpfProgram's status was correctly updated + err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, bpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) +} diff --git a/bpfman-operator/controllers/bpfman-agent/fexit-program.go b/bpfman-operator/controllers/bpfman-agent/fexit-program.go new file mode 100644 index 000000000..bb2bfc9ba --- /dev/null +++ b/bpfman-operator/controllers/bpfman-agent/fexit-program.go @@ -0,0 +1,329 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanagent + +import ( + "context" + "fmt" + "strings" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + "github.com/bpfman/bpfman/bpfman-operator/internal" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=fexitprograms,verbs=get;list;watch + +// BpfProgramReconciler reconciles a BpfProgram object +type FexitProgramReconciler struct { + ReconcilerCommon + currentFexitProgram *bpfmaniov1alpha1.FexitProgram + ourNode *v1.Node +} + +func (r *FexitProgramReconciler) getRecCommon() *ReconcilerCommon { + return &r.ReconcilerCommon +} + +func (r *FexitProgramReconciler) getFinalizer() string { + return internal.FexitProgramControllerFinalizer +} + +func (r *FexitProgramReconciler) getRecType() string { + return internal.Tracing.String() +} + +// SetupWithManager sets up the controller with the Manager. +// The Bpfman-Agent should reconcile whenever a FexitProgram is updated, +// load the program to the node via bpfman, and then create a bpfProgram object +// to reflect per node state information. +func (r *FexitProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.FexitProgram{}, builder.WithPredicates(predicate.And(predicate.GenerationChangedPredicate{}, predicate.ResourceVersionChangedPredicate{}))). + Owns(&bpfmaniov1alpha1.BpfProgram{}, + builder.WithPredicates(predicate.And( + internal.BpfProgramTypePredicate(internal.Tracing.String()), + internal.BpfProgramNodePredicate(r.NodeName)), + ), + ). + // Only trigger reconciliation if node labels change since that could + // make the FexitProgram no longer select the Node. Additionally only + // care about node events specific to our node + Watches( + &source.Kind{Type: &v1.Node{}}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), + ). + Complete(r) +} + +func (r *FexitProgramReconciler) expectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) { + progs := &bpfmaniov1alpha1.BpfProgramList{} + + // sanitize fexit name to work in a bpfProgram name + sanatizedFexit := strings.Replace(strings.Replace(r.currentFexitProgram.Spec.FunctionName, "/", "-", -1), "_", "-", -1) + bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentFexitProgram.Name, r.NodeName, sanatizedFexit) + + annotations := map[string]string{internal.FexitProgramFunction: r.currentFexitProgram.Spec.FunctionName} + + prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentFexitProgram, r.getRecType(), annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) + } + + progs.Items = append(progs.Items, *prog) + + return progs, nil +} + +func (r *FexitProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + // Initialize node and current program + r.currentFexitProgram = &bpfmaniov1alpha1.FexitProgram{} + r.ourNode = &v1.Node{} + r.Logger = ctrl.Log.WithName("fexit") + + ctxLogger := log.FromContext(ctx) + ctxLogger.Info("Reconcile Fexit: Enter", "ReconcileKey", req) + + // Lookup K8s node object for this bpfman-agent This should always succeed + if err := r.Get(ctx, types.NamespacedName{Namespace: v1.NamespaceAll, Name: r.NodeName}, r.ourNode); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfman-agent node %s : %v", + req.NamespacedName, err) + } + + fexitPrograms := &bpfmaniov1alpha1.FexitProgramList{} + + opts := []client.ListOption{} + + if err := r.List(ctx, fexitPrograms, opts...); err != nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting FexitPrograms for full reconcile %s : %v", + req.NamespacedName, err) + } + + if len(fexitPrograms.Items) == 0 { + r.Logger.Info("FexitProgramController found no Fexit Programs") + return ctrl.Result{Requeue: false}, nil + } + + // Get existing ebpf state from bpfman. + programMap, err := bpfmanagentinternal.ListBpfmanPrograms(ctx, r.BpfmanClient, internal.Tracing) + if err != nil { + r.Logger.Error(err, "failed to list loaded bpfman programs") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationAgent}, nil + } + + // Reconcile each FexitProgram. Don't return error here because it will trigger an infinite reconcile loop, instead + // report the error to user and retry if specified. For some errors the controller may not decide to retry. + // Note: This only results in grpc calls to bpfman if we need to change something + requeue := false // initialize requeue to false + for _, fexitProgram := range fexitPrograms.Items { + r.Logger.Info("FexitProgramController is reconciling", "currentFexitProgram", fexitProgram.Name) + r.currentFexitProgram = &fexitProgram + result, err := reconcileProgram(ctx, r, r.currentFexitProgram, &r.currentFexitProgram.Spec.BpfProgramCommon, r.ourNode, programMap) + if err != nil { + r.Logger.Error(err, "Reconciling FexitProgram Failed", "FexitProgramName", r.currentFexitProgram.Name, "ReconcileResult", result.String()) + } + + switch result { + case internal.Unchanged: + // continue with next program + case internal.Updated: + // return + return ctrl.Result{Requeue: false}, nil + case internal.Requeue: + // remember to do a requeue when we're done and continue with next program + requeue = true + } + } + + if requeue { + // A requeue has been requested + return ctrl.Result{RequeueAfter: retryDurationAgent}, nil + } else { + // We've made it through all the programs in the list without anything being + // updated and a reque has not been requested. + return ctrl.Result{Requeue: false}, nil + } +} + +func (r *FexitProgramReconciler) buildFexitLoadRequest( + bytecode *gobpfman.BytecodeLocation, + uuid string, + bpfProgram *bpfmaniov1alpha1.BpfProgram, + mapOwnerId *uint32) *gobpfman.LoadRequest { + + return &gobpfman.LoadRequest{ + Bytecode: bytecode, + Name: r.currentFexitProgram.Spec.BpfFunctionName, + ProgramType: uint32(internal.Tracing), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_FexitAttachInfo{ + FexitAttachInfo: &gobpfman.FexitAttachInfo{ + FnName: bpfProgram.Annotations[internal.FexitProgramFunction], + }, + }, + }, + Metadata: map[string]string{internal.UuidMetadataKey: uuid, internal.ProgramNameKey: r.currentFexitProgram.Name}, + GlobalData: r.currentFexitProgram.Spec.GlobalData, + MapOwnerId: mapOwnerId, + } +} + +// reconcileBpfmanPrograms ONLY reconciles the bpfman state for a single BpfProgram. +// It does not interact with the k8s API in any way. +func (r *FexitProgramReconciler) reconcileBpfmanProgram(ctx context.Context, + existingBpfPrograms map[string]*gobpfman.ListResponse_ListResult, + bytecodeSelector *bpfmaniov1alpha1.BytecodeSelector, + bpfProgram *bpfmaniov1alpha1.BpfProgram, + isNodeSelected bool, + isBeingDeleted bool, + mapOwnerStatus *MapOwnerParamStatus) (bpfmaniov1alpha1.BpfProgramConditionType, error) { + + r.Logger.V(1).Info("Existing bpfProgram", "UUID", bpfProgram.UID, "Name", bpfProgram.Name, "CurrentFexitProgram", r.currentFexitProgram.Name) + + uuid := bpfProgram.UID + + getLoadRequest := func() (*gobpfman.LoadRequest, bpfmaniov1alpha1.BpfProgramConditionType, error) { + bytecode, err := bpfmanagentinternal.GetBytecode(r.Client, bytecodeSelector) + if err != nil { + return nil, bpfmaniov1alpha1.BpfProgCondBytecodeSelectorError, fmt.Errorf("failed to process bytecode selector: %v", err) + } + loadRequest := r.buildFexitLoadRequest(bytecode, string(uuid), bpfProgram, mapOwnerStatus.mapOwnerId) + return loadRequest, bpfmaniov1alpha1.BpfProgCondNone, nil + } + + existingProgram, doesProgramExist := existingBpfPrograms[string(uuid)] + if !doesProgramExist { + r.Logger.V(1).Info("FexitProgram doesn't exist on node") + + // If FexitProgram is being deleted just exit + if isBeingDeleted { + return bpfmaniov1alpha1.BpfProgCondUnloaded, nil + } + + // Make sure if we're not selected just exit + if !isNodeSelected { + return bpfmaniov1alpha1.BpfProgCondNotSelected, nil + } + + // Make sure if the Map Owner is set but not found then just exit + if mapOwnerStatus.isSet && !mapOwnerStatus.isFound { + return bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound, nil + } + + // Make sure if the Map Owner is set but not loaded then just exit + if mapOwnerStatus.isSet && !mapOwnerStatus.isLoaded { + return bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded, nil + } + + // otherwise load it + loadRequest, condition, err := getLoadRequest() + if err != nil { + return condition, err + } + + r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest) + if err != nil { + r.Logger.Error(err, "Failed to load FexitProgram") + return bpfmaniov1alpha1.BpfProgCondNotLoaded, nil + } + + r.Logger.Info("bpfman called to load FexitProgram on Node", "Name", bpfProgram.Name, "UUID", uuid) + return bpfmaniov1alpha1.BpfProgCondLoaded, nil + } + + // prog ID should already have been set if program exists + id, err := bpfmanagentinternal.GetID(bpfProgram) + if err != nil { + r.Logger.Error(err, "Failed to get program ID") + return bpfmaniov1alpha1.BpfProgCondNotLoaded, nil + } + + // BpfProgram exists but either FexitProgram is being deleted, node is no + // longer selected, or map is not available....unload program + if isBeingDeleted || !isNodeSelected || + (mapOwnerStatus.isSet && (!mapOwnerStatus.isFound || !mapOwnerStatus.isLoaded)) { + r.Logger.V(1).Info("FexitProgram exists on Node but is scheduled for deletion, not selected, or map not available", + "isDeleted", isBeingDeleted, "isSelected", isNodeSelected, "mapIsSet", mapOwnerStatus.isSet, + "mapIsFound", mapOwnerStatus.isFound, "mapIsLoaded", mapOwnerStatus.isLoaded, "id", id) + + if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil { + r.Logger.Error(err, "Failed to unload FexitProgram") + return bpfmaniov1alpha1.BpfProgCondNotUnloaded, nil + } + + r.Logger.Info("bpfman called to unload FexitProgram on Node", "Name", bpfProgram.Name, "UUID", uuid) + + if isBeingDeleted { + return bpfmaniov1alpha1.BpfProgCondUnloaded, nil + } + + if !isNodeSelected { + return bpfmaniov1alpha1.BpfProgCondNotSelected, nil + } + + if mapOwnerStatus.isSet && !mapOwnerStatus.isFound { + return bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound, nil + } + + if mapOwnerStatus.isSet && !mapOwnerStatus.isLoaded { + return bpfmaniov1alpha1.BpfProgCondMapOwnerNotLoaded, nil + } + } + + // BpfProgram exists but is not correct state, unload and recreate + loadRequest, condition, err := getLoadRequest() + if err != nil { + return condition, err + } + + r.Logger.V(1).WithValues("expectedProgram", loadRequest).WithValues("existingProgram", existingProgram).Info("StateMatch") + + isSame, reasons := bpfmanagentinternal.DoesProgExist(existingProgram, loadRequest) + if !isSame { + r.Logger.V(1).Info("FexitProgram is in wrong state, unloading and reloading", "Reason", reasons) + if err := bpfmanagentinternal.UnloadBpfmanProgram(ctx, r.BpfmanClient, *id); err != nil { + r.Logger.Error(err, "Failed to unload FexitProgram") + return bpfmaniov1alpha1.BpfProgCondNotUnloaded, nil + } + + r.progId, err = bpfmanagentinternal.LoadBpfmanProgram(ctx, r.BpfmanClient, loadRequest) + if err != nil { + r.Logger.Error(err, "Failed to load FexitProgram") + return bpfmaniov1alpha1.BpfProgCondNotLoaded, err + } + + r.Logger.Info("bpfman called to reload FexitProgram on Node", "Name", bpfProgram.Name, "UUID", uuid) + } else { + // Program exists and bpfProgram K8s Object is up to date + r.Logger.V(1).Info("Ignoring Object Change nothing to do in bpfman") + r.progId = id + } + + return bpfmaniov1alpha1.BpfProgCondLoaded, nil +} diff --git a/bpfman-operator/controllers/bpfman-agent/fexit-program_test.go b/bpfman-operator/controllers/bpfman-agent/fexit-program_test.go new file mode 100644 index 000000000..02850c930 --- /dev/null +++ b/bpfman-operator/controllers/bpfman-agent/fexit-program_test.go @@ -0,0 +1,197 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanagent + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + bpfmanagentinternal "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal" + agenttestutils "github.com/bpfman/bpfman/bpfman-operator/controllers/bpfman-agent/internal/test-utils" + internal "github.com/bpfman/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman/bpfman-operator/internal/test-utils" + + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestFexitProgramControllerCreate(t *testing.T) { + var ( + name = "fakeFexitProgram" + namespace = "bpfman" + bytecodePath = "/tmp/fexit.o" + bpfFunctionName = "test_fexit" + functionName = "do_unlinkat" + fakeNode = testutils.NewNode("fake-control-plane") + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s-%s", name, fakeNode.Name, "do-unlinkat") + bpfProg = &bpfmaniov1alpha1.BpfProgram{} + fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" + ) + // A FexitProgram object with metadata and spec. + Fexit := &bpfmaniov1alpha1.FexitProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.FexitProgramSpec{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + FunctionName: functionName, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, Fexit} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Fexit) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.FexitProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + + cli := agenttestutils.NewBpfmanClientFake() + + rc := ReconcilerCommon{ + Client: cl, + Scheme: s, + BpfmanClient: cli, + NodeName: fakeNode.Name, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a ReconcileMemcached object with the scheme and fake client. + r := &FexitProgramReconciler{ReconcilerCommon: rc, ourNode: fakeNode} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: namespace, + }, + } + + // First reconcile should create the bpf program object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Check the BpfProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, bpfProg) + require.NoError(t, err) + + require.NotEmpty(t, bpfProg) + // Finalizer is written + require.Equal(t, r.getFinalizer(), bpfProg.Finalizers[0]) + // owningConfig Label was correctly set + require.Equal(t, bpfProg.Labels[internal.BpfProgramOwnerLabel], name) + // node Label was correctly set + require.Equal(t, bpfProg.Labels[internal.K8sHostLabel], fakeNode.Name) + // fexit function Annotation was correctly set + require.Equal(t, bpfProg.Annotations[internal.FexitProgramFunction], functionName) + // Type is set + require.Equal(t, r.getRecType(), bpfProg.Spec.Type) + // Require no requeue + require.False(t, res.Requeue) + + // Update UID of bpfProgram with Fake UID since the fake API server won't + bpfProg.UID = types.UID(fakeUID) + err = cl.Update(ctx, bpfProg) + require.NoError(t, err) + + // Second reconcile should create the bpfman Load Request and update the + // BpfProgram object's maps field and id annotation. + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + expectedLoadReq := &gobpfman.LoadRequest{ + Bytecode: &gobpfman.BytecodeLocation{ + Location: &gobpfman.BytecodeLocation_File{File: bytecodePath}, + }, + Name: bpfFunctionName, + ProgramType: *internal.Tracing.Uint32(), + Metadata: map[string]string{internal.UuidMetadataKey: string(bpfProg.UID), internal.ProgramNameKey: name}, + MapOwnerId: nil, + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_FexitAttachInfo{ + FexitAttachInfo: &gobpfman.FexitAttachInfo{ + FnName: functionName, + }, + }, + }, + } + + // Check that the bpfProgram's programs was correctly updated + err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, bpfProg) + require.NoError(t, err) + + // prog ID should already have been set + id, err := bpfmanagentinternal.GetID(bpfProg) + require.NoError(t, err) + + // Check the bpfLoadRequest was correctly Built + if !cmp.Equal(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) { + cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform()) + t.Logf("Diff %v", cmp.Diff(expectedLoadReq, cli.LoadRequests[int(*id)], protocmp.Transform())) + t.Fatal("Built bpfman LoadRequest does not match expected") + } + + // Third reconcile should set the status to loaded + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check that the bpfProgram's status was correctly updated + err = cl.Get(ctx, types.NamespacedName{Name: bpfProgName, Namespace: metav1.NamespaceAll}, bpfProg) + require.NoError(t, err) + + require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) +} diff --git a/bpfman-operator/controllers/bpfman-agent/internal/bpfman-core.go b/bpfman-operator/controllers/bpfman-agent/internal/bpfman-core.go index 01564bb09..af9096970 100644 --- a/bpfman-operator/controllers/bpfman-agent/internal/bpfman-core.go +++ b/bpfman-operator/controllers/bpfman-agent/internal/bpfman-core.go @@ -165,8 +165,10 @@ func GetBpfmanProgram(ctx context.Context, bpfmanClient gobpfman.BpfmanClient, u return nil, err } - if len(listResponse.Results) != 1 { - return nil, fmt.Errorf("multiple programs found for uuid: %+v ", uuid) + if len(listResponse.Results) == 0 { + return nil, fmt.Errorf("unable to find program for uuid: %+v ", uuid) + } else if len(listResponse.Results) != 1 { + return nil, fmt.Errorf("multiple programs found for uuid: %+v instances: %d", uuid, len(listResponse.Results)) } return listResponse.Results[0], nil diff --git a/bpfman-operator/controllers/bpfman-agent/internal/cmp.go b/bpfman-operator/controllers/bpfman-agent/internal/cmp.go index 7c6f1d66f..1d316c1de 100644 --- a/bpfman-operator/controllers/bpfman-agent/internal/cmp.go +++ b/bpfman-operator/controllers/bpfman-agent/internal/cmp.go @@ -136,7 +136,7 @@ func DoesProgExist(actual *gobpfman.ListResponse_ListResult, expected *gobpfman. if actualKprobe.FnName != expectedKprobe.FnName || actualKprobe.Offset != expectedKprobe.Offset || actualKprobe.Retprobe != expectedKprobe.Retprobe || - !reflect.DeepEqual(actualKprobe.Namespace, expectedKprobe.Namespace) { + !reflect.DeepEqual(actualKprobe.ContainerPid, expectedKprobe.ContainerPid) { reasons = append(reasons, fmt.Sprintf("Expected Kprobe to be %v but found %v", expectedKprobe, actualKprobe)) } @@ -150,11 +150,29 @@ func DoesProgExist(actual *gobpfman.ListResponse_ListResult, expected *gobpfman. actualUprobe.Target != expectedUprobe.Target || actualUprobe.Retprobe != expectedUprobe.Retprobe || actualUprobe.Pid != expectedUprobe.Pid || - !reflect.DeepEqual(actualUprobe.Namespace, expectedUprobe.Namespace) { + !reflect.DeepEqual(actualUprobe.ContainerPid, expectedUprobe.ContainerPid) { reasons = append(reasons, fmt.Sprintf("Expected Uprobe to be %v but found %v", expectedUprobe, actualUprobe)) } } + + actualFentry := actualAttach.GetFentryAttachInfo() + expectedFentry := expectedAttach.GetFentryAttachInfo() + if actualFentry != nil && expectedFentry != nil { + if !reflect.DeepEqual(actualFentry.FnName, expectedFentry.FnName) { + reasons = append(reasons, fmt.Sprintf("Expected Fentry to be %v but found %v", + expectedFentry, actualFentry)) + } + } + + actualFexit := actualAttach.GetFexitAttachInfo() + expectedFexit := expectedAttach.GetFexitAttachInfo() + if actualFexit != nil && expectedFexit != nil { + if !reflect.DeepEqual(actualFexit.FnName, expectedFexit.FnName) { + reasons = append(reasons, fmt.Sprintf("Expected Fexit to be %v but found %v", + expectedFexit, actualFexit)) + } + } } if len(reasons) == 0 { diff --git a/bpfman-operator/controllers/bpfman-agent/kprobe-program.go b/bpfman-operator/controllers/bpfman-agent/kprobe-program.go index ed6ea8088..a519a633e 100644 --- a/bpfman-operator/controllers/bpfman-agent/kprobe-program.go +++ b/bpfman-operator/controllers/bpfman-agent/kprobe-program.go @@ -85,21 +85,19 @@ func (r *KprobeProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *KprobeProgramReconciler) expectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) { progs := &bpfmaniov1alpha1.BpfProgramList{} - for _, function := range r.currentKprobeProgram.Spec.FunctionNames { - // sanitize kprobe name to work in a bpfProgram name - sanatizedKprobe := strings.Replace(strings.Replace(function, "/", "-", -1), "_", "-", -1) - bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentKprobeProgram.Name, r.NodeName, sanatizedKprobe) + // sanitize kprobe name to work in a bpfProgram name + sanatizedKprobe := strings.Replace(strings.Replace(r.currentKprobeProgram.Spec.FunctionName, "/", "-", -1), "_", "-", -1) + bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentKprobeProgram.Name, r.NodeName, sanatizedKprobe) - annotations := map[string]string{internal.KprobeProgramFunction: function} + annotations := map[string]string{internal.KprobeProgramFunction: r.currentKprobeProgram.Spec.FunctionName} - prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentKprobeProgram, r.getRecType(), annotations) - if err != nil { - return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) - } - - progs.Items = append(progs.Items, *prog) + prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentKprobeProgram, r.getRecType(), annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) } + progs.Items = append(progs.Items, *prog) + return progs, nil } @@ -179,8 +177,8 @@ func (r *KprobeProgramReconciler) buildKprobeLoadRequest( bpfProgram *bpfmaniov1alpha1.BpfProgram, mapOwnerId *uint32) *gobpfman.LoadRequest { - // Namespace isn't supported yet in bpfman, so set it to an empty string. - namespace := "" + // Container PID isn't supported yet in bpfman, so set it to zero. + var container_pid int32 = 0 return &gobpfman.LoadRequest{ Bytecode: bytecode, @@ -189,10 +187,10 @@ func (r *KprobeProgramReconciler) buildKprobeLoadRequest( Attach: &gobpfman.AttachInfo{ Info: &gobpfman.AttachInfo_KprobeAttachInfo{ KprobeAttachInfo: &gobpfman.KprobeAttachInfo{ - FnName: bpfProgram.Annotations[internal.KprobeProgramFunction], - Offset: r.currentKprobeProgram.Spec.Offset, - Retprobe: r.currentKprobeProgram.Spec.RetProbe, - Namespace: &namespace, + FnName: bpfProgram.Annotations[internal.KprobeProgramFunction], + Offset: r.currentKprobeProgram.Spec.Offset, + Retprobe: r.currentKprobeProgram.Spec.RetProbe, + ContainerPid: &container_pid, }, }, }, diff --git a/bpfman-operator/controllers/bpfman-agent/kprobe-program_test.go b/bpfman-operator/controllers/bpfman-agent/kprobe-program_test.go index ac32a2d71..5370a3d78 100644 --- a/bpfman-operator/controllers/bpfman-agent/kprobe-program_test.go +++ b/bpfman-operator/controllers/bpfman-agent/kprobe-program_test.go @@ -45,19 +45,19 @@ import ( func TestKprobeProgramControllerCreate(t *testing.T) { var ( - name = "fakeKprobeProgram" - namespace = "bpfman" - bytecodePath = "/tmp/hello.o" - bpfFunctionName = "test" - functionName = "try_to_wake_up" - offset = 0 - retprobe = false - kprobenamespace = "" - fakeNode = testutils.NewNode("fake-control-plane") - ctx = context.TODO() - bpfProgName = fmt.Sprintf("%s-%s-%s", name, fakeNode.Name, "try-to-wake-up") - bpfProg = &bpfmaniov1alpha1.BpfProgram{} - fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" + name = "fakeKprobeProgram" + namespace = "bpfman" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "test" + functionName = "try_to_wake_up" + offset = 0 + retprobe = false + kprobecontainerpid int32 = 0 + fakeNode = testutils.NewNode("fake-control-plane") + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s-%s", name, fakeNode.Name, "try-to-wake-up") + bpfProg = &bpfmaniov1alpha1.BpfProgram{} + fakeUID = "ef71d42c-aa21-48e8-a697-82391d801a81" ) // A KprobeProgram object with metadata and spec. Kprobe := &bpfmaniov1alpha1.KprobeProgram{ @@ -72,9 +72,9 @@ func TestKprobeProgramControllerCreate(t *testing.T) { Path: &bytecodePath, }, }, - FunctionNames: []string{functionName}, - Offset: uint64(offset), - RetProbe: retprobe, + FunctionName: functionName, + Offset: uint64(offset), + RetProbe: retprobe, }, } @@ -164,10 +164,10 @@ func TestKprobeProgramControllerCreate(t *testing.T) { Attach: &gobpfman.AttachInfo{ Info: &gobpfman.AttachInfo_KprobeAttachInfo{ KprobeAttachInfo: &gobpfman.KprobeAttachInfo{ - FnName: functionName, - Offset: uint64(offset), - Retprobe: retprobe, - Namespace: &kprobenamespace, + FnName: functionName, + Offset: uint64(offset), + Retprobe: retprobe, + ContainerPid: &kprobecontainerpid, }, }, }, diff --git a/bpfman-operator/controllers/bpfman-agent/uprobe-program.go b/bpfman-operator/controllers/bpfman-agent/uprobe-program.go index 8231a1815..017d3189e 100644 --- a/bpfman-operator/controllers/bpfman-agent/uprobe-program.go +++ b/bpfman-operator/controllers/bpfman-agent/uprobe-program.go @@ -19,6 +19,7 @@ package bpfmanagent import ( "context" "fmt" + "strconv" "strings" "k8s.io/apimachinery/pkg/types" @@ -73,30 +74,105 @@ func (r *UprobeProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { internal.BpfProgramNodePredicate(r.NodeName)), ), ). - // Only trigger reconciliation if node labels change since that could - // make the UprobeProgram no longer select the Node. Additionally only - // care about node events specific to our node + // Trigger reconciliation if node labels change since that could make + // the UprobeProgram no longer select the Node. Trigger on pod events + // for when uprobes are attached inside containers. In both cases, only + // care about events specific to our node Watches( &source.Kind{Type: &v1.Node{}}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(predicate.And(predicate.LabelChangedPredicate{}, nodePredicate(r.NodeName))), ). + // Watch for changes in Pod resources in case we are using a container selector. + Watches( + &source.Kind{Type: &v1.Pod{}}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(podOnNodePredicate(r.NodeName)), + ). Complete(r) } +// Figure out the list of container pids the uProbe needs to be attached to. +func (r *UprobeProgramReconciler) getUprobeContainerInfo(ctx context.Context) (*[]containerInfo, error) { + + clientSet, err := getClientset() + if err != nil { + return nil, fmt.Errorf("failed to get clientset: %v", err) + } + + // Get the list of pods that match the selector. + podList, err := getPodsForNode(ctx, clientSet, r.currentUprobeProgram.Spec.Containers, r.NodeName) + if err != nil { + return nil, fmt.Errorf("failed to get pod list: %v", err) + } + + // Get the list of containers in the list of pods that match the selector. + containers, err := getContainerInfo(podList, r.currentUprobeProgram.Spec.Containers.ContainerNames, r.Logger) + if err != nil { + return nil, fmt.Errorf("failed to get container info: %v", err) + } + + r.Logger.V(1).Info("from getContainerInfo", "containers", containers) + + return containers, nil +} + func (r *UprobeProgramReconciler) expectedBpfPrograms(ctx context.Context) (*bpfmaniov1alpha1.BpfProgramList, error) { progs := &bpfmaniov1alpha1.BpfProgramList{} - for _, target := range r.currentUprobeProgram.Spec.Targets { - // sanitize uprobe name to work in a bpfProgram name - sanatizedUprobe := strings.Replace(strings.Replace(target, "/", "-", -1), "_", "-", -1) - bpfProgramName := fmt.Sprintf("%s-%s-%s", r.currentUprobeProgram.Name, r.NodeName, sanatizedUprobe) + // sanitize uprobe name to work in a bpfProgram name + sanatizedUprobe := strings.Replace(strings.Replace(r.currentUprobeProgram.Spec.Target, "/", "-", -1), "_", "-", -1) + bpfProgramNameBase := fmt.Sprintf("%s-%s-%s", r.currentUprobeProgram.Name, r.NodeName, sanatizedUprobe) - annotations := map[string]string{internal.UprobeProgramTarget: target} + if r.currentUprobeProgram.Spec.Containers != nil { - prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentUprobeProgram, r.getRecType(), annotations) + // Some containers were specified, so we need to get the containers + containerInfo, err := r.getUprobeContainerInfo(ctx) if err != nil { - return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) + return nil, fmt.Errorf("failed to get container pids: %v", err) + } + if containerInfo == nil || len(*containerInfo) == 0 { + // There were no errors, but the container selector didn't + // select any containers on this node. + + annotations := map[string]string{ + internal.UprobeProgramTarget: r.currentUprobeProgram.Spec.Target, + internal.UprobeNoContainersOnNode: "true", + } + + bpfProgramName := fmt.Sprintf("%s-%s", bpfProgramNameBase, "no-containers-on-node") + + prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentUprobeProgram, r.getRecType(), annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramNameBase, err) + } + + progs.Items = append(progs.Items, *prog) + } else { + + // We got some containers, so create the bpfPrograms for each one. + for i := range *containerInfo { + container := (*containerInfo)[i] + + annotations := map[string]string{internal.UprobeProgramTarget: r.currentUprobeProgram.Spec.Target} + annotations[internal.UprobeContainerPid] = strconv.FormatInt(container.pid, 10) + + bpfProgramName := fmt.Sprintf("%s-%s-%s", bpfProgramNameBase, container.podName, container.containerName) + + prog, err := r.createBpfProgram(ctx, bpfProgramName, r.getFinalizer(), r.currentUprobeProgram, r.getRecType(), annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramName, err) + } + + progs.Items = append(progs.Items, *prog) + } + } + } else { + annotations := map[string]string{internal.UprobeProgramTarget: r.currentUprobeProgram.Spec.Target} + + prog, err := r.createBpfProgram(ctx, bpfProgramNameBase, r.getFinalizer(), r.currentUprobeProgram, r.getRecType(), annotations) + if err != nil { + return nil, fmt.Errorf("failed to create BpfProgram %s: %v", bpfProgramNameBase, err) } progs.Items = append(progs.Items, *prog) @@ -182,8 +258,33 @@ func (r *UprobeProgramReconciler) buildUprobeLoadRequest( bpfProgram *bpfmaniov1alpha1.BpfProgram, mapOwnerId *uint32) *gobpfman.LoadRequest { - // Namespace isn't supported yet in bpfman, so set it to an empty string. - namespace := "" + var uprobeAttachInfo *gobpfman.UprobeAttachInfo + + var containerPid int32 + hasContainerPid := false + + containerPidStr, ok := bpfProgram.Annotations[internal.UprobeContainerPid] + + if ok { + containerPidInt64, err := strconv.ParseInt(containerPidStr, 10, 32) + if err != nil { + r.Logger.Error(err, "ParseInt() error on containerPidStr", "containerPidStr", containerPidStr) + } else { + containerPid = int32(containerPidInt64) + hasContainerPid = true + } + } + + uprobeAttachInfo = &gobpfman.UprobeAttachInfo{ + FnName: &r.currentUprobeProgram.Spec.FunctionName, + Offset: r.currentUprobeProgram.Spec.Offset, + Target: bpfProgram.Annotations[internal.UprobeProgramTarget], + Retprobe: r.currentUprobeProgram.Spec.RetProbe, + } + + if hasContainerPid { + uprobeAttachInfo.ContainerPid = &containerPid + } return &gobpfman.LoadRequest{ Bytecode: bytecode, @@ -191,13 +292,7 @@ func (r *UprobeProgramReconciler) buildUprobeLoadRequest( ProgramType: uint32(internal.Kprobe), Attach: &gobpfman.AttachInfo{ Info: &gobpfman.AttachInfo_UprobeAttachInfo{ - UprobeAttachInfo: &gobpfman.UprobeAttachInfo{ - FnName: &r.currentUprobeProgram.Spec.FunctionName, - Offset: r.currentUprobeProgram.Spec.Offset, - Target: bpfProgram.Annotations[internal.UprobeProgramTarget], - Retprobe: r.currentUprobeProgram.Spec.RetProbe, - Namespace: &namespace, - }, + UprobeAttachInfo: uprobeAttachInfo, }, }, Metadata: map[string]string{internal.UuidMetadataKey: uuid, internal.ProgramNameKey: r.currentUprobeProgram.Name}, @@ -229,6 +324,8 @@ func (r *UprobeProgramReconciler) reconcileBpfmanProgram(ctx context.Context, return loadRequest, bpfmaniov1alpha1.BpfProgCondNone, nil } + noContainers := noContainersOnNode(bpfProgram) + existingProgram, doesProgramExist := existingBpfPrograms[string(uuid)] if !doesProgramExist { r.Logger.V(1).Info("UprobeProgram doesn't exist on node") @@ -243,6 +340,13 @@ func (r *UprobeProgramReconciler) reconcileBpfmanProgram(ctx context.Context, return bpfmaniov1alpha1.BpfProgCondNotSelected, nil } + // If a container selector is present but there were no matching + // containers on this node, just exit. + if noContainers { + r.Logger.V(1).Info("Program does not exist and there are no matching containers on this node") + return bpfmaniov1alpha1.BpfProgCondNoContainersOnNode, nil + } + // Make sure if the Map Owner is set but not found then just exit if mapOwnerStatus.isSet && !mapOwnerStatus.isFound { return bpfmaniov1alpha1.BpfProgCondMapOwnerNotFound, nil @@ -278,7 +382,7 @@ func (r *UprobeProgramReconciler) reconcileBpfmanProgram(ctx context.Context, // BpfProgram exists but either UprobeProgram is being deleted, node is no // longer selected, or map is not available....unload program - if isBeingDeleted || !isNodeSelected || + if isBeingDeleted || !isNodeSelected || noContainers || (mapOwnerStatus.isSet && (!mapOwnerStatus.isFound || !mapOwnerStatus.isLoaded)) { r.Logger.V(1).Info("UprobeProgram exists on Node but is scheduled for deletion, not selected, or map not available", "isDeleted", isBeingDeleted, "isSelected", isNodeSelected, "mapIsSet", mapOwnerStatus.isSet, @@ -289,7 +393,7 @@ func (r *UprobeProgramReconciler) reconcileBpfmanProgram(ctx context.Context, return bpfmaniov1alpha1.BpfProgCondNotUnloaded, nil } - r.Logger.Info("bpfman called to unload UprobeProgram on Node", "Name", bpfProgram.Name, "UUID", uuid) + r.Logger.Info("bpfman called to unload UprobeProgram on Node", "Name", bpfProgram.Name, "Program ID", id) if isBeingDeleted { return bpfmaniov1alpha1.BpfProgCondUnloaded, nil @@ -334,6 +438,7 @@ func (r *UprobeProgramReconciler) reconcileBpfmanProgram(ctx context.Context, } else { // Program exists and bpfProgram K8s Object is up to date r.Logger.V(1).Info("Ignoring Object Change nothing to do in bpfman") + r.progId = id } return bpfmaniov1alpha1.BpfProgCondLoaded, nil diff --git a/bpfman-operator/controllers/bpfman-agent/uprobe-program_test.go b/bpfman-operator/controllers/bpfman-agent/uprobe-program_test.go index 8b45b1988..465200c57 100644 --- a/bpfman-operator/controllers/bpfman-agent/uprobe-program_test.go +++ b/bpfman-operator/controllers/bpfman-agent/uprobe-program_test.go @@ -32,9 +32,11 @@ import ( gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + clientGoFake "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -53,7 +55,6 @@ func TestUprobeProgramControllerCreate(t *testing.T) { target = "libc" offset = 0 retprobe = false - uprobenamespace = "" fakeNode = testutils.NewNode("fake-control-plane") ctx = context.TODO() bpfProgName = fmt.Sprintf("%s-%s-%s", name, fakeNode.Name, "libc") @@ -74,7 +75,7 @@ func TestUprobeProgramControllerCreate(t *testing.T) { }, }, FunctionName: functionName, - Targets: []string{target}, + Target: target, Offset: uint64(offset), RetProbe: retprobe, }, @@ -166,11 +167,10 @@ func TestUprobeProgramControllerCreate(t *testing.T) { Attach: &gobpfman.AttachInfo{ Info: &gobpfman.AttachInfo_UprobeAttachInfo{ UprobeAttachInfo: &gobpfman.UprobeAttachInfo{ - FnName: &functionName, - Target: target, - Offset: uint64(offset), - Retprobe: retprobe, - Namespace: &uprobenamespace, + FnName: &functionName, + Target: target, + Offset: uint64(offset), + Retprobe: retprobe, }, }, }, @@ -206,3 +206,59 @@ func TestUprobeProgramControllerCreate(t *testing.T) { require.Equal(t, string(bpfmaniov1alpha1.BpfProgCondLoaded), bpfProg.Status.Conditions[0].Type) } + +func TestGetPods(t *testing.T) { + ctx := context.TODO() + + // Create a fake clientset + clientset := clientGoFake.NewSimpleClientset() + + // Create a ContainerSelector + containerSelector := &bpfmaniov1alpha1.ContainerSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "test", + }, + }, + Namespace: "default", + } + + nodeName := "test-node" + + // Create a Pod that matches the label selector and is on the correct node + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pod", + Namespace: "default", + Labels: map[string]string{ + "app": "test", + }, + }, + Spec: v1.PodSpec{ + NodeName: nodeName, + }, + } + + // Add the Pod to the fake clientset + _, err := clientset.CoreV1().Pods("default").Create(ctx, pod, metav1.CreateOptions{}) + require.NoError(t, err) + + // Call getPods and check the returned PodList + podList, err := getPodsForNode(ctx, clientset, containerSelector, nodeName) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Equal(t, "test-pod", podList.Items[0].Name) + + // Try another selector + // Create a ContainerSelector + containerSelector = &bpfmaniov1alpha1.ContainerSelector{ + Pods: metav1.LabelSelector{ + MatchLabels: map[string]string{}, + }, + } + + podList, err = getPodsForNode(ctx, clientset, containerSelector, nodeName) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Equal(t, "test-pod", podList.Items[0].Name) +} diff --git a/bpfman-operator/controllers/bpfman-operator/common.go b/bpfman-operator/controllers/bpfman-operator/common.go index d3a30ad4f..77fc675e5 100644 --- a/bpfman-operator/controllers/bpfman-operator/common.go +++ b/bpfman-operator/controllers/bpfman-operator/common.go @@ -91,12 +91,22 @@ func reconcileBpfProgram(ctx context.Context, rec ProgramReconciler, prog client return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil } - // Return NotYetLoaded Status if - // BpfPrograms for each node haven't been created by bpfman-agent and the config isn't - // being deleted. - if len(nodes.Items) != len(bpfPrograms.Items) && prog.GetDeletionTimestamp().IsZero() { - // Causes Requeue - return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramNotYetLoaded, "") + // If the program isn't being deleted, make sure that each node has at + // least one bpfprogram object. If not, Return NotYetLoaded Status. + if prog.GetDeletionTimestamp().IsZero() { + for _, node := range nodes.Items { + nodeFound := false + for _, program := range bpfPrograms.Items { + bpfProgramNode := program.ObjectMeta.Labels[internal.K8sHostLabel] + if node.Name == bpfProgramNode { + nodeFound = true + break + } + } + if !nodeFound { + return rec.updateStatus(ctx, progName, bpfmaniov1alpha1.ProgramNotYetLoaded, "") + } + } } failedBpfPrograms := []string{} diff --git a/bpfman-operator/controllers/bpfman-operator/configmap.go b/bpfman-operator/controllers/bpfman-operator/configmap.go index 96f7bd61f..5584c57d8 100644 --- a/bpfman-operator/controllers/bpfman-operator/configmap.go +++ b/bpfman-operator/controllers/bpfman-operator/configmap.go @@ -21,6 +21,7 @@ import ( "io" "os" "reflect" + "strings" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -248,6 +249,8 @@ func LoadAndConfigureBpfmanDs(config *corev1.ConfigMap, path string) *appsv1.Dae bpfmanAgentImage := config.Data["bpfman.agent.image"] bpfmanLogLevel := config.Data["bpfman.log.level"] bpfmanAgentLogLevel := config.Data["bpfman.agent.log.level"] + bpfmanHealthProbeAddr := config.Data["bpfman.agent.healthprobe.addr"] + bpfmanMetricAddr := config.Data["bpfman.agent.metric.addr"] // Annotate the log level on the ds so we get automatic restarts on changes. if staticBpfmanDeployment.Spec.Template.ObjectMeta.Annotations == nil { @@ -256,10 +259,32 @@ func LoadAndConfigureBpfmanDs(config *corev1.ConfigMap, path string) *appsv1.Dae staticBpfmanDeployment.Spec.Template.ObjectMeta.Annotations["bpfman.io.bpfman.loglevel"] = bpfmanLogLevel staticBpfmanDeployment.Spec.Template.ObjectMeta.Annotations["bpfman.io.bpfman.agent.loglevel"] = bpfmanAgentLogLevel - staticBpfmanDeployment.Name = "bpfman-daemon" + staticBpfmanDeployment.Spec.Template.ObjectMeta.Annotations["bpfman.io.bpfman.agent.healthprobeaddr"] = bpfmanHealthProbeAddr + staticBpfmanDeployment.Spec.Template.ObjectMeta.Annotations["bpfman.io.bpfman.agent.metricaddr"] = bpfmanMetricAddr + staticBpfmanDeployment.Name = internal.BpfmanDsName staticBpfmanDeployment.Namespace = config.Namespace - staticBpfmanDeployment.Spec.Template.Spec.Containers[0].Image = bpfmanImage - staticBpfmanDeployment.Spec.Template.Spec.Containers[1].Image = bpfmanAgentImage + for cindex, container := range staticBpfmanDeployment.Spec.Template.Spec.Containers { + if container.Name == internal.BpfmanContainerName { + staticBpfmanDeployment.Spec.Template.Spec.Containers[cindex].Image = bpfmanImage + } else if container.Name == internal.BpfmanAgentContainerName { + staticBpfmanDeployment.Spec.Template.Spec.Containers[cindex].Image = bpfmanAgentImage + + for aindex, arg := range container.Args { + if bpfmanHealthProbeAddr != "" { + if strings.Contains(arg, "health-probe-bind-address") { + staticBpfmanDeployment.Spec.Template.Spec.Containers[cindex].Args[aindex] = + "--health-probe-bind-address=" + bpfmanHealthProbeAddr + } + } + if bpfmanMetricAddr != "" { + if strings.Contains(arg, "metrics-bind-address") { + staticBpfmanDeployment.Spec.Template.Spec.Containers[cindex].Args[aindex] = + "--metrics-bind-address=" + bpfmanMetricAddr + } + } + } + } + } controllerutil.AddFinalizer(staticBpfmanDeployment, internal.BpfmanOperatorFinalizer) return staticBpfmanDeployment diff --git a/bpfman-operator/controllers/bpfman-operator/fentry-program.go b/bpfman-operator/controllers/bpfman-operator/fentry-program.go new file mode 100644 index 000000000..0ec57b94e --- /dev/null +++ b/bpfman-operator/controllers/bpfman-operator/fentry-program.go @@ -0,0 +1,116 @@ +/* +Copyright 2024. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman/bpfman-operator/internal" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=fentryprograms/finalizers,verbs=update + +type FentryProgramReconciler struct { + ReconcilerCommon +} + +func (r *FentryProgramReconciler) getRecCommon() *ReconcilerCommon { + return &r.ReconcilerCommon +} + +func (r *FentryProgramReconciler) getFinalizer() string { + return internal.FentryProgramControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *FentryProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.FentryProgram{}). + // Watch bpfPrograms which are owned by FentryPrograms + Watches( + &source.Kind{Type: &bpfmaniov1alpha1.BpfProgram{}}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Tracing.String()))), + ). + Complete(r) +} + +func (r *FentryProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = log.FromContext(ctx) + + fentryProgram := &bpfmaniov1alpha1.FentryProgram{} + if err := r.Get(ctx, req.NamespacedName, fentryProgram); err != nil { + // Reconcile was triggered by bpfProgram event, get parent FentryProgram Object. + if errors.IsNotFound(err) { + bpfProgram := &bpfmaniov1alpha1.BpfProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("bpfProgram not found stale reconcile, exiting", "Name", req.NamespacedName) + } else { + r.Logger.Error(err, "failed getting bpfProgram Object", "Name", req.NamespacedName) + } + return ctrl.Result{}, nil + } + + // Get owning FentryProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: corev1.NamespaceAll, Name: ownerRef.Name}, fentryProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("Fentry Program from ownerRef not found stale reconcile exiting", "Name", req.NamespacedName) + } else { + r.Logger.Error(err, "failed getting FentryProgram Object from ownerRef", "Name", req.NamespacedName) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting FentryProgram Object", "Name", req.NamespacedName) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, fentryProgram) +} + +func (r *FentryProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { + // Sometimes we end up with a stale FentryProgram due to races, do this + // get to ensure we're up to date before attempting a status update. + prog := &bpfmaniov1alpha1.FentryProgram{} + if err := r.Get(ctx, types.NamespacedName{Namespace: corev1.NamespaceAll, Name: name}, prog); err != nil { + r.Logger.V(1).Info("failed to get fresh FentryProgram object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.ReconcilerCommon.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message) +} diff --git a/bpfman-operator/controllers/bpfman-operator/fentry-program_test.go b/bpfman-operator/controllers/bpfman-operator/fentry-program_test.go new file mode 100644 index 000000000..91e4cd764 --- /dev/null +++ b/bpfman-operator/controllers/bpfman-operator/fentry-program_test.go @@ -0,0 +1,181 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + meta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// Runs the FentryProgramReconcile test. If multiCondition == true, it runs it +// with an error case in which the program object has multiple conditions. +func fentryProgramReconcile(t *testing.T, multiCondition bool) { + var ( + name = "fakeFentryProgram" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "fentry_test" + fakeNode = testutils.NewNode("fake-control-plane") + functionName = "do_unlinkat" + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + // A FentryProgram object with metadata and spec. + Fentry := &bpfmaniov1alpha1.FentryProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.FentryProgramSpec{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + FunctionName: functionName, + }, + } + + // The expected accompanying BpfProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + OwnerReferences: []metav1.OwnerReference{ + { + Name: Fentry.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwnerLabel: Fentry.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.FentryProgramControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "fentry", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, Fentry, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Fentry) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.TcProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon{ + Client: cl, + Scheme: s, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a FentryProgram object with the scheme and fake client. + r := &FentryProgramReconciler{ReconcilerCommon: rc} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + }, + } + + // First reconcile should add the finalzier to the fentryProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Fentry.Name, Namespace: metav1.NamespaceAll}, Fentry) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, Fentry.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // NOTE: THIS IS A TEST FOR AN ERROR PATH. THERE SHOULD NEVER BE MORE THAN + // ONE CONDITION. + if multiCondition { + // Add some random conditions and verify that the condition still gets + // updated correctly. + meta.SetStatusCondition(&Fentry.Status.Conditions, bpfmaniov1alpha1.ProgramDeleteError.Condition("bogus condition #1")) + if err := r.Status().Update(ctx, Fentry); err != nil { + r.Logger.V(1).Info("failed to set FentryProgram object status") + } + meta.SetStatusCondition(&Fentry.Status.Conditions, bpfmaniov1alpha1.ProgramReconcileError.Condition("bogus condition #2")) + if err := r.Status().Update(ctx, Fentry); err != nil { + r.Logger.V(1).Info("failed to set FentryProgram object status") + } + // Make sure we have 2 conditions + require.Equal(t, 2, len(Fentry.Status.Conditions)) + } + + // Second reconcile should check bpfProgram Status and write Success condition to tcProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Fentry.Name, Namespace: metav1.NamespaceAll}, Fentry) + require.NoError(t, err) + + // Make sure we only have 1 condition now + require.Equal(t, 1, len(Fentry.Status.Conditions)) + // Make sure it's the right one. + require.Equal(t, Fentry.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) +} + +func TestFentryProgramReconcile(t *testing.T) { + fentryProgramReconcile(t, false) +} + +func TestFentryUpdateStatus(t *testing.T) { + fentryProgramReconcile(t, true) +} diff --git a/bpfman-operator/controllers/bpfman-operator/fexit-program.go b/bpfman-operator/controllers/bpfman-operator/fexit-program.go new file mode 100644 index 000000000..6739ed10c --- /dev/null +++ b/bpfman-operator/controllers/bpfman-operator/fexit-program.go @@ -0,0 +1,116 @@ +/* +Copyright 2024. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + "github.com/bpfman/bpfman/bpfman-operator/internal" +) + +//+kubebuilder:rbac:groups=bpfman.io,resources=fexitprograms,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=bpfman.io,resources=fexitprograms/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=bpfman.io,resources=fexitprograms/finalizers,verbs=update + +type FexitProgramReconciler struct { + ReconcilerCommon +} + +func (r *FexitProgramReconciler) getRecCommon() *ReconcilerCommon { + return &r.ReconcilerCommon +} + +func (r *FexitProgramReconciler) getFinalizer() string { + return internal.FexitProgramControllerFinalizer +} + +// SetupWithManager sets up the controller with the Manager. +func (r *FexitProgramReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&bpfmaniov1alpha1.FexitProgram{}). + // Watch bpfPrograms which are owned by FexitPrograms + Watches( + &source.Kind{Type: &bpfmaniov1alpha1.BpfProgram{}}, + &handler.EnqueueRequestForObject{}, + builder.WithPredicates(predicate.And(statusChangedPredicate(), internal.BpfProgramTypePredicate(internal.Tracing.String()))), + ). + Complete(r) +} + +func (r *FexitProgramReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger = log.FromContext(ctx) + + fexitProgram := &bpfmaniov1alpha1.FexitProgram{} + if err := r.Get(ctx, req.NamespacedName, fexitProgram); err != nil { + // Reconcile was triggered by bpfProgram event, get parent FexitProgram Object. + if errors.IsNotFound(err) { + bpfProgram := &bpfmaniov1alpha1.BpfProgram{} + if err := r.Get(ctx, req.NamespacedName, bpfProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.V(1).Info("bpfProgram not found stale reconcile, exiting", "Name", req.NamespacedName) + } else { + r.Logger.Error(err, "failed getting bpfProgram Object", "Name", req.NamespacedName) + } + return ctrl.Result{}, nil + } + + // Get owning FexitProgram object from ownerRef + ownerRef := metav1.GetControllerOf(bpfProgram) + if ownerRef == nil { + return ctrl.Result{Requeue: false}, fmt.Errorf("failed getting bpfProgram Object owner") + } + + if err := r.Get(ctx, types.NamespacedName{Namespace: corev1.NamespaceAll, Name: ownerRef.Name}, fexitProgram); err != nil { + if errors.IsNotFound(err) { + r.Logger.Info("Fexit Program from ownerRef not found stale reconcile exiting", "Name", req.NamespacedName) + } else { + r.Logger.Error(err, "failed getting FexitProgram Object from ownerRef", "Name", req.NamespacedName) + } + return ctrl.Result{}, nil + } + + } else { + r.Logger.Error(err, "failed getting FexitProgram Object", "Name", req.NamespacedName) + return ctrl.Result{}, nil + } + } + + return reconcileBpfProgram(ctx, r, fexitProgram) +} + +func (r *FexitProgramReconciler) updateStatus(ctx context.Context, name string, cond bpfmaniov1alpha1.ProgramConditionType, message string) (ctrl.Result, error) { + // Sometimes we end up with a stale FexitProgram due to races, do this + // get to ensure we're up to date before attempting a status update. + prog := &bpfmaniov1alpha1.FexitProgram{} + if err := r.Get(ctx, types.NamespacedName{Namespace: corev1.NamespaceAll, Name: name}, prog); err != nil { + r.Logger.V(1).Info("failed to get fresh FexitProgram object...requeuing") + return ctrl.Result{Requeue: true, RequeueAfter: retryDurationOperator}, nil + } + + return r.ReconcilerCommon.updateCondition(ctx, prog, &prog.Status.Conditions, cond, message) +} diff --git a/bpfman-operator/controllers/bpfman-operator/fexit-program_test.go b/bpfman-operator/controllers/bpfman-operator/fexit-program_test.go new file mode 100644 index 000000000..9ed9caac8 --- /dev/null +++ b/bpfman-operator/controllers/bpfman-operator/fexit-program_test.go @@ -0,0 +1,181 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bpfmanoperator + +import ( + "context" + "fmt" + "testing" + + bpfmaniov1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + internal "github.com/bpfman/bpfman/bpfman-operator/internal" + testutils "github.com/bpfman/bpfman/bpfman-operator/internal/test-utils" + + "github.com/stretchr/testify/require" + meta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// Runs the FexitProgramReconcile test. If multiCondition == true, it runs it +// with an error case in which the program object has multiple conditions. +func fexitProgramReconcile(t *testing.T, multiCondition bool) { + var ( + name = "fakeFexitProgram" + bytecodePath = "/tmp/hello.o" + bpfFunctionName = "fexit_test" + fakeNode = testutils.NewNode("fake-control-plane") + functionName = "do_unlinkat" + ctx = context.TODO() + bpfProgName = fmt.Sprintf("%s-%s", name, fakeNode.Name) + ) + // A FexitProgram object with metadata and spec. + Fexit := &bpfmaniov1alpha1.FexitProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: bpfmaniov1alpha1.FexitProgramSpec{ + BpfProgramCommon: bpfmaniov1alpha1.BpfProgramCommon{ + BpfFunctionName: bpfFunctionName, + NodeSelector: metav1.LabelSelector{}, + ByteCode: bpfmaniov1alpha1.BytecodeSelector{ + Path: &bytecodePath, + }, + }, + FunctionName: functionName, + }, + } + + // The expected accompanying BpfProgram object + expectedBpfProg := &bpfmaniov1alpha1.BpfProgram{ + ObjectMeta: metav1.ObjectMeta{ + Name: bpfProgName, + OwnerReferences: []metav1.OwnerReference{ + { + Name: Fexit.Name, + Controller: &[]bool{true}[0], + }, + }, + Labels: map[string]string{internal.BpfProgramOwnerLabel: Fexit.Name, internal.K8sHostLabel: fakeNode.Name}, + Finalizers: []string{internal.FexitProgramControllerFinalizer}, + }, + Spec: bpfmaniov1alpha1.BpfProgramSpec{ + Type: "fexit", + }, + Status: bpfmaniov1alpha1.BpfProgramStatus{ + Conditions: []metav1.Condition{bpfmaniov1alpha1.BpfProgCondLoaded.Condition()}, + }, + } + + // Objects to track in the fake client. + objs := []runtime.Object{fakeNode, Fexit, expectedBpfProg} + + // Register operator types with the runtime scheme. + s := scheme.Scheme + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, Fexit) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.TcProgramList{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgram{}) + s.AddKnownTypes(bpfmaniov1alpha1.SchemeGroupVersion, &bpfmaniov1alpha1.BpfProgramList{}) + + // Create a fake client to mock API calls. + cl := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build() + + rc := ReconcilerCommon{ + Client: cl, + Scheme: s, + } + + // Set development Logger so we can see all logs in tests. + logf.SetLogger(zap.New(zap.UseFlagOptions(&zap.Options{Development: true}))) + + // Create a FexitProgram object with the scheme and fake client. + r := &FexitProgramReconciler{ReconcilerCommon: rc} + + // Mock request to simulate Reconcile() being called on an event for a + // watched resource . + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + }, + } + + // First reconcile should add the finalzier to the fexitProgram object + res, err := r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Fexit.Name, Namespace: metav1.NamespaceAll}, Fexit) + require.NoError(t, err) + + // Check the bpfman-operator finalizer was successfully added + require.Contains(t, Fexit.GetFinalizers(), internal.BpfmanOperatorFinalizer) + + // NOTE: THIS IS A TEST FOR AN ERROR PATH. THERE SHOULD NEVER BE MORE THAN + // ONE CONDITION. + if multiCondition { + // Add some random conditions and verify that the condition still gets + // updated correctly. + meta.SetStatusCondition(&Fexit.Status.Conditions, bpfmaniov1alpha1.ProgramDeleteError.Condition("bogus condition #1")) + if err := r.Status().Update(ctx, Fexit); err != nil { + r.Logger.V(1).Info("failed to set FexitProgram object status") + } + meta.SetStatusCondition(&Fexit.Status.Conditions, bpfmaniov1alpha1.ProgramReconcileError.Condition("bogus condition #2")) + if err := r.Status().Update(ctx, Fexit); err != nil { + r.Logger.V(1).Info("failed to set FexitProgram object status") + } + // Make sure we have 2 conditions + require.Equal(t, 2, len(Fexit.Status.Conditions)) + } + + // Second reconcile should check bpfProgram Status and write Success condition to tcProgram Status + res, err = r.Reconcile(ctx, req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + + // Require no requeue + require.False(t, res.Requeue) + + // Check the BpfProgram Object was created successfully + err = cl.Get(ctx, types.NamespacedName{Name: Fexit.Name, Namespace: metav1.NamespaceAll}, Fexit) + require.NoError(t, err) + + // Make sure we only have 1 condition now + require.Equal(t, 1, len(Fexit.Status.Conditions)) + // Make sure it's the right one. + require.Equal(t, Fexit.Status.Conditions[0].Type, string(bpfmaniov1alpha1.ProgramReconcileSuccess)) +} + +func TestFexitProgramReconcile(t *testing.T) { + fexitProgramReconcile(t, false) +} + +func TestFexitUpdateStatus(t *testing.T) { + fexitProgramReconcile(t, true) +} diff --git a/bpfman-operator/controllers/bpfman-operator/kprobe-program_test.go b/bpfman-operator/controllers/bpfman-operator/kprobe-program_test.go index fb8d148e0..2612c1306 100644 --- a/bpfman-operator/controllers/bpfman-operator/kprobe-program_test.go +++ b/bpfman-operator/controllers/bpfman-operator/kprobe-program_test.go @@ -65,9 +65,9 @@ func kprobeProgramReconcile(t *testing.T, multiCondition bool) { Path: &bytecodePath, }, }, - FunctionNames: []string{functionName}, - Offset: uint64(offset), - RetProbe: retprobe, + FunctionName: functionName, + Offset: uint64(offset), + RetProbe: retprobe, }, } @@ -180,6 +180,6 @@ func TestKprobeProgramReconcile(t *testing.T) { kprobeProgramReconcile(t, false) } -func TestUpdateStatus(t *testing.T) { +func TestKprobeUpdateStatus(t *testing.T) { kprobeProgramReconcile(t, true) } diff --git a/bpfman-operator/controllers/bpfman-operator/uprobe-program_test.go b/bpfman-operator/controllers/bpfman-operator/uprobe-program_test.go index 2161c80e3..6a91105ac 100644 --- a/bpfman-operator/controllers/bpfman-operator/uprobe-program_test.go +++ b/bpfman-operator/controllers/bpfman-operator/uprobe-program_test.go @@ -64,7 +64,7 @@ func TestUprobeProgramReconcile(t *testing.T) { }, }, FunctionName: functionName, - Targets: []string{target}, + Target: target, Offset: uint64(offset), RetProbe: retprobe, }, diff --git a/bpfman-operator/hack/build-release-yamls.sh b/bpfman-operator/hack/build-release-yamls.sh index 35d9fb5b5..5ff02e6b9 100755 --- a/bpfman-operator/hack/build-release-yamls.sh +++ b/bpfman-operator/hack/build-release-yamls.sh @@ -20,7 +20,7 @@ set -o pipefail thisyear=`date +"%Y"` -mkdir -p release/ +mkdir -p release-v${VERSION}/ ## Location to install dependencies to LOCALBIN=$(pwd)/bin @@ -33,9 +33,9 @@ KUSTOMIZE=${LOCALBIN}/kustomize ## 1. bpfman CRD install # Make clean files with boilerplate -cat hack/boilerplate.sh.txt > release/bpfman-crds-install-v${VERSION}.yaml -sed -i "s/YEAR/$thisyear/g" release/bpfman-crds-install-v${VERSION}.yaml -cat << EOF >> release/bpfman-crds-install-v${VERSION}.yaml +cat hack/boilerplate.sh.txt > release-v${VERSION}/bpfman-crds-install.yaml +sed -i "s/YEAR/$thisyear/g" release-v${VERSION}/bpfman-crds-install.yaml +cat << EOF >> release-v${VERSION}/bpfman-crds-install.yaml # # bpfman Kubernetes API install # @@ -43,44 +43,56 @@ EOF for file in `ls config/crd/bases/bpfman*.yaml` do - echo "---" >> release/bpfman-crds-install-v${VERSION}.yaml - echo "#" >> release/bpfman-crds-install-v${VERSION}.yaml - echo "# $file" >> release/bpfman-crds-install-v${VERSION}.yaml - echo "#" >> release/bpfman-crds-install-v${VERSION}.yaml - cat $file >> release/bpfman-crds-install-v${VERSION}.yaml + echo "---" >> release-v${VERSION}/bpfman-crds-install.yaml + echo "#" >> release-v${VERSION}/bpfman-crds-install.yaml + echo "# $file" >> release-v${VERSION}/bpfman-crds-install.yaml + echo "#" >> release-v${VERSION}/bpfman-crds-install.yaml + cat $file >> release-v${VERSION}/bpfman-crds-install.yaml done -echo "Generated:" release/bpfman-crds-install-v${VERSION}.yaml +echo "Generated:" release-v${VERSION}/bpfman-crds-install.yaml ## 2. bpfman-operator install yaml $(cd ./config/bpfman-operator-deployment && ${KUSTOMIZE} edit set image quay.io/bpfman/bpfman-operator=quay.io/bpfman/bpfman-operator:v${VERSION}) -${KUSTOMIZE} build ./config/default > release/bpfman-operator-install-v${VERSION}.yaml +${KUSTOMIZE} build ./config/default > release-v${VERSION}/bpfman-operator-install.yaml ### replace configmap :latest images with :v${VERSION} -sed -i "s/quay.io\/bpfman\/bpfman-agent:latest/quay.io\/bpfman\/bpfman-agent:v${VERSION}/g" release/bpfman-operator-install-v${VERSION}.yaml -sed -i "s/quay.io\/bpfman\/bpfman:latest/quay.io\/bpfman\/bpfman:v${VERSION}/g" release/bpfman-operator-install-v${VERSION}.yaml +sed -i "s/quay.io\/bpfman\/bpfman-agent:latest/quay.io\/bpfman\/bpfman-agent:v${VERSION}/g" release-v${VERSION}/bpfman-operator-install.yaml +sed -i "s/quay.io\/bpfman\/bpfman:latest/quay.io\/bpfman\/bpfman:v${VERSION}/g" release-v${VERSION}/bpfman-operator-install.yaml -echo "Generated:" release/bpfman-operator-install-v${VERSION}.yaml +echo "Generated:" release-v${VERSION}/bpfman-operator-install.yaml ## 3. examples install yamls ### XDP -${KUSTOMIZE} build ../examples/config/v${VERSION}/go-xdp-counter > release/go-xdp-counter-install-v${VERSION}.yaml -echo "Generated:" go-xdp-counter-install-v${VERSION}.yaml +${KUSTOMIZE} build ../examples/config/v${VERSION}/go-xdp-counter > release-v${VERSION}/go-xdp-counter-install.yaml +echo "Generated:" release-v${VERSION}/go-xdp-counter-install.yaml ### TC -${KUSTOMIZE} build ../examples/config/v${VERSION}/go-tc-counter > release/go-tc-counter-install-v${VERSION}.yaml -echo "Generated:" go-tc-counter-install-v${VERSION}.yaml +${KUSTOMIZE} build ../examples/config/v${VERSION}/go-tc-counter > release-v${VERSION}/go-tc-counter-install.yaml +echo "Generated:" release-v${VERSION}/go-tc-counter-install.yaml ### TRACEPOINT -${KUSTOMIZE} build ../examples/config/v${VERSION}/go-tracepoint-counter > release/go-tracepoint-counter-install-v${VERSION}.yaml -echo "Generated:" go-tracepoint-counter-install-v${VERSION}.yaml +${KUSTOMIZE} build ../examples/config/v${VERSION}/go-tracepoint-counter > release-v${VERSION}/go-tracepoint-counter-install.yaml +echo "Generated:" release-v${VERSION}/go-tracepoint-counter-install.yaml +### UPROBE +${KUSTOMIZE} build ../examples/config/v${VERSION}/go-uprobe-counter > release-v${VERSION}/go-uprobe-counter-install.yaml +echo "Generated:" release-v${VERSION}/go-uprobe-counter-install.yaml +### KPROBE +${KUSTOMIZE} build ../examples/config/v${VERSION}/go-kprobe-counter > release-v${VERSION}/go-kprobe-counter-install.yaml +echo "Generated:" release-v${VERSION}/go-kprobe-counter-install.yaml ## 4. examples install yamls for OCP ### XDP -${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-xdp-counter > release/go-xdp-counter-install-ocp-v${VERSION}.yaml -echo "Generated:" go-xdp-counter-install-ocp-v${VERSION}.yaml +${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-xdp-counter > release-v${VERSION}/go-xdp-counter-install-ocp.yaml +echo "Generated:" release-v${VERSION}/go-xdp-counter-install-ocp.yaml ### TC -${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-tc-counter > release/go-tc-counter-install-ocp-v${VERSION}.yaml -echo "Generated:" go-tc-counter-install-ocp-v${VERSION}.yaml +${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-tc-counter > release-v${VERSION}/go-tc-counter-install-ocp.yaml +echo "Generated:" release-v${VERSION}/go-tc-counter-install-ocp.yaml ### TRACEPOINT -${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-tracepoint-counter > release/go-tracepoint-counter-install-ocp-v${VERSION}.yaml -echo "Generated:" go-tracepoint-counter-install-ocp-v${VERSION}.yaml +${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-tracepoint-counter > release-v${VERSION}/go-tracepoint-counter-install-ocp.yaml +echo "Generated:" release-v${VERSION}/go-tracepoint-counter-install-ocp.yaml +### UPROBE +${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-uprobe-counter > release-v${VERSION}/go-uprobe-counter-install-ocp.yaml +echo "Generated:" release-v${VERSION}/go-uprobe-counter-install-ocp.yaml +### KPROBE +${KUSTOMIZE} build ../examples/config/v${VERSION}-ocp/go-kprobe-counter > release-v${VERSION}/go-kprobe-counter-install-ocp.yaml +echo "Generated:" release-v${VERSION}/go-kprobe-counter-install-ocp.yaml diff --git a/bpfman-operator/hack/kubectl-bpfprogramconfigs b/bpfman-operator/hack/kubectl-bpfprogramconfigs deleted file mode 100755 index a73cf8f78..000000000 --- a/bpfman-operator/hack/kubectl-bpfprogramconfigs +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -kubectl get bpfprogramconfigs -o custom-columns="NAME":.metadata.name,"TYPE":.spec.type,"SECNAME":.spec.name,"STATUS":.status.conditions[-1:].type,"INTERFACE":.spec.attachpoint.networkmultiattach.interfaceselector.interface,"PRIORITY":.spec.attachpoint.networkmultiattach.priority,"DIRECTION":.spec.attachpoint.networkmultiattach.direction,"TRACEPOINT":.spec.attachpoint.singleattach.name diff --git a/bpfman-operator/hack/kubectl-bpfprograms b/bpfman-operator/hack/kubectl-bpfprograms deleted file mode 100755 index 72b6e6e48..000000000 --- a/bpfman-operator/hack/kubectl-bpfprograms +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -kubectl get bpfprograms -o jsonpath='{"\n"}{"STATUS\tREASON\t\tNAME\n IFACE\tPRI\tDIR\tTRACEPOINT\n\n"}{range .items[*]}{range .status.conditions[-1:]}{.type}{"\t"}{.reason}{end}{"\t"}{.metadata.name}{"\n"}{range .spec.programs.*}{" "}{.attachpoint.networkmultiattach.interfaceselector.interface}{"\t"}{.attachpoint.networkmultiattach.priority}{"\t"}{.attachpoint.networkmultiattach.direction}{"\t"}{.attachpoint.singleattach.name}{end}{"\n"}{"\n"}{end}' diff --git a/bpfman-operator/hack/nginx-deployment.yaml b/bpfman-operator/hack/nginx-deployment.yaml new file mode 100644 index 000000000..656ddc322 --- /dev/null +++ b/bpfman-operator/hack/nginx-deployment.yaml @@ -0,0 +1,19 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + selector: + matchLabels: + app: nginx + replicas: 2 # tells deployment to run 2 pods matching the template + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:latest + ports: + - containerPort: 80 diff --git a/bpfman-operator/internal/conn/conn.go b/bpfman-operator/internal/conn/conn.go index 2737e815e..2f28f257a 100644 --- a/bpfman-operator/internal/conn/conn.go +++ b/bpfman-operator/internal/conn/conn.go @@ -19,81 +19,20 @@ package conn import ( "context" "fmt" - "io" - "os" "github.com/bpfman/bpfman/bpfman-operator/internal" - toml "github.com/pelletier/go-toml" "google.golang.org/grpc" "google.golang.org/grpc/credentials" - ctrl "sigs.k8s.io/controller-runtime" ) -var log = ctrl.Log.WithName("bpfman-conn") +//var log = ctrl.Log.WithName("bpfman-conn") -type Endpoint struct { - Type string `toml:"type"` - Path string `toml:"path"` - Port uint16 `toml:"port"` - Enabled bool `toml:"enabled"` -} - -type Grpc struct { - Endpoints []Endpoint `toml:"endpoints"` -} - -type ConfigFileData struct { - Grpc Grpc `toml:"grpc"` -} - -func LoadConfig() ConfigFileData { - config := ConfigFileData{} - - log.Info("Reading...\n", "Default config path", internal.DefaultConfigPath) - file, err := os.Open(internal.DefaultConfigPath) +func CreateConnection(ctx context.Context, creds credentials.TransportCredentials) (*grpc.ClientConn, error) { + addr := fmt.Sprintf("unix://%s", internal.DefaultPath) + conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(creds), grpc.WithBlock()) if err != nil { - panic(err) - } - - b, err := io.ReadAll(file) - if err == nil { - err = toml.Unmarshal(b, &config) - if err != nil { - log.Info("Unmarshal failed: err %+v\n", err) - } - } else { - log.Info("Read config-path failed: err\n", "config-path", internal.DefaultConfigPath, "err", err) - } - - return config -} - -func CreateConnection(endpoints []Endpoint, ctx context.Context, creds credentials.TransportCredentials) (*grpc.ClientConn, error) { - var ( - addr string - ) - - // TODO(astoycos) this currently connects to the first valid endpoint - // rather then spawning multiple connections. This should be cleaned up - // and made explicitly configurable. - for _, e := range endpoints { - if !e.Enabled { - continue - } - - if e.Type == "tcp" { - addr = fmt.Sprintf("localhost:%d", e.Port) - } else if e.Type == "unix" { - addr = fmt.Sprintf("unix://%s", e.Path) - } - - conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(creds), grpc.WithBlock()) - if err != nil { - return nil, fmt.Errorf("unable to establish connection to %s: %w", addr, err) - } - - return conn, nil + return nil, fmt.Errorf("unable to establish connection to %s: %w", addr, err) } - return nil, fmt.Errorf("unable to establish connection, no valid endpoints") + return conn, nil } diff --git a/bpfman-operator/internal/constants.go b/bpfman-operator/internal/constants.go index 90953b824..a0d14b253 100644 --- a/bpfman-operator/internal/constants.go +++ b/bpfman-operator/internal/constants.go @@ -24,6 +24,10 @@ const ( TracepointProgramTracepoint = "bpfman.io.tracepointprogramcontroller/tracepoint" KprobeProgramFunction = "bpfman.io.kprobeprogramcontroller/function" UprobeProgramTarget = "bpfman.io.uprobeprogramcontroller/target" + UprobeContainerPid = "bpfman.io.uprobeprogramcontroller/containerpid" + UprobeNoContainersOnNode = "bpfman.io.uprobeprogramcontroller/nocontainersonnode" + FentryProgramFunction = "bpfman.io.fentryprogramcontroller/function" + FexitProgramFunction = "bpfman.io.fexitprogramcontroller/function" BpfProgramOwnerLabel = "bpfman.io/ownedByProgram" K8sHostLabel = "kubernetes.io/hostname" DiscoveredLabel = "bpfman.io/discoveredProgram" @@ -35,12 +39,13 @@ const ( BpfmanDsName = "bpfman-daemon" BpfmanConfigName = "bpfman-config" BpfmanCsiDriverName = "csi.bpfman.io" + BpfmanContainerName = "bpfman" + BpfmanAgentContainerName = "bpfman-agent" BpfmanDaemonManifestPath = "./config/bpfman-deployment/daemonset.yaml" BpfmanCsiDriverPath = "./config/bpfman-deployment/csidriverinfo.yaml" BpfmanMapFs = "/run/bpfman/fs/maps" - DefaultConfigPath = "/etc/bpfman/bpfman.toml" DefaultType = "tcp" - DefaultPath = "/run/bpfman/bpfman.sock" + DefaultPath = "/run/bpfman-sock/bpfman.sock" DefaultPort = 50051 DefaultEnabled = true ) @@ -65,9 +70,15 @@ const ( // KprobeProgramControllerFinalizer is the finalizer that holds a Kprobe // BpfProgram object from deletion until cleanup can be performed. KprobeProgramControllerFinalizer = "bpfman.io.kprobeprogramcontroller/finalizer" - // KprobeProgramControllerFinalizer is the finalizer that holds a Uprobe + // UprobeProgramControllerFinalizer is the finalizer that holds a Uprobe // BpfProgram object from deletion until cleanup can be performed. UprobeProgramControllerFinalizer = "bpfman.io.uprobeprogramcontroller/finalizer" + // FentryProgramControllerFinalizer is the finalizer that holds a Fentry + // BpfProgram object from deletion until cleanup can be performed. + FentryProgramControllerFinalizer = "bpfman.io.fentryprogramcontroller/finalizer" + // FexitProgramControllerFinalizer is the finalizer that holds a Fexit + // BpfProgram object from deletion until cleanup can be performed. + FexitProgramControllerFinalizer = "bpfman.io.fexitprogramcontroller/finalizer" ) // Must match the kernel's `bpf_prog_type` enum. @@ -127,6 +138,10 @@ func FromString(p string) (*ProgramType, error) { programType = Kprobe case "uprobe": programType = Kprobe + case "fentry": + programType = Tracing + case "fexit": + programType = Tracing default: return nil, fmt.Errorf("unknown program type: %s", p) } diff --git a/bpfman-operator/pkg/client/clientset/versioned/clientset.go b/bpfman-operator/pkg/client/clientset/versioned/clientset.go index e20c15d71..9cfe012e5 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/clientset.go +++ b/bpfman-operator/pkg/client/clientset/versioned/clientset.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package versioned @@ -32,7 +33,8 @@ type Interface interface { BpfmanV1alpha1() bpfmanv1alpha1.BpfmanV1alpha1Interface } -// Clientset contains the clients for groups. +// Clientset contains the clients for groups. Each group has exactly one +// version included in a Clientset. type Clientset struct { *discovery.DiscoveryClient bpfmanV1alpha1 *bpfmanv1alpha1.BpfmanV1alpha1Client diff --git a/bpfman-operator/pkg/client/clientset/versioned/doc.go b/bpfman-operator/pkg/client/clientset/versioned/doc.go index b7179e900..ce60fa421 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/doc.go +++ b/bpfman-operator/pkg/client/clientset/versioned/doc.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. // This package has the automatically generated clientset. diff --git a/bpfman-operator/pkg/client/clientset/versioned/fake/clientset_generated.go b/bpfman-operator/pkg/client/clientset/versioned/fake/clientset_generated.go index bd7a1c584..ecad40626 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/bpfman-operator/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package fake diff --git a/bpfman-operator/pkg/client/clientset/versioned/fake/doc.go b/bpfman-operator/pkg/client/clientset/versioned/fake/doc.go index 1d2134ad6..f579368d7 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/fake/doc.go +++ b/bpfman-operator/pkg/client/clientset/versioned/fake/doc.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. // This package has the automatically generated fake clientset. diff --git a/bpfman-operator/pkg/client/clientset/versioned/fake/register.go b/bpfman-operator/pkg/client/clientset/versioned/fake/register.go index 320e47d99..d3dfad540 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/fake/register.go +++ b/bpfman-operator/pkg/client/clientset/versioned/fake/register.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package fake diff --git a/bpfman-operator/pkg/client/clientset/versioned/scheme/doc.go b/bpfman-operator/pkg/client/clientset/versioned/scheme/doc.go index 19c6633f8..04ec2e1bf 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/scheme/doc.go +++ b/bpfman-operator/pkg/client/clientset/versioned/scheme/doc.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. // This package contains the scheme of the automatically generated clientset. diff --git a/bpfman-operator/pkg/client/clientset/versioned/scheme/register.go b/bpfman-operator/pkg/client/clientset/versioned/scheme/register.go index de8bf4d87..7d109c14f 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/scheme/register.go +++ b/bpfman-operator/pkg/client/clientset/versioned/scheme/register.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package scheme diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go index 7df4ac867..2934f7b01 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package v1alpha1 @@ -28,6 +29,8 @@ import ( type BpfmanV1alpha1Interface interface { RESTClient() rest.Interface BpfProgramsGetter + FentryProgramsGetter + FexitProgramsGetter KprobeProgramsGetter TcProgramsGetter TracepointProgramsGetter @@ -44,6 +47,14 @@ func (c *BpfmanV1alpha1Client) BpfPrograms() BpfProgramInterface { return newBpfPrograms(c) } +func (c *BpfmanV1alpha1Client) FentryPrograms() FentryProgramInterface { + return newFentryPrograms(c) +} + +func (c *BpfmanV1alpha1Client) FexitPrograms() FexitProgramInterface { + return newFexitPrograms(c) +} + func (c *BpfmanV1alpha1Client) KprobePrograms() KprobeProgramInterface { return newKprobePrograms(c) } diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/bpfprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/bpfprogram.go index b3a94a48d..382a6fa13 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/bpfprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/bpfprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/doc.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/doc.go index a47d86894..d7981c096 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/doc.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/doc.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. // This package has the automatically generated typed clients. diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go index fe81f65a2..872443930 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. // Package fake has the automatically generated clients. diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go index 065a2c6f4..bea6d7271 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package fake @@ -31,6 +32,14 @@ func (c *FakeBpfmanV1alpha1) BpfPrograms() v1alpha1.BpfProgramInterface { return &FakeBpfPrograms{c} } +func (c *FakeBpfmanV1alpha1) FentryPrograms() v1alpha1.FentryProgramInterface { + return &FakeFentryPrograms{c} +} + +func (c *FakeBpfmanV1alpha1) FexitPrograms() v1alpha1.FexitProgramInterface { + return &FakeFexitPrograms{c} +} + func (c *FakeBpfmanV1alpha1) KprobePrograms() v1alpha1.KprobeProgramInterface { return &FakeKprobePrograms{c} } diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_bpfprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_bpfprogram.go index 10e840857..fc762e31b 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_bpfprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_bpfprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package fake diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fentryprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fentryprogram.go new file mode 100644 index 000000000..932db226f --- /dev/null +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fentryprogram.go @@ -0,0 +1,133 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeFentryPrograms implements FentryProgramInterface +type FakeFentryPrograms struct { + Fake *FakeBpfmanV1alpha1 +} + +var fentryprogramsResource = schema.GroupVersionResource{Group: "bpfman.io", Version: "v1alpha1", Resource: "fentryprograms"} + +var fentryprogramsKind = schema.GroupVersionKind{Group: "bpfman.io", Version: "v1alpha1", Kind: "FentryProgram"} + +// Get takes name of the fentryProgram, and returns the corresponding fentryProgram object, and an error if there is any. +func (c *FakeFentryPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.FentryProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(fentryprogramsResource, name), &v1alpha1.FentryProgram{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.FentryProgram), err +} + +// List takes label and field selectors, and returns the list of FentryPrograms that match those selectors. +func (c *FakeFentryPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.FentryProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(fentryprogramsResource, fentryprogramsKind, opts), &v1alpha1.FentryProgramList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.FentryProgramList{ListMeta: obj.(*v1alpha1.FentryProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.FentryProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested fentryPrograms. +func (c *FakeFentryPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(fentryprogramsResource, opts)) +} + +// Create takes the representation of a fentryProgram and creates it. Returns the server's representation of the fentryProgram, and an error, if there is any. +func (c *FakeFentryPrograms) Create(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.CreateOptions) (result *v1alpha1.FentryProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(fentryprogramsResource, fentryProgram), &v1alpha1.FentryProgram{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.FentryProgram), err +} + +// Update takes the representation of a fentryProgram and updates it. Returns the server's representation of the fentryProgram, and an error, if there is any. +func (c *FakeFentryPrograms) Update(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (result *v1alpha1.FentryProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(fentryprogramsResource, fentryProgram), &v1alpha1.FentryProgram{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.FentryProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeFentryPrograms) UpdateStatus(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (*v1alpha1.FentryProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(fentryprogramsResource, "status", fentryProgram), &v1alpha1.FentryProgram{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.FentryProgram), err +} + +// Delete takes name of the fentryProgram and deletes it. Returns an error if one occurs. +func (c *FakeFentryPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(fentryprogramsResource, name, opts), &v1alpha1.FentryProgram{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeFentryPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(fentryprogramsResource, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.FentryProgramList{}) + return err +} + +// Patch applies the patch and returns the patched fentryProgram. +func (c *FakeFentryPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.FentryProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(fentryprogramsResource, name, pt, data, subresources...), &v1alpha1.FentryProgram{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.FentryProgram), err +} diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fexitprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fexitprogram.go new file mode 100644 index 000000000..81cb152bd --- /dev/null +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_fexitprogram.go @@ -0,0 +1,133 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeFexitPrograms implements FexitProgramInterface +type FakeFexitPrograms struct { + Fake *FakeBpfmanV1alpha1 +} + +var fexitprogramsResource = schema.GroupVersionResource{Group: "bpfman.io", Version: "v1alpha1", Resource: "fexitprograms"} + +var fexitprogramsKind = schema.GroupVersionKind{Group: "bpfman.io", Version: "v1alpha1", Kind: "FexitProgram"} + +// Get takes name of the fexitProgram, and returns the corresponding fexitProgram object, and an error if there is any. +func (c *FakeFexitPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.FexitProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(fexitprogramsResource, name), &v1alpha1.FexitProgram{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.FexitProgram), err +} + +// List takes label and field selectors, and returns the list of FexitPrograms that match those selectors. +func (c *FakeFexitPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.FexitProgramList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(fexitprogramsResource, fexitprogramsKind, opts), &v1alpha1.FexitProgramList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.FexitProgramList{ListMeta: obj.(*v1alpha1.FexitProgramList).ListMeta} + for _, item := range obj.(*v1alpha1.FexitProgramList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested fexitPrograms. +func (c *FakeFexitPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(fexitprogramsResource, opts)) +} + +// Create takes the representation of a fexitProgram and creates it. Returns the server's representation of the fexitProgram, and an error, if there is any. +func (c *FakeFexitPrograms) Create(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.CreateOptions) (result *v1alpha1.FexitProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(fexitprogramsResource, fexitProgram), &v1alpha1.FexitProgram{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.FexitProgram), err +} + +// Update takes the representation of a fexitProgram and updates it. Returns the server's representation of the fexitProgram, and an error, if there is any. +func (c *FakeFexitPrograms) Update(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (result *v1alpha1.FexitProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(fexitprogramsResource, fexitProgram), &v1alpha1.FexitProgram{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.FexitProgram), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeFexitPrograms) UpdateStatus(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (*v1alpha1.FexitProgram, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(fexitprogramsResource, "status", fexitProgram), &v1alpha1.FexitProgram{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.FexitProgram), err +} + +// Delete takes name of the fexitProgram and deletes it. Returns an error if one occurs. +func (c *FakeFexitPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(fexitprogramsResource, name, opts), &v1alpha1.FexitProgram{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeFexitPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(fexitprogramsResource, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.FexitProgramList{}) + return err +} + +// Patch applies the patch and returns the patched fexitProgram. +func (c *FakeFexitPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.FexitProgram, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(fexitprogramsResource, name, pt, data, subresources...), &v1alpha1.FexitProgram{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.FexitProgram), err +} diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_kprobeprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_kprobeprogram.go index f68c42b47..759f9dc47 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_kprobeprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_kprobeprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package fake diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tcprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tcprogram.go index fab3b1caa..e7206e53f 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tcprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tcprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package fake diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tracepointprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tracepointprogram.go index 24361afed..0c9c2af20 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tracepointprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_tracepointprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package fake diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_uprobeprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_uprobeprogram.go index 2d21bfe19..7133cf804 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_uprobeprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_uprobeprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package fake diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_xdpprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_xdpprogram.go index 37ff9a11b..68d94aac6 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_xdpprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_xdpprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package fake diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fentryprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fentryprogram.go new file mode 100644 index 000000000..b66f6a3e8 --- /dev/null +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fentryprogram.go @@ -0,0 +1,184 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman/bpfman-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// FentryProgramsGetter has a method to return a FentryProgramInterface. +// A group's client should implement this interface. +type FentryProgramsGetter interface { + FentryPrograms() FentryProgramInterface +} + +// FentryProgramInterface has methods to work with FentryProgram resources. +type FentryProgramInterface interface { + Create(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.CreateOptions) (*v1alpha1.FentryProgram, error) + Update(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (*v1alpha1.FentryProgram, error) + UpdateStatus(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (*v1alpha1.FentryProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.FentryProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.FentryProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.FentryProgram, err error) + FentryProgramExpansion +} + +// fentryPrograms implements FentryProgramInterface +type fentryPrograms struct { + client rest.Interface +} + +// newFentryPrograms returns a FentryPrograms +func newFentryPrograms(c *BpfmanV1alpha1Client) *fentryPrograms { + return &fentryPrograms{ + client: c.RESTClient(), + } +} + +// Get takes name of the fentryProgram, and returns the corresponding fentryProgram object, and an error if there is any. +func (c *fentryPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.FentryProgram, err error) { + result = &v1alpha1.FentryProgram{} + err = c.client.Get(). + Resource("fentryprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of FentryPrograms that match those selectors. +func (c *fentryPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.FentryProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.FentryProgramList{} + err = c.client.Get(). + Resource("fentryprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested fentryPrograms. +func (c *fentryPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Resource("fentryprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a fentryProgram and creates it. Returns the server's representation of the fentryProgram, and an error, if there is any. +func (c *fentryPrograms) Create(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.CreateOptions) (result *v1alpha1.FentryProgram, err error) { + result = &v1alpha1.FentryProgram{} + err = c.client.Post(). + Resource("fentryprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(fentryProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a fentryProgram and updates it. Returns the server's representation of the fentryProgram, and an error, if there is any. +func (c *fentryPrograms) Update(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (result *v1alpha1.FentryProgram, err error) { + result = &v1alpha1.FentryProgram{} + err = c.client.Put(). + Resource("fentryprograms"). + Name(fentryProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(fentryProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *fentryPrograms) UpdateStatus(ctx context.Context, fentryProgram *v1alpha1.FentryProgram, opts v1.UpdateOptions) (result *v1alpha1.FentryProgram, err error) { + result = &v1alpha1.FentryProgram{} + err = c.client.Put(). + Resource("fentryprograms"). + Name(fentryProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(fentryProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the fentryProgram and deletes it. Returns an error if one occurs. +func (c *fentryPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Resource("fentryprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *fentryPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Resource("fentryprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched fentryProgram. +func (c *fentryPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.FentryProgram, err error) { + result = &v1alpha1.FentryProgram{} + err = c.client.Patch(pt). + Resource("fentryprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fexitprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fexitprogram.go new file mode 100644 index 000000000..f9bbcad63 --- /dev/null +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/fexitprogram.go @@ -0,0 +1,184 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + scheme "github.com/bpfman/bpfman/bpfman-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// FexitProgramsGetter has a method to return a FexitProgramInterface. +// A group's client should implement this interface. +type FexitProgramsGetter interface { + FexitPrograms() FexitProgramInterface +} + +// FexitProgramInterface has methods to work with FexitProgram resources. +type FexitProgramInterface interface { + Create(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.CreateOptions) (*v1alpha1.FexitProgram, error) + Update(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (*v1alpha1.FexitProgram, error) + UpdateStatus(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (*v1alpha1.FexitProgram, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.FexitProgram, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.FexitProgramList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.FexitProgram, err error) + FexitProgramExpansion +} + +// fexitPrograms implements FexitProgramInterface +type fexitPrograms struct { + client rest.Interface +} + +// newFexitPrograms returns a FexitPrograms +func newFexitPrograms(c *BpfmanV1alpha1Client) *fexitPrograms { + return &fexitPrograms{ + client: c.RESTClient(), + } +} + +// Get takes name of the fexitProgram, and returns the corresponding fexitProgram object, and an error if there is any. +func (c *fexitPrograms) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.FexitProgram, err error) { + result = &v1alpha1.FexitProgram{} + err = c.client.Get(). + Resource("fexitprograms"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of FexitPrograms that match those selectors. +func (c *fexitPrograms) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.FexitProgramList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.FexitProgramList{} + err = c.client.Get(). + Resource("fexitprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested fexitPrograms. +func (c *fexitPrograms) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Resource("fexitprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a fexitProgram and creates it. Returns the server's representation of the fexitProgram, and an error, if there is any. +func (c *fexitPrograms) Create(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.CreateOptions) (result *v1alpha1.FexitProgram, err error) { + result = &v1alpha1.FexitProgram{} + err = c.client.Post(). + Resource("fexitprograms"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(fexitProgram). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a fexitProgram and updates it. Returns the server's representation of the fexitProgram, and an error, if there is any. +func (c *fexitPrograms) Update(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (result *v1alpha1.FexitProgram, err error) { + result = &v1alpha1.FexitProgram{} + err = c.client.Put(). + Resource("fexitprograms"). + Name(fexitProgram.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(fexitProgram). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *fexitPrograms) UpdateStatus(ctx context.Context, fexitProgram *v1alpha1.FexitProgram, opts v1.UpdateOptions) (result *v1alpha1.FexitProgram, err error) { + result = &v1alpha1.FexitProgram{} + err = c.client.Put(). + Resource("fexitprograms"). + Name(fexitProgram.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(fexitProgram). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the fexitProgram and deletes it. Returns an error if one occurs. +func (c *fexitPrograms) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Resource("fexitprograms"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *fexitPrograms) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Resource("fexitprograms"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched fexitProgram. +func (c *fexitPrograms) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.FexitProgram, err error) { + result = &v1alpha1.FexitProgram{} + err = c.client.Patch(pt). + Resource("fexitprograms"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go index 95f0e6483..ff5a8ed4d 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go @@ -13,12 +13,17 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package v1alpha1 type BpfProgramExpansion interface{} +type FentryProgramExpansion interface{} + +type FexitProgramExpansion interface{} + type KprobeProgramExpansion interface{} type TcProgramExpansion interface{} diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/kprobeprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/kprobeprogram.go index 5fbdcfe59..0054fa652 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/kprobeprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/kprobeprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tcprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tcprogram.go index 5baa971a4..b27a6e988 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tcprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tcprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tracepointprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tracepointprogram.go index e65fe8997..fe92947dc 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tracepointprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/tracepointprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/uprobeprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/uprobeprogram.go index 4752bc6fe..b7350003a 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/uprobeprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/uprobeprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/xdpprogram.go b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/xdpprogram.go index 7032681cd..4304fef8f 100644 --- a/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/xdpprogram.go +++ b/bpfman-operator/pkg/client/clientset/versioned/typed/apis/v1alpha1/xdpprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by client-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/interface.go b/bpfman-operator/pkg/client/informers/externalversions/apis/interface.go index 60f381797..398b5c1e5 100644 --- a/bpfman-operator/pkg/client/informers/externalversions/apis/interface.go +++ b/bpfman-operator/pkg/client/informers/externalversions/apis/interface.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by informer-gen. DO NOT EDIT. package apis diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/bpfprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/bpfprogram.go index ef19de4d2..73cc6435e 100644 --- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/bpfprogram.go +++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/bpfprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by informer-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fentryprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fentryprogram.go new file mode 100644 index 000000000..0cfeab682 --- /dev/null +++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fentryprogram.go @@ -0,0 +1,89 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + versioned "github.com/bpfman/bpfman/bpfman-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/bpfman/bpfman/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/pkg/client/listers/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// FentryProgramInformer provides access to a shared informer and lister for +// FentryPrograms. +type FentryProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.FentryProgramLister +} + +type fentryProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewFentryProgramInformer constructs a new informer for FentryProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFentryProgramInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredFentryProgramInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredFentryProgramInformer constructs a new informer for FentryProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredFentryProgramInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().FentryPrograms().List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().FentryPrograms().Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.FentryProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *fentryProgramInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredFentryProgramInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *fentryProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.FentryProgram{}, f.defaultInformer) +} + +func (f *fentryProgramInformer) Lister() v1alpha1.FentryProgramLister { + return v1alpha1.NewFentryProgramLister(f.Informer().GetIndexer()) +} diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fexitprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fexitprogram.go new file mode 100644 index 000000000..03af8df1e --- /dev/null +++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/fexitprogram.go @@ -0,0 +1,89 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + apisv1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + versioned "github.com/bpfman/bpfman/bpfman-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/bpfman/bpfman/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/pkg/client/listers/apis/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// FexitProgramInformer provides access to a shared informer and lister for +// FexitPrograms. +type FexitProgramInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.FexitProgramLister +} + +type fexitProgramInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewFexitProgramInformer constructs a new informer for FexitProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFexitProgramInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredFexitProgramInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredFexitProgramInformer constructs a new informer for FexitProgram type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredFexitProgramInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().FexitPrograms().List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.BpfmanV1alpha1().FexitPrograms().Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.FexitProgram{}, + resyncPeriod, + indexers, + ) +} + +func (f *fexitProgramInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredFexitProgramInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *fexitProgramInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.FexitProgram{}, f.defaultInformer) +} + +func (f *fexitProgramInformer) Lister() v1alpha1.FexitProgramLister { + return v1alpha1.NewFexitProgramLister(f.Informer().GetIndexer()) +} diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/interface.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/interface.go index 2a020b4aa..b68bddfdb 100644 --- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/interface.go +++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/interface.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by informer-gen. DO NOT EDIT. package v1alpha1 @@ -25,6 +26,10 @@ import ( type Interface interface { // BpfPrograms returns a BpfProgramInformer. BpfPrograms() BpfProgramInformer + // FentryPrograms returns a FentryProgramInformer. + FentryPrograms() FentryProgramInformer + // FexitPrograms returns a FexitProgramInformer. + FexitPrograms() FexitProgramInformer // KprobePrograms returns a KprobeProgramInformer. KprobePrograms() KprobeProgramInformer // TcPrograms returns a TcProgramInformer. @@ -53,6 +58,16 @@ func (v *version) BpfPrograms() BpfProgramInformer { return &bpfProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// FentryPrograms returns a FentryProgramInformer. +func (v *version) FentryPrograms() FentryProgramInformer { + return &fentryProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + +// FexitPrograms returns a FexitProgramInformer. +func (v *version) FexitPrograms() FexitProgramInformer { + return &fexitProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + // KprobePrograms returns a KprobeProgramInformer. func (v *version) KprobePrograms() KprobeProgramInformer { return &kprobeProgramInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/kprobeprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/kprobeprogram.go index f371908b9..56b6b6188 100644 --- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/kprobeprogram.go +++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/kprobeprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by informer-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tcprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tcprogram.go index 9dd1907ed..61e56aa2b 100644 --- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tcprogram.go +++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tcprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by informer-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tracepointprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tracepointprogram.go index 6c3e4cb95..f60a34312 100644 --- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tracepointprogram.go +++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/tracepointprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by informer-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/uprobeprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/uprobeprogram.go index 430f2a6fa..f36c25d6a 100644 --- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/uprobeprogram.go +++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/uprobeprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by informer-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/xdpprogram.go b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/xdpprogram.go index f3bc9e8a2..36ea795b9 100644 --- a/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/xdpprogram.go +++ b/bpfman-operator/pkg/client/informers/externalversions/apis/v1alpha1/xdpprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by informer-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/informers/externalversions/factory.go b/bpfman-operator/pkg/client/informers/externalversions/factory.go index 04965035d..aa63614c3 100644 --- a/bpfman-operator/pkg/client/informers/externalversions/factory.go +++ b/bpfman-operator/pkg/client/informers/externalversions/factory.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by informer-gen. DO NOT EDIT. package externalversions @@ -46,11 +47,6 @@ type sharedInformerFactory struct { // startedInformers is used for tracking which informers have been started. // This allows Start() to be called multiple times safely. startedInformers map[reflect.Type]bool - // wg tracks how many goroutines were started. - wg sync.WaitGroup - // shuttingDown is true when Shutdown has been called. It may still be running - // because it needs to wait for goroutines. - shuttingDown bool } // WithCustomResyncConfig sets a custom resync period for the specified informer types. @@ -111,39 +107,20 @@ func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResy return factory } +// Start initializes all requested informers. func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { f.lock.Lock() defer f.lock.Unlock() - if f.shuttingDown { - return - } - for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - f.wg.Add(1) - // We need a new variable in each loop iteration, - // otherwise the goroutine would use the loop variable - // and that keeps changing. - informer := informer - go func() { - defer f.wg.Done() - informer.Run(stopCh) - }() + go informer.Run(stopCh) f.startedInformers[informerType] = true } } } -func (f *sharedInformerFactory) Shutdown() { - f.lock.Lock() - f.shuttingDown = true - f.lock.Unlock() - - // Will return immediately if there is nothing to wait for. - f.wg.Wait() -} - +// WaitForCacheSync waits for all started informers' cache were synced. func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { informers := func() map[reflect.Type]cache.SharedIndexInformer { f.lock.Lock() @@ -190,57 +167,10 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal // SharedInformerFactory provides shared informers for resources in all known // API group versions. -// -// It is typically used like this: -// -// ctx, cancel := context.Background() -// defer cancel() -// factory := NewSharedInformerFactory(client, resyncPeriod) -// defer factory.WaitForStop() // Returns immediately if nothing was started. -// genericInformer := factory.ForResource(resource) -// typedInformer := factory.SomeAPIGroup().V1().SomeType() -// factory.Start(ctx.Done()) // Start processing these informers. -// synced := factory.WaitForCacheSync(ctx.Done()) -// for v, ok := range synced { -// if !ok { -// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) -// return -// } -// } -// -// // Creating informers can also be created after Start, but then -// // Start must be called again: -// anotherGenericInformer := factory.ForResource(resource) -// factory.Start(ctx.Done()) type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory - - // Start initializes all requested informers. They are handled in goroutines - // which run until the stop channel gets closed. - Start(stopCh <-chan struct{}) - - // Shutdown marks a factory as shutting down. At that point no new - // informers can be started anymore and Start will return without - // doing anything. - // - // In addition, Shutdown blocks until all goroutines have terminated. For that - // to happen, the close channel(s) that they were started with must be closed, - // either before Shutdown gets called or while it is waiting. - // - // Shutdown may be called multiple times, even concurrently. All such calls will - // block until all goroutines have terminated. - Shutdown() - - // WaitForCacheSync blocks until all started informers' caches were synced - // or the stop channel gets closed. - WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool - - // ForResource gives generic access to a shared informer of the matching type. ForResource(resource schema.GroupVersionResource) (GenericInformer, error) - - // InternalInformerFor returns the SharedIndexInformer for obj using an internal - // client. - InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool Bpfman() apis.Interface } diff --git a/bpfman-operator/pkg/client/informers/externalversions/generic.go b/bpfman-operator/pkg/client/informers/externalversions/generic.go index 4bcf6d181..5514655e8 100644 --- a/bpfman-operator/pkg/client/informers/externalversions/generic.go +++ b/bpfman-operator/pkg/client/informers/externalversions/generic.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by informer-gen. DO NOT EDIT. package externalversions @@ -54,6 +55,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=bpfman.io, Version=v1alpha1 case v1alpha1.SchemeGroupVersion.WithResource("bpfprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().BpfPrograms().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("fentryprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().FentryPrograms().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("fexitprograms"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().FexitPrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("kprobeprograms"): return &genericInformer{resource: resource.GroupResource(), informer: f.Bpfman().V1alpha1().KprobePrograms().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("tcprograms"): diff --git a/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go b/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go index a5db4b06c..56f5b91d6 100644 --- a/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/bpfman-operator/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by informer-gen. DO NOT EDIT. package internalinterfaces diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/bpfprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/bpfprogram.go index 0a613e747..877627d99 100644 --- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/bpfprogram.go +++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/bpfprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by lister-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/expansion_generated.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/expansion_generated.go index 68073a95d..5ee0564f6 100644 --- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/expansion_generated.go +++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/expansion_generated.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by lister-gen. DO NOT EDIT. package v1alpha1 @@ -21,6 +22,14 @@ package v1alpha1 // BpfProgramLister. type BpfProgramListerExpansion interface{} +// FentryProgramListerExpansion allows custom methods to be added to +// FentryProgramLister. +type FentryProgramListerExpansion interface{} + +// FexitProgramListerExpansion allows custom methods to be added to +// FexitProgramLister. +type FexitProgramListerExpansion interface{} + // KprobeProgramListerExpansion allows custom methods to be added to // KprobeProgramLister. type KprobeProgramListerExpansion interface{} diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/fentryprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/fentryprogram.go new file mode 100644 index 000000000..dc505be7c --- /dev/null +++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/fentryprogram.go @@ -0,0 +1,68 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// FentryProgramLister helps list FentryPrograms. +// All objects returned here must be treated as read-only. +type FentryProgramLister interface { + // List lists all FentryPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.FentryProgram, err error) + // Get retrieves the FentryProgram from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.FentryProgram, error) + FentryProgramListerExpansion +} + +// fentryProgramLister implements the FentryProgramLister interface. +type fentryProgramLister struct { + indexer cache.Indexer +} + +// NewFentryProgramLister returns a new FentryProgramLister. +func NewFentryProgramLister(indexer cache.Indexer) FentryProgramLister { + return &fentryProgramLister{indexer: indexer} +} + +// List lists all FentryPrograms in the indexer. +func (s *fentryProgramLister) List(selector labels.Selector) (ret []*v1alpha1.FentryProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.FentryProgram)) + }) + return ret, err +} + +// Get retrieves the FentryProgram from the index for a given name. +func (s *fentryProgramLister) Get(name string) (*v1alpha1.FentryProgram, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("fentryprogram"), name) + } + return obj.(*v1alpha1.FentryProgram), nil +} diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/fexitprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/fexitprogram.go new file mode 100644 index 000000000..a773e71ba --- /dev/null +++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/fexitprogram.go @@ -0,0 +1,68 @@ +/* +Copyright 2023 The bpfman Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/bpfman/bpfman/bpfman-operator/apis/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// FexitProgramLister helps list FexitPrograms. +// All objects returned here must be treated as read-only. +type FexitProgramLister interface { + // List lists all FexitPrograms in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.FexitProgram, err error) + // Get retrieves the FexitProgram from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.FexitProgram, error) + FexitProgramListerExpansion +} + +// fexitProgramLister implements the FexitProgramLister interface. +type fexitProgramLister struct { + indexer cache.Indexer +} + +// NewFexitProgramLister returns a new FexitProgramLister. +func NewFexitProgramLister(indexer cache.Indexer) FexitProgramLister { + return &fexitProgramLister{indexer: indexer} +} + +// List lists all FexitPrograms in the indexer. +func (s *fexitProgramLister) List(selector labels.Selector) (ret []*v1alpha1.FexitProgram, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.FexitProgram)) + }) + return ret, err +} + +// Get retrieves the FexitProgram from the index for a given name. +func (s *fexitProgramLister) Get(name string) (*v1alpha1.FexitProgram, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("fexitprogram"), name) + } + return obj.(*v1alpha1.FexitProgram), nil +} diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/kprobeprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/kprobeprogram.go index 65efa0070..5ae1d443f 100644 --- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/kprobeprogram.go +++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/kprobeprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by lister-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/tcprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/tcprogram.go index 905c3c279..b6ec8190e 100644 --- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/tcprogram.go +++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/tcprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by lister-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/tracepointprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/tracepointprogram.go index dc7ec6107..224a175d4 100644 --- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/tracepointprogram.go +++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/tracepointprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by lister-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/uprobeprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/uprobeprogram.go index 78af46298..6f0eb828b 100644 --- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/uprobeprogram.go +++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/uprobeprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by lister-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/client/listers/apis/v1alpha1/xdpprogram.go b/bpfman-operator/pkg/client/listers/apis/v1alpha1/xdpprogram.go index d485a39aa..995985eb7 100644 --- a/bpfman-operator/pkg/client/listers/apis/v1alpha1/xdpprogram.go +++ b/bpfman-operator/pkg/client/listers/apis/v1alpha1/xdpprogram.go @@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + // Code generated by lister-gen. DO NOT EDIT. package v1alpha1 diff --git a/bpfman-operator/pkg/helpers/helpers.go b/bpfman-operator/pkg/helpers/helpers.go index c58be99f5..7a51e979b 100644 --- a/bpfman-operator/pkg/helpers/helpers.go +++ b/bpfman-operator/pkg/helpers/helpers.go @@ -46,6 +46,7 @@ const ( Tc ProgramType = 3 Tracepoint ProgramType = 5 Xdp ProgramType = 6 + Tracing ProgramType = 26 ) func (p ProgramType) Uint32() *uint32 { @@ -64,6 +65,8 @@ func FromString(p string) (*ProgramType, error) { programType = Xdp case "tracepoint": programType = Tracepoint + case "tracing": + programType = Tracing default: return nil, fmt.Errorf("unknown program type: %s", p) } @@ -81,6 +84,8 @@ func (p ProgramType) String() string { return "xdp" case Tracepoint: return "tracepoint" + case Tracing: + return "tracing" default: return "" } @@ -163,7 +168,28 @@ func isKprobebpfmanProgLoaded(c *bpfmanclientset.Clientset, progConfName string) progLoaded, condType := isProgLoaded(&bpfProgConfig.Status.Conditions) if !progLoaded { - log.Info("kprobProgram: %s not ready with condition: %s, waiting until timeout", progConfName, condType) + log.Info("kprobeProgram: %s not ready with condition: %s, waiting until timeout", progConfName, condType) + return false, nil + } + + return true, nil + } +} + +func isFentrybpfmanProgLoaded(c *bpfmanclientset.Clientset, progConfName string) wait.ConditionFunc { + ctx := context.Background() + + return func() (bool, error) { + log.Info(".") // progress bar! + bpfProgConfig, err := c.BpfmanV1alpha1().FentryPrograms().Get(ctx, progConfName, metav1.GetOptions{}) + if err != nil { + return false, err + } + + progLoaded, condType := isProgLoaded(&bpfProgConfig.Status.Conditions) + + if !progLoaded { + log.Info("fentryProgram: %s not ready with condition: %s, waiting until timeout", progConfName, condType) return false, nil } @@ -246,10 +272,16 @@ func WaitForBpfProgConfLoad(c *bpfmanclientset.Clientset, progName string, timeo return wait.PollImmediate(time.Second, timeout, isXdpbpfmanProgLoaded(c, progName)) case Tracepoint: return wait.PollImmediate(time.Second, timeout, isTracepointbpfmanProgLoaded(c, progName)) + case Tracing: + return wait.PollImmediate(time.Second, timeout, isFentrybpfmanProgLoaded(c, progName)) // TODO: case Uprobe: not covered. Since Uprobe has the same ProgramType as // Kprobe, we need a different way to distinguish them. Options include // creating an internal ProgramType for Uprobe or using a different // identifier such as the string representation of the program type. + // TODO: case Fexit: not covered. Since Fexit has the same ProgramType as + // Fentry, we need a different way to distinguish them. Options include + // creating an internal ProgramType for Fexit or using a different + // identifier such as the string representation of the program type. default: return fmt.Errorf("unknown bpf program type: %s", progType) } diff --git a/bpfman-operator/test/integration/kprobe_test.go b/bpfman-operator/test/integration/kprobe_test.go new file mode 100644 index 000000000..f80ab2e76 --- /dev/null +++ b/bpfman-operator/test/integration/kprobe_test.go @@ -0,0 +1,70 @@ +//go:build integration_tests +// +build integration_tests + +package integration + +import ( + "bytes" + "context" + "io" + "regexp" + "strconv" + "testing" + "time" + + "github.com/kong/kubernetes-testing-framework/pkg/clusters" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + kprobeGoCounterKustomize = "../../../examples/config/default/go-kprobe-counter" + kprobeGoCounterUserspaceNs = "go-kprobe-counter" + kprobeGoCounterUserspaceDsName = "go-kprobe-counter-ds" +) + +func TestKprobeGoCounter(t *testing.T) { + t.Log("deploying kprobe counter program") + require.NoError(t, clusters.KustomizeDeployForCluster(ctx, env.Cluster(), kprobeGoCounterKustomize)) + addCleanup(func(context.Context) error { + cleanupLog("cleaning up kprobe counter program") + return clusters.KustomizeDeleteForCluster(ctx, env.Cluster(), kprobeGoCounterKustomize) + }) + + t.Log("waiting for go kprobe counter userspace daemon to be available") + require.Eventually(t, func() bool { + daemon, err := env.Cluster().Client().AppsV1().DaemonSets(kprobeGoCounterUserspaceNs).Get(ctx, kprobeGoCounterUserspaceDsName, metav1.GetOptions{}) + require.NoError(t, err) + return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable + }, + // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043 + 5*time.Minute, 10*time.Second) + + pods, err := env.Cluster().Client().CoreV1().Pods(kprobeGoCounterUserspaceNs).List(ctx, metav1.ListOptions{LabelSelector: "name=go-kprobe-counter"}) + require.NoError(t, err) + goKprobeCounterPod := pods.Items[0] + + want := regexp.MustCompile(`Kprobe count: ([0-9]+)`) + req := env.Cluster().Client().CoreV1().Pods(kprobeGoCounterUserspaceNs).GetLogs(goKprobeCounterPod.Name, &corev1.PodLogOptions{}) + require.Eventually(t, func() bool { + logs, err := req.Stream(ctx) + require.NoError(t, err) + defer logs.Close() + output := new(bytes.Buffer) + _, err = io.Copy(output, logs) + require.NoError(t, err) + t.Logf("counter pod log %s", output.String()) + + matches := want.FindAllStringSubmatch(output.String(), -1) + if len(matches) >= 1 && len(matches[0]) >= 2 { + count, err := strconv.Atoi(matches[0][1]) + require.NoError(t, err) + if count > 0 { + t.Logf("counted %d kprobe executions so far, BPF program is functioning", count) + return true + } + } + return false + }, 30*time.Second, time.Second) +} diff --git a/bpfman-operator/test/integration/tc_test.go b/bpfman-operator/test/integration/tc_test.go index 00258662e..386e31d61 100644 --- a/bpfman-operator/test/integration/tc_test.go +++ b/bpfman-operator/test/integration/tc_test.go @@ -36,7 +36,9 @@ func TestTcGoCounter(t *testing.T) { daemon, err := env.Cluster().Client().AppsV1().DaemonSets(tcGoCounterUserspaceNs).Get(ctx, tcGoCounterUserspaceDsName, metav1.GetOptions{}) require.NoError(t, err) return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable - }, time.Minute, time.Second) + }, + // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043 + 5 * time.Minute, 10 * time.Second) pods, err := env.Cluster().Client().CoreV1().Pods(tcGoCounterUserspaceNs).List(ctx, metav1.ListOptions{LabelSelector: "name=go-tc-counter"}) require.NoError(t, err) diff --git a/bpfman-operator/test/integration/tracepoint_test.go b/bpfman-operator/test/integration/tracepoint_test.go index d1c3a57ce..1a4d9207f 100644 --- a/bpfman-operator/test/integration/tracepoint_test.go +++ b/bpfman-operator/test/integration/tracepoint_test.go @@ -37,8 +37,10 @@ func TestTracepointGoCounter(t *testing.T) { daemon, err := env.Cluster().Client().AppsV1().DaemonSets(tracepointGoCounterUserspaceNs).Get(ctx, tracepointGoCounterUserspaceDsName, metav1.GetOptions{}) require.NoError(t, err) return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable - }, time.Minute, time.Second) - + }, + // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043 + 5 * time.Minute, 10 * time.Second) + pods, err := env.Cluster().Client().CoreV1().Pods(tracepointGoCounterUserspaceNs).List(ctx, metav1.ListOptions{LabelSelector: "name=go-tracepoint-counter"}) require.NoError(t, err) goTracepointCounterPod := pods.Items[0] diff --git a/bpfman-operator/test/integration/uprobe_test.go b/bpfman-operator/test/integration/uprobe_test.go new file mode 100644 index 000000000..4be4ae443 --- /dev/null +++ b/bpfman-operator/test/integration/uprobe_test.go @@ -0,0 +1,89 @@ +//go:build integration_tests +// +build integration_tests + +package integration + +import ( + "bytes" + "context" + "io" + "regexp" + "strconv" + "testing" + "time" + + "github.com/kong/kubernetes-testing-framework/pkg/clusters" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + uprobeGoCounterKustomize = "../../../examples/config/default/go-uprobe-counter" + uprobeGoCounterUserspaceNs = "go-uprobe-counter" + uprobeGoCounterUserspaceDsName = "go-uprobe-counter-ds" + targetKustomize = "../../../examples/config/default/go-target" + targetUserspaceNs = "go-target" + targetUserspaceDsName = "go-target-ds" +) + +func TestUprobeGoCounter(t *testing.T) { + t.Log("deploying target for uprobe counter program") + require.NoError(t, clusters.KustomizeDeployForCluster(ctx, env.Cluster(), targetKustomize)) + addCleanup(func(context.Context) error { + cleanupLog("cleaning up target program") + return clusters.KustomizeDeleteForCluster(ctx, env.Cluster(), targetKustomize) + }) + + t.Log("waiting for go target userspace daemon to be available") + require.Eventually(t, func() bool { + daemon, err := env.Cluster().Client().AppsV1().DaemonSets(targetUserspaceNs).Get(ctx, targetUserspaceDsName, metav1.GetOptions{}) + require.NoError(t, err) + return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable + }, + // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043 + 5*time.Minute, 10*time.Second) + + t.Log("deploying uprobe counter program") + require.NoError(t, clusters.KustomizeDeployForCluster(ctx, env.Cluster(), uprobeGoCounterKustomize)) + addCleanup(func(context.Context) error { + cleanupLog("cleaning up uprobe counter program") + return clusters.KustomizeDeleteForCluster(ctx, env.Cluster(), uprobeGoCounterKustomize) + }) + + t.Log("waiting for go uprobe counter userspace daemon to be available") + require.Eventually(t, func() bool { + daemon, err := env.Cluster().Client().AppsV1().DaemonSets(uprobeGoCounterUserspaceNs).Get(ctx, uprobeGoCounterUserspaceDsName, metav1.GetOptions{}) + require.NoError(t, err) + return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable + }, + // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043 + 5*time.Minute, 10*time.Second) + + pods, err := env.Cluster().Client().CoreV1().Pods(uprobeGoCounterUserspaceNs).List(ctx, metav1.ListOptions{LabelSelector: "name=go-uprobe-counter"}) + require.NoError(t, err) + goUprobeCounterPod := pods.Items[0] + + want := regexp.MustCompile(`Uprobe count: ([0-9]+)`) + req := env.Cluster().Client().CoreV1().Pods(uprobeGoCounterUserspaceNs).GetLogs(goUprobeCounterPod.Name, &corev1.PodLogOptions{}) + require.Eventually(t, func() bool { + logs, err := req.Stream(ctx) + require.NoError(t, err) + defer logs.Close() + output := new(bytes.Buffer) + _, err = io.Copy(output, logs) + require.NoError(t, err) + t.Logf("counter pod log %s", output.String()) + + matches := want.FindAllStringSubmatch(output.String(), -1) + if len(matches) >= 1 && len(matches[0]) >= 2 { + count, err := strconv.Atoi(matches[0][1]) + require.NoError(t, err) + if count > 0 { + t.Logf("counted %d uprobe executions so far, BPF program is functioning", count) + return true + } + } + return false + }, 30*time.Second, time.Second) +} diff --git a/bpfman-operator/test/integration/xdp_test.go b/bpfman-operator/test/integration/xdp_test.go index ccb1ff6da..3f92dcb52 100644 --- a/bpfman-operator/test/integration/xdp_test.go +++ b/bpfman-operator/test/integration/xdp_test.go @@ -6,7 +6,9 @@ package integration import ( "bytes" "context" + "encoding/base64" "io" + "os" "strings" "testing" "time" @@ -16,6 +18,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/yaml" ) const ( @@ -24,28 +27,43 @@ const ( xdpGoCounterUserspaceDsName = "go-xdp-counter-ds" ) +var xdpPassPrivateImageCreds = os.Getenv("XDP_PASS_PRIVATE_IMAGE_CREDS") + func TestXdpPassPrivate(t *testing.T) { t.Log("deploying secret for privated xdp bytecode image in the bpfman namespace") // Generated from /* - kubectl create secret -n bpfman docker-registry regcred --docker-server=quay.io --docker-username=bpfman-bytecode+bpfmancreds --docker-password=D49CKWI1MMOFGRCAT8SHW5A56FSVP30TGYX54BBWKY2J129XRI6Q5TVH2ZZGTJ1M + kubectl create secret -n bpfman docker-registry regcred --docker-server=quay.io --docker-username= --docker-password= */ - xdpPassPrivateSecretYAML := `--- ---- -apiVersion: v1 -kind: Secret -metadata: - name: regcred - namespace: default -type: kubernetes.io/dockerconfigjson -data: - .dockerconfigjson: eyJhdXRocyI6eyJxdWF5LmlvIjp7InVzZXJuYW1lIjoiYnBmbWFuLWJ5dGVjb2RlK2JwZm1hbmNyZWRzIiwicGFzc3dvcmQiOiJENDlDS1dJMU1NT0ZHUkNBVDhTSFc1QTU2RlNWUDMwVEdZWDU0QkJXS1kySjEyOVhSSTZRNVRWSDJaWkdUSjFNIiwiYXV0aCI6IlluQm1iV0Z1TFdKNWRHVmpiMlJsSzJKd1ptMWhibU55WldSek9rUTBPVU5MVjBreFRVMVBSa2RTUTBGVU9GTklWelZCTlRaR1UxWlFNekJVUjFsWU5UUkNRbGRMV1RKS01USTVXRkpKTmxFMVZGWklNbHBhUjFSS01VMD0ifX19 -` - require.NoError(t, clusters.ApplyManifestByYAML(ctx, env.Cluster(), xdpPassPrivateSecretYAML)) + if xdpPassPrivateImageCreds == "" { + t.Log("XDP_PASS_PRIVATE_IMAGE_CREDS must be provided to run this test, skipping") + t.SkipNow() + } + + xdpPassPrivateJson, err := base64.StdEncoding.DecodeString(xdpPassPrivateImageCreds) + require.NoError(t, err) + + xdpPassPrivateSecret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "regcred", + Namespace: "default", + }, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{".dockerconfigjson": []byte(xdpPassPrivateJson)}, + } + + yamlData, err := yaml.Marshal(xdpPassPrivateSecret) + require.NoError(t, err) + + require.NoError(t, clusters.ApplyManifestByYAML(ctx, env.Cluster(), string(yamlData))) addCleanup(func(ctx context.Context) error { cleanupLog("cleaning up xdp pass private secret") - return clusters.DeleteManifestByYAML(ctx, env.Cluster(), xdpPassPrivateSecretYAML) + return clusters.DeleteManifestByYAML(ctx, env.Cluster(), string(yamlData)) }) xdpPassPrivateXdpProgramYAML := `--- @@ -98,7 +116,9 @@ func TestXdpGoCounter(t *testing.T) { daemon, err := env.Cluster().Client().AppsV1().DaemonSets(xdpGoCounterUserspaceNs).Get(ctx, xdpGoCounterUserspaceDsName, metav1.GetOptions{}) require.NoError(t, err) return daemon.Status.DesiredNumberScheduled == daemon.Status.NumberAvailable - }, time.Minute, time.Second) + }, + // Wait 5 minutes since cosign is slow, https://github.com/bpfman/bpfman/issues/1043 + 5 * time.Minute, 10 * time.Second) pods, err := env.Cluster().Client().CoreV1().Pods(xdpGoCounterUserspaceNs).List(ctx, metav1.ListOptions{LabelSelector: "name=go-xdp-counter"}) require.NoError(t, err) diff --git a/bpfman.spec b/bpfman.spec new file mode 100644 index 000000000..72157fffd --- /dev/null +++ b/bpfman.spec @@ -0,0 +1,107 @@ +# Generated by rust2rpm 25 +%bcond_without check + +%global crate bpfman +%global commit GITSHA +%global shortcommit GITSHORTSHA +%global base_version 0.4.0 +%global prerelease dev +%global package_version %{base_version}%{?prerelease:~%{prerelease}} +%global upstream_version %{base_version}%{?prerelease:~%{prerelease}} + +Name: bpfman +Version: %{package_version} +Release: %autorelease +Summary: An eBPF program manager + +SourceLicense: Apache-2.0 +# (Apache-2.0 OR MIT) AND BSD-3-Clause +# (MIT OR Apache-2.0) AND Unicode-DFS-2016 +# 0BSD OR MIT OR Apache-2.0 +# Apache-2.0 +# Apache-2.0 OR BSL-1.0 +# Apache-2.0 OR ISC OR MIT +# Apache-2.0 OR MIT +# Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT +# BSD-2-Clause OR Apache-2.0 OR MIT +# BSD-3-Clause +# ISC +# MIT +# MIT AND BSD-3-Clause +# MIT OR Apache-2.0 +# MIT OR Apache-2.0 OR BSD-1-Clause +# MIT OR Apache-2.0 OR Zlib +# MIT OR Zlib OR Apache-2.0 +# MPL-2.0 +# Unlicense OR MIT +# Zlib OR Apache-2.0 OR MIT +License: Apache-2.0 AND Unicode-DFS-2016 AND BSD-3-Clause AND ISC AND MIT AND MPL-2.0 +# LICENSE.dependencies contains a full license breakdown + +URL: https://bpfman.io +Source0: https://github.com/bpfman/bpfman/archive/%{commit}/%{name}-%{shortcommit}.tar.gz +# The vendor tarball is created using cargo-vendor-filterer to remove Windows +# related files (https://github.com/coreos/cargo-vendor-filterer) +# cargo vendor-filterer --format tar.gz --prefix vendor bpfman-bpfman-vendor.tar.gz +Source1: bpfman-bpfman-vendor.tar.gz + +BuildRequires: cargo-rpm-macros >= 25 +BuildRequires: systemd-rpm-macros +BuildRequires: openssl-devel + +# TODO: Generate Provides for all of the vendored dependencies + +%global _description %{expand: +An eBPF Program Manager.} + +%description %{_description} + +%prep +%autosetup %{name}-%{version_no_tilde} -n %{name}-%{commit} -p1 -a1 + +# Source1 is vendored dependencies +tar -xoaf %{SOURCE1} +# Let the macros setup Cargo.toml to use vendored sources +%cargo_prep -v vendor +%cargo_license_summary +%cargo_license + +%build +%cargo_build +%{cargo_license_summary} +%{cargo_license} > LICENSE.dependencies +%{cargo_vendor_manifest} + +%install +install -Dpm 0755 \ + -t %{buildroot}%{_sbindir} \ + ./target/release/bpfman +install -Dpm 644 \ + -t %{buildroot}%{_unitdir} \ + ./scripts/bpfman.socket +install -Dpm 644 \ + -t %{buildroot}%{_unitdir} \ + ./scripts/bpfman.service + +%post +%systemd_post bpfman.socket + +%preun +%systemd_preun bpfman.socket + +%postun +%systemd_postun_with_restart bpfman.socket + +%files +%license LICENSE-APACHE +%license LICENSE-BSD2 +%license LICENSE-GPL2 +%license LICENSE.dependencies +%license cargo-vendor.txt +%doc README.md +%{_sbindir}/bpfman +%{_unitdir}/bpfman.socket +%{_unitdir}/bpfman.service + +%changelog +%autochangelog diff --git a/bpfman/Cargo.toml b/bpfman/Cargo.toml index b0851e3c9..59b5f0ac4 100644 --- a/bpfman/Cargo.toml +++ b/bpfman/Cargo.toml @@ -1,14 +1,18 @@ [package] -description = "A system daemon for loading BPF programs" -edition = "2021" -license = "Apache-2.0" +description = "An eBPF Program Manager" +edition.workspace = true +license.workspace = true name = "bpfman" -repository = "https://github.com/bpfman/bpfman" -version = "0.3.1" +repository.workspace = true +version.workspace = true + +[lib] +name = "bpfman" +path = "src/lib.rs" [[bin]] name = "bpfman" -path = "src/main.rs" +path = "src/bin/cli/main.rs" [dependencies] anyhow = { workspace = true, features = ["std"] } @@ -16,7 +20,6 @@ async-trait = { workspace = true } aya = { workspace = true } base16ct = { workspace = true, features = ["alloc"] } base64 = { workspace = true } -bpfman-api = { workspace = true } bpfman-csi = { workspace = true } caps = { workspace = true } chrono = { workspace = true } @@ -33,6 +36,7 @@ env_logger = { workspace = true } flate2 = { workspace = true, features = ["zlib"] } futures = { workspace = true } hex = { workspace = true, features = ["std"] } +lazy_static = { workspace = true } log = { workspace = true } netlink-packet-route = { workspace = true } nix = { workspace = true, features = [ @@ -44,29 +48,30 @@ nix = { workspace = true, features = [ "user", ] } oci-distribution = { workspace = true, default-features = false, features = [ - "rustls-tls", + "native-tls", "trust-dns", ] } +rand = { workspace = true } rtnetlink = { workspace = true, features = ["tokio_socket"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["std"] } sha2 = { workspace = true } sigstore = { workspace = true, features = [ "cached-client", - "cosign-rustls-tls", - "tuf", + "cosign-native-tls", + "sigstore-trust-root", ] } +sled = { workspace = true } systemd-journal-logger = { workspace = true } tar = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full", "signal"] } tokio-stream = { workspace = true, features = ["net"] } -toml = { workspace = true } +toml = { workspace = true, features = ["parse"] } tonic = { workspace = true, features = ["transport"] } tower = { workspace = true } url = { workspace = true } -users = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } diff --git a/bpfman/src/bin/cli/args.rs b/bpfman/src/bin/cli/args.rs new file mode 100644 index 000000000..0f0f7448b --- /dev/null +++ b/bpfman/src/bin/cli/args.rs @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use bpfman::types::ProgramType; +use clap::{Args, Parser, Subcommand}; +use hex::FromHex; + +#[derive(Parser, Debug)] +#[command( + long_about = "An eBPF manager focusing on simplifying the deployment and administration of eBPF programs." +)] +#[command(name = "bpfman")] +#[command(disable_version_flag = true)] +pub(crate) struct Cli { + #[command(subcommand)] + pub(crate) command: Commands, +} + +#[derive(Subcommand, Debug)] +pub(crate) enum Commands { + /// Load an eBPF program on the system. + #[command(subcommand)] + Load(LoadSubcommand), + /// Unload an eBPF program using the Program Id. + Unload(UnloadArgs), + /// List all eBPF programs loaded via bpfman. + List(ListArgs), + /// Get an eBPF program using the Program Id. + Get(GetArgs), + /// eBPF Bytecode Image related commands. + #[command(subcommand)] + Image(ImageSubCommand), +} + +#[derive(Subcommand, Debug)] +#[command(disable_version_flag = true)] +pub(crate) enum LoadSubcommand { + /// Load an eBPF program from a local .o file. + File(LoadFileArgs), + /// Load an eBPF program packaged in a OCI container image from a given registry. + Image(LoadImageArgs), +} + +#[derive(Args, Debug)] +pub(crate) struct LoadFileArgs { + /// Required: Location of local bytecode file + /// Example: --path /run/bpfman/examples/go-xdp-counter/bpf_bpfel.o + #[clap(short, long, verbatim_doc_comment)] + pub(crate) path: String, + + /// Required: The name of the function that is the entry point for the BPF program. + #[clap(short, long)] + pub(crate) name: String, + + /// Optional: Global variables to be set when program is loaded. + /// Format: = + /// + /// This is a very low level primitive. The caller is responsible for formatting + /// the byte string appropriately considering such things as size, endianness, + /// alignment and packing of data structures. + #[clap(short, long, verbatim_doc_comment, num_args(1..), value_parser=parse_global_arg)] + pub(crate) global: Option>, + + /// Optional: Specify Key/Value metadata to be attached to a program when it + /// is loaded by bpfman. + /// Format: = + /// + /// This can later be used to `list` a certain subset of programs which contain + /// the specified metadata. + /// Example: --metadata owner=acme + #[clap(short, long, verbatim_doc_comment, value_parser=parse_key_val, value_delimiter = ',')] + pub(crate) metadata: Option>, + + /// Optional: Program Id of loaded eBPF program this eBPF program will share a map with. + /// Only used when multiple eBPF programs need to share a map. + /// Example: --map-owner-id 63178 + #[clap(long, verbatim_doc_comment)] + pub(crate) map_owner_id: Option, + + #[clap(subcommand)] + pub(crate) command: LoadCommands, +} + +#[derive(Args, Debug)] +pub(crate) struct LoadImageArgs { + /// Specify how the bytecode image should be pulled. + #[command(flatten)] + pub(crate) pull_args: PullBytecodeArgs, + + /// Optional: The name of the function that is the entry point for the BPF program. + /// If not provided, the program name defined as part of the bytecode image will be used. + #[clap(short, long, verbatim_doc_comment, default_value = "")] + pub(crate) name: String, + + /// Optional: Global variables to be set when program is loaded. + /// Format: = + /// + /// This is a very low level primitive. The caller is responsible for formatting + /// the byte string appropriately considering such things as size, endianness, + /// alignment and packing of data structures. + #[clap(short, long, verbatim_doc_comment, num_args(1..), value_parser=parse_global_arg)] + pub(crate) global: Option>, + + /// Optional: Specify Key/Value metadata to be attached to a program when it + /// is loaded by bpfman. + /// Format: = + /// + /// This can later be used to list a certain subset of programs which contain + /// the specified metadata. + /// Example: --metadata owner=acme + #[clap(short, long, verbatim_doc_comment, value_parser=parse_key_val, value_delimiter = ',')] + pub(crate) metadata: Option>, + + /// Optional: Program Id of loaded eBPF program this eBPF program will share a map with. + /// Only used when multiple eBPF programs need to share a map. + /// Example: --map-owner-id 63178 + #[clap(long, verbatim_doc_comment)] + pub(crate) map_owner_id: Option, + + #[clap(subcommand)] + pub(crate) command: LoadCommands, +} + +#[derive(Clone, Debug)] +pub(crate) struct GlobalArg { + pub(crate) name: String, + pub(crate) value: Vec, +} + +#[derive(Subcommand, Debug)] +#[command(disable_version_flag = true)] +pub(crate) enum LoadCommands { + #[command(disable_version_flag = true)] + /// Install an eBPF program on the XDP hook point for a given interface. + Xdp { + /// Required: Interface to load program on. + #[clap(short, long)] + iface: String, + + /// Required: Priority to run program in chain. Lower value runs first. + #[clap(short, long)] + priority: i32, + + /// Optional: Proceed to call other programs in chain on this exit code. + /// Multiple values supported by repeating the parameter. + /// Example: --proceed-on "pass" --proceed-on "drop" + /// + /// [possible values: aborted, drop, pass, tx, redirect, dispatcher_return] + /// + /// [default: pass, dispatcher_return] + #[clap(long, verbatim_doc_comment, num_args(1..))] + proceed_on: Vec, + }, + #[command(disable_version_flag = true)] + /// Install an eBPF program on the TC hook point for a given interface. + Tc { + /// Required: Direction to apply program. + /// + /// [possible values: ingress, egress] + #[clap(short, long, verbatim_doc_comment)] + direction: String, + + /// Required: Interface to load program on. + #[clap(short, long)] + iface: String, + + /// Required: Priority to run program in chain. Lower value runs first. + #[clap(short, long)] + priority: i32, + + /// Optional: Proceed to call other programs in chain on this exit code. + /// Multiple values supported by repeating the parameter. + /// Example: --proceed-on "ok" --proceed-on "pipe" + /// + /// [possible values: unspec, ok, reclassify, shot, pipe, stolen, queued, + /// repeat, redirect, trap, dispatcher_return] + /// + /// [default: ok, pipe, dispatcher_return] + #[clap(long, verbatim_doc_comment, num_args(1..))] + proceed_on: Vec, + }, + #[command(disable_version_flag = true)] + /// Install an eBPF program on a Tracepoint. + Tracepoint { + /// Required: The tracepoint to attach to. + /// Example: --tracepoint "sched/sched_switch" + #[clap(short, long, verbatim_doc_comment)] + tracepoint: String, + }, + #[command(disable_version_flag = true)] + /// Install a kprobe or kretprobe eBPF probe + Kprobe { + /// Required: Function to attach the kprobe to. + #[clap(short, long)] + fn_name: String, + + /// Optional: Offset added to the address of the function for kprobe. + /// Not allowed for kretprobes. + #[clap(short, long, verbatim_doc_comment)] + offset: Option, + + /// Optional: Whether the program is a kretprobe. + /// + /// [default: false] + #[clap(short, long, verbatim_doc_comment)] + retprobe: bool, + + /// Optional: Host PID of container to attach the kprobe in. + /// (NOT CURRENTLY SUPPORTED) + #[clap(short, long)] + container_pid: Option, + }, + #[command(disable_version_flag = true)] + /// Install a uprobe or uretprobe eBPF probe + Uprobe { + /// Optional: Function to attach the uprobe to. + #[clap(short, long)] + fn_name: Option, + + /// Optional: Offset added to the address of the target function (or + /// beginning of target if no function is identified). Offsets are + /// supported for uretprobes, but use with caution because they can + /// result in unintended side effects. + #[clap(short, long, verbatim_doc_comment)] + offset: Option, + + /// Required: Library name or the absolute path to a binary or library. + /// Example: --target "libc". + #[clap(short, long, verbatim_doc_comment)] + target: String, + + /// Optional: Whether the program is a uretprobe. + /// + /// [default: false] + #[clap(short, long, verbatim_doc_comment)] + retprobe: bool, + + /// Optional: Only execute uprobe for given process identification number (PID). + /// If PID is not provided, uprobe executes for all PIDs. + #[clap(short, long, verbatim_doc_comment)] + pid: Option, + + /// Optional: Host PID of container to attach the uprobe in. + /// (NOT CURRENTLY SUPPORTED) + #[clap(short, long)] + container_pid: Option, + }, + #[command(disable_version_flag = true)] + /// Install a fentry eBPF probe + Fentry { + /// Required: Kernel function to attach the fentry probe. + #[clap(short, long)] + fn_name: String, + }, + #[command(disable_version_flag = true)] + /// Install a fexit eBPF probe + Fexit { + /// Required: Kernel function to attach the fexit probe. + #[clap(short, long)] + fn_name: String, + }, +} + +#[derive(Args, Debug)] +#[command(disable_version_flag = true)] +pub(crate) struct UnloadArgs { + /// Required: Program Id to be unloaded. + pub(crate) program_id: u32, +} + +#[derive(Args, Debug)] +#[command(disable_version_flag = true)] +pub(crate) struct ListArgs { + /// Optional: List a specific program type + /// Example: --program-type xdp + /// + /// [possible values: unspec, socket-filter, probe, tc, sched-act, + /// tracepoint, xdp, perf-event, cgroup-skb, + /// cgroup-sock, lwt-in, lwt-out, lwt-xmit, sock-ops, + /// sk-skb, cgroup-device, sk-msg, raw-tracepoint, + /// cgroup-sock-addr, lwt-seg6-local, lirc-mode2, + /// sk-reuseport, flow-dissector, cgroup-sysctl, + /// raw-tracepoint-writable, cgroup-sockopt, tracing, + /// struct-ops, ext, lsm, sk-lookup, syscall] + #[clap(short, long, verbatim_doc_comment, hide_possible_values = true)] + pub(crate) program_type: Option, + + /// Optional: List programs which contain a specific set of metadata labels + /// that were applied when the program was loaded with `--metadata` parameter. + /// Format: = + /// + /// Example: --metadata-selector owner=acme + #[clap(short, long, verbatim_doc_comment, value_parser=parse_key_val, value_delimiter = ',')] + pub(crate) metadata_selector: Option>, + + /// Optional: List all programs. + #[clap(short, long, verbatim_doc_comment)] + pub(crate) all: bool, +} + +#[derive(Args, Debug)] +#[command(disable_version_flag = true)] +pub(crate) struct GetArgs { + /// Required: Program Id to get. + pub(crate) program_id: u32, +} + +#[derive(Subcommand, Debug)] +#[command(disable_version_flag = true)] +pub(crate) enum ImageSubCommand { + /// Pull an eBPF bytecode image from a remote registry. + Pull(PullBytecodeArgs), +} + +#[derive(Args, Debug)] +#[command(disable_version_flag = true)] +pub(crate) struct PullBytecodeArgs { + /// Required: Container Image URL. + /// Example: --image-url quay.io/bpfman-bytecode/xdp_pass:latest + #[clap(short, long, verbatim_doc_comment)] + pub(crate) image_url: String, + + /// Optional: Registry auth for authenticating with the specified image registry. + /// This should be base64 encoded from the ':' string just like + /// it's stored in the docker/podman host config. + /// Example: --registry_auth "YnjrcKw63PhDcQodiU9hYxQ2" + #[clap(short, long, verbatim_doc_comment)] + pub(crate) registry_auth: Option, + + /// Optional: Pull policy for remote images. + /// + /// [possible values: Always, IfNotPresent, Never] + #[clap(short, long, verbatim_doc_comment, default_value = "IfNotPresent")] + pub(crate) pull_policy: String, +} + +/// Parse a single key-value pair +pub(crate) fn parse_key_val(s: &str) -> Result<(String, String), std::io::Error> { + let pos = s.find('=').ok_or(std::io::ErrorKind::InvalidInput)?; + Ok((s[..pos].to_string(), s[pos + 1..].to_string())) +} + +pub(crate) fn parse_global_arg(global_arg: &str) -> Result { + let mut parts = global_arg.split('='); + + let name_str = parts.next().ok_or(std::io::ErrorKind::InvalidInput)?; + + let value_str = parts.next().ok_or(std::io::ErrorKind::InvalidInput)?; + let value = Vec::::from_hex(value_str).map_err(|_e| std::io::ErrorKind::InvalidInput)?; + if value.is_empty() { + return Err(std::io::ErrorKind::InvalidInput.into()); + } + + Ok(GlobalArg { + name: name_str.to_string(), + value, + }) +} diff --git a/bpfman/src/bin/cli/get.rs b/bpfman/src/bin/cli/get.rs new file mode 100644 index 000000000..91809e80c --- /dev/null +++ b/bpfman/src/bin/cli/get.rs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use bpfman::{errors::BpfmanError, get_program}; +use log::warn; + +use crate::{args::GetArgs, table::ProgTable}; + +pub(crate) async fn execute_get(args: &GetArgs) -> Result<(), BpfmanError> { + match get_program(args.program_id).await { + Ok(program) => { + ProgTable::new_program(&program)?.print(); + ProgTable::new_kernel_info(&program)?.print(); + Ok(()) + } + Err(e) => { + warn!("BPFMAN get error: {}", e); + Err(e) + } + } +} diff --git a/bpfman/src/bin/cli/image.rs b/bpfman/src/bin/cli/image.rs new file mode 100644 index 000000000..3b2de6cf9 --- /dev/null +++ b/bpfman/src/bin/cli/image.rs @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use base64::{engine::general_purpose, Engine}; +use bpfman::{ + pull_bytecode, + types::{BytecodeImage, ImagePullPolicy}, +}; + +use crate::args::{ImageSubCommand, PullBytecodeArgs}; + +impl ImageSubCommand { + pub(crate) async fn execute(&self) -> anyhow::Result<()> { + match self { + ImageSubCommand::Pull(args) => execute_pull(args).await, + } + } +} + +impl TryFrom<&PullBytecodeArgs> for BytecodeImage { + type Error = anyhow::Error; + + fn try_from(value: &PullBytecodeArgs) -> Result { + let image_pull_policy: ImagePullPolicy = value.pull_policy.as_str().try_into()?; + let (username, password) = match &value.registry_auth { + Some(a) => { + let auth_raw = general_purpose::STANDARD.decode(a)?; + let auth_string = String::from_utf8(auth_raw)?; + let (username, password) = auth_string.split_once(':').unwrap(); + (Some(username.to_owned()), Some(password.to_owned())) + } + None => (None, None), + }; + + Ok(BytecodeImage { + image_url: value.image_url.clone(), + image_pull_policy, + username, + password, + }) + } +} + +pub(crate) async fn execute_pull(args: &PullBytecodeArgs) -> anyhow::Result<()> { + let image: BytecodeImage = args.try_into()?; + pull_bytecode(image).await?; + + Ok(()) +} diff --git a/bpfman/src/bin/cli/list.rs b/bpfman/src/bin/cli/list.rs new file mode 100644 index 000000000..76e403ed0 --- /dev/null +++ b/bpfman/src/bin/cli/list.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use anyhow::bail; +use bpfman::{list_programs, types::ListFilter}; + +use crate::{args::ListArgs, table::ProgTable}; + +pub(crate) async fn execute_list(args: &ListArgs) -> anyhow::Result<()> { + let prog_type_filter = args.program_type.map(|p| p as u32); + + let filter = ListFilter::new( + prog_type_filter, + args.metadata_selector + .clone() + .unwrap_or_default() + .iter() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect(), + !args.all, + ); + + let mut table = ProgTable::new_list(); + + for r in list_programs(filter).await? { + if let Err(e) = table.add_response_prog(r) { + bail!(e) + } + } + table.print(); + Ok(()) +} diff --git a/bpfman/src/bin/cli/load.rs b/bpfman/src/bin/cli/load.rs new file mode 100644 index 000000000..b40018a6d --- /dev/null +++ b/bpfman/src/bin/cli/load.rs @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use std::collections::HashMap; + +use anyhow::bail; +use bpfman::{ + add_program, + types::{ + FentryProgram, FexitProgram, KprobeProgram, Location, Program, ProgramData, TcProceedOn, + TcProgram, TracepointProgram, UprobeProgram, XdpProceedOn, XdpProgram, + }, +}; + +use crate::{ + args::{GlobalArg, LoadCommands, LoadFileArgs, LoadImageArgs, LoadSubcommand}, + table::ProgTable, +}; + +impl LoadSubcommand { + pub(crate) async fn execute(&self) -> anyhow::Result<()> { + match self { + LoadSubcommand::File(l) => execute_load_file(l).await, + LoadSubcommand::Image(l) => execute_load_image(l).await, + } + } +} + +pub(crate) async fn execute_load_file(args: &LoadFileArgs) -> anyhow::Result<()> { + let bytecode_source = Location::File(args.path.clone()); + + let data = ProgramData::new( + bytecode_source, + args.name.clone(), + args.metadata + .clone() + .unwrap_or_default() + .iter() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect(), + parse_global(&args.global), + args.map_owner_id, + )?; + + let program = add_program(args.command.get_program(data)?).await?; + + ProgTable::new_program(&program)?.print(); + ProgTable::new_kernel_info(&program)?.print(); + Ok(()) +} + +pub(crate) async fn execute_load_image(args: &LoadImageArgs) -> anyhow::Result<()> { + let bytecode_source = Location::Image((&args.pull_args).try_into()?); + + let data = ProgramData::new( + bytecode_source, + args.name.clone(), + args.metadata + .clone() + .unwrap_or_default() + .iter() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect(), + parse_global(&args.global), + args.map_owner_id, + )?; + + let program = add_program(args.command.get_program(data)?).await?; + + ProgTable::new_program(&program)?.print(); + ProgTable::new_kernel_info(&program)?.print(); + Ok(()) +} + +impl LoadCommands { + pub(crate) fn get_program(&self, data: ProgramData) -> Result { + match self { + LoadCommands::Xdp { + iface, + priority, + proceed_on, + } => { + let proc_on = match XdpProceedOn::from_strings(proceed_on) { + Ok(p) => p, + Err(e) => bail!("error parsing proceed_on {e}"), + }; + Ok(Program::Xdp(XdpProgram::new( + data, + *priority, + iface.to_string(), + XdpProceedOn::from_int32s(proc_on.as_action_vec())?, + )?)) + } + LoadCommands::Tc { + direction, + iface, + priority, + proceed_on, + } => { + match direction.as_str() { + "ingress" | "egress" => (), + other => bail!("{} is not a valid direction", other), + }; + let proc_on = match TcProceedOn::from_strings(proceed_on) { + Ok(p) => p, + Err(e) => bail!("error parsing proceed_on {e}"), + }; + Ok(Program::Tc(TcProgram::new( + data, + *priority, + iface.to_string(), + proc_on, + direction.to_string().try_into()?, + )?)) + } + LoadCommands::Tracepoint { tracepoint } => Ok(Program::Tracepoint( + TracepointProgram::new(data, tracepoint.to_string())?, + )), + LoadCommands::Kprobe { + fn_name, + offset, + retprobe, + container_pid, + } => { + if container_pid.is_some() { + bail!("kprobe container option not supported yet"); + } + let offset = offset.unwrap_or(0); + Ok(Program::Kprobe(KprobeProgram::new( + data, + fn_name.to_string(), + offset, + *retprobe, + None, + )?)) + } + LoadCommands::Uprobe { + fn_name, + offset, + target, + retprobe, + pid, + container_pid, + } => { + let offset = offset.unwrap_or(0); + Ok(Program::Uprobe(UprobeProgram::new( + data, + fn_name.clone(), + offset, + target.to_string(), + *retprobe, + *pid, + *container_pid, + )?)) + } + LoadCommands::Fentry { fn_name } => Ok(Program::Fentry(FentryProgram::new( + data, + fn_name.to_string(), + )?)), + LoadCommands::Fexit { fn_name } => Ok(Program::Fexit(FexitProgram::new( + data, + fn_name.to_string(), + )?)), + } + } +} + +fn parse_global(global: &Option>) -> HashMap> { + let mut global_data: HashMap> = HashMap::new(); + + if let Some(global) = global { + for g in global.iter() { + global_data.insert(g.name.to_string(), g.value.clone()); + } + } + global_data +} diff --git a/bpfman/src/bin/cli/main.rs b/bpfman/src/bin/cli/main.rs new file mode 100644 index 000000000..3c27cefb1 --- /dev/null +++ b/bpfman/src/bin/cli/main.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use anyhow::anyhow; +use args::Commands; +use clap::Parser; +use get::execute_get; +use list::execute_list; +use unload::execute_unload; + +mod args; +mod get; +mod image; +mod list; +mod load; +mod table; +mod unload; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let cli = crate::args::Cli::parse(); + + cli.command.execute().await +} + +impl Commands { + pub(crate) async fn execute(&self) -> Result<(), anyhow::Error> { + match self { + Commands::Load(l) => l.execute().await, + Commands::Unload(args) => execute_unload(args).await, + Commands::List(args) => execute_list(args).await, + Commands::Get(args) => execute_get(args) + .await + .map_err(|e| anyhow!("get error: {e}")), + Commands::Image(i) => i.execute().await, + }?; + + Ok(()) + } +} diff --git a/bpfman/src/bin/cli/table.rs b/bpfman/src/bin/cli/table.rs new file mode 100644 index 000000000..c3ebda84c --- /dev/null +++ b/bpfman/src/bin/cli/table.rs @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use bpfman::types::{ImagePullPolicy, Location, ProbeType::*, Program}; +use comfy_table::{Cell, Color, Table}; +use hex::encode_upper; +pub(crate) struct ProgTable(Table); + +impl ProgTable { + pub(crate) fn new_program(program: &Program) -> Result { + let mut table = Table::new(); + + table.load_preset(comfy_table::presets::NOTHING); + table.set_header(vec![Cell::new("Bpfman State") + .add_attribute(comfy_table::Attribute::Bold) + .add_attribute(comfy_table::Attribute::Underlined) + .fg(Color::Green)]); + + let data = program.get_data(); + + let name = data.get_name()?; + if name.is_empty() { + table.add_row(vec!["Name:", "None"]); + } else { + table.add_row(vec!["Name:", &name]); + } + + match data.get_location()? { + Location::Image(i) => { + table.add_row(vec!["Image URL:", &i.image_url]); + table.add_row(vec![ + "Pull Policy:", + &format! { "{}", TryInto::::try_into(i.image_pull_policy)?}, + ]); + } + Location::File(p) => { + table.add_row(vec!["Path:", &p]); + } + }; + + let global_data = data.get_global_data()?; + if global_data.is_empty() { + table.add_row(vec!["Global:", "None"]); + } else { + let mut first = true; + for (key, value) in global_data { + let data = &format! {"{key}={}", encode_upper(value)}; + if first { + first = false; + table.add_row(vec!["Global:", data]); + } else { + table.add_row(vec!["", data]); + } + } + } + + let metadata = data.get_metadata()?; + if metadata.is_empty() { + table.add_row(vec!["Metadata:", "None"]); + } else { + let mut first = true; + for (key, value) in metadata.clone() { + let data = &format! {"{key}={value}"}; + if first { + first = false; + table.add_row(vec!["Metadata:", data]); + } else { + table.add_row(vec!["", data]); + } + } + } + + if let Some(map_pin_path) = data.get_map_pin_path()? { + table.add_row(vec![ + "Map Pin Path:", + map_pin_path + .to_str() + .expect("map_pin_path is not valid Unicode"), + ]); + } else { + table.add_row(vec!["Map Pin Path:", "None"]); + } + + match data.get_map_owner_id()? { + Some(id) => table.add_row(vec!["Map Owner ID:", &id.to_string()]), + None => table.add_row(vec!["Map Owner ID:", "None"]), + }; + + let map_used_by = data.get_maps_used_by()?; + if map_used_by.is_empty() { + table.add_row(vec!["Maps Used By:", "None"]); + } else { + let mut first = true; + for prog_id in map_used_by { + if first { + first = false; + table.add_row(vec!["Maps Used By:", &prog_id.to_string()]); + } else { + table.add_row(vec!["", &prog_id.to_string()]); + } + } + }; + + match program { + Program::Xdp(p) => { + table.add_row(vec!["Priority:", &p.get_priority()?.to_string()]); + table.add_row(vec!["Iface:", &p.get_iface()?]); + table.add_row(vec![ + "Position:", + &match p.get_current_position()? { + Some(pos) => pos.to_string(), + None => "NONE".to_string(), + }, + ]); + table.add_row(vec!["Proceed On:", &format!("{}", p.get_proceed_on()?)]); + } + Program::Tc(p) => { + table.add_row(vec!["Priority:", &p.get_priority()?.to_string()]); + table.add_row(vec!["Iface:", &p.get_iface()?]); + table.add_row(vec![ + "Position:", + &match p.get_current_position()? { + Some(pos) => pos.to_string(), + None => "NONE".to_string(), + }, + ]); + table.add_row(vec!["Direction:", &p.get_direction()?.to_string()]); + table.add_row(vec!["Proceed On:", &format!("{}", p.get_proceed_on()?)]); + } + Program::Tracepoint(p) => { + table.add_row(vec!["Tracepoint:", &p.get_tracepoint()?]); + } + Program::Kprobe(p) => { + let probe_type = match p.get_retprobe()? { + true => Kretprobe, + false => Kprobe, + }; + + table.add_row(vec!["Probe Type:", &format!["{probe_type}"]]); + table.add_row(vec!["Function Name:", &p.get_fn_name()?]); + table.add_row(vec!["Offset:", &p.get_offset()?.to_string()]); + table.add_row(vec![ + "PID:", + &p.get_container_pid()?.unwrap_or(0).to_string(), + ]); + } + Program::Uprobe(p) => { + let probe_type = match p.get_retprobe()? { + true => Kretprobe, + false => Kprobe, + }; + table.add_row(vec!["Probe Type:", &format!["{probe_type}"]]); + table.add_row(vec![ + "Function Name:", + &p.get_fn_name()?.unwrap_or("NONE".to_string()), + ]); + table.add_row(vec!["Offset:", &p.get_offset()?.to_string()]); + table.add_row(vec!["Target:", &p.get_target()?]); + table.add_row(vec!["PID", &p.get_pid()?.unwrap_or(0).to_string()]); + table.add_row(vec![ + "Container PID:", + &p.get_container_pid()?.unwrap_or(0).to_string(), + ]); + } + Program::Fentry(p) => { + table.add_row(vec!["Function Name:", &p.get_fn_name()?]); + } + Program::Fexit(p) => { + table.add_row(vec!["Function Name:", &p.get_fn_name()?]); + } + Program::Unsupported(_) => { + table.add_row(vec!["Unsupported Program Type", "None"]); + } + } + Ok(ProgTable(table)) + } + + pub(crate) fn new_kernel_info(r: &Program) -> Result { + let mut table = Table::new(); + + table.load_preset(comfy_table::presets::NOTHING); + table.set_header(vec![Cell::new("Kernel State") + .add_attribute(comfy_table::Attribute::Bold) + .add_attribute(comfy_table::Attribute::Underlined) + .fg(Color::Green)]); + + let p = r.get_data(); + let name = if p.get_kernel_name()?.is_empty() { + "None".to_string() + } else { + p.get_kernel_name()? + }; + + let rows = vec![ + vec!["Program ID:".to_string(), p.get_id()?.to_string()], + vec!["Name:".to_string(), name], + vec!["Type:".to_string(), format!("{}", r.kind())], + vec!["Loaded At:".to_string(), p.get_kernel_loaded_at()?], + vec!["Tag:".to_string(), p.get_kernel_tag()?], + vec![ + "GPL Compatible:".to_string(), + p.get_kernel_gpl_compatible()?.to_string(), + ], + vec![ + "Map IDs:".to_string(), + format!("{:?}", p.get_kernel_map_ids()?), + ], + vec!["BTF ID:".to_string(), p.get_kernel_btf_id()?.to_string()], + vec![ + "Size Translated (bytes):".to_string(), + p.get_kernel_bytes_xlated()?.to_string(), + ], + vec!["JITted:".to_string(), p.get_kernel_jited()?.to_string()], + vec![ + "Size JITted:".to_string(), + p.get_kernel_bytes_jited()?.to_string(), + ], + vec![ + "Kernel Allocated Memory (bytes):".to_string(), + p.get_kernel_bytes_memlock()?.to_string(), + ], + vec![ + "Verified Instruction Count:".to_string(), + p.get_kernel_verified_insns()?.to_string(), + ], + ]; + table.add_rows(rows); + Ok(ProgTable(table)) + } + + pub(crate) fn new_list() -> Self { + let mut table = Table::new(); + + table.load_preset(comfy_table::presets::NOTHING); + table.set_header(vec!["Program ID", "Name", "Type", "Load Time"]); + ProgTable(table) + } + + pub(crate) fn add_row_list( + &mut self, + id: String, + name: String, + type_: String, + load_time: String, + ) { + self.0.add_row(vec![id, name, type_, load_time]); + } + + pub(crate) fn add_response_prog(&mut self, r: Program) -> anyhow::Result<()> { + let data = r.get_data(); + + self.add_row_list( + data.get_id()?.to_string(), + data.get_kernel_name()?, + r.kind().to_string(), + data.get_kernel_loaded_at()?, + ); + + Ok(()) + } + + pub(crate) fn print(&self) { + println!("{self}\n") + } +} + +impl std::fmt::Display for ProgTable { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/bpfman/src/bin/cli/unload.rs b/bpfman/src/bin/cli/unload.rs new file mode 100644 index 000000000..b204e1412 --- /dev/null +++ b/bpfman/src/bin/cli/unload.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use bpfman::remove_program; + +use crate::args::UnloadArgs; + +pub(crate) async fn execute_unload(args: &UnloadArgs) -> Result<(), anyhow::Error> { + remove_program(args.program_id).await?; + Ok(()) +} diff --git a/bpfman/src/bpf.rs b/bpfman/src/bpf.rs deleted file mode 100644 index fc29d1f06..000000000 --- a/bpfman/src/bpf.rs +++ /dev/null @@ -1,1097 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use std::{ - collections::HashMap, - convert::TryInto, - path::{Path, PathBuf}, -}; - -use anyhow::anyhow; -use aya::{ - programs::{ - kprobe::KProbeLink, links::FdLink, loaded_programs, trace_point::TracePointLink, - uprobe::UProbeLink, KProbe, TracePoint, UProbe, - }, - BpfLoader, -}; -use bpfman_api::{ - config::Config, - util::directories::*, - ProbeType::{self, *}, - ProgramType, TcProceedOn, -}; -use log::{debug, info, warn}; -use tokio::{ - fs::{create_dir_all, read_dir, remove_dir_all}, - select, - sync::{ - mpsc::{Receiver, Sender}, - oneshot, - }, -}; - -use crate::{ - command::{ - BpfMap, Command, Direction, - Direction::{Egress, Ingress}, - Program, ProgramData, PullBytecodeArgs, TcProgram, UnloadArgs, - }, - errors::BpfmanError, - multiprog::{Dispatcher, DispatcherId, DispatcherInfo, TcDispatcher, XdpDispatcher}, - oci_utils::image_manager::Command as ImageManagerCommand, - serve::shutdown_handler, - utils::{get_ifindex, set_dir_permissions, should_map_be_pinned}, -}; - -const MAPS_MODE: u32 = 0o0660; - -pub(crate) struct BpfManager { - config: Config, - dispatchers: DispatcherMap, - programs: ProgramMap, - maps: HashMap, - commands: Receiver, - image_manager: Sender, -} - -pub(crate) struct ProgramMap { - programs: HashMap, -} - -impl ProgramMap { - fn new() -> Self { - ProgramMap { - programs: HashMap::new(), - } - } - - fn insert(&mut self, id: u32, prog: Program) -> Option { - self.programs.insert(id, prog) - } - - fn remove(&mut self, id: &u32) -> Option { - self.programs.remove(id) - } - - fn get_mut(&mut self, id: &u32) -> Option<&mut Program> { - self.programs.get_mut(id) - } - - fn get(&self, id: &u32) -> Option<&Program> { - self.programs.get(id) - } - - fn programs_mut<'a>( - &'a mut self, - program_type: &'a ProgramType, - if_index: &'a Option, - direction: &'a Option, - ) -> impl Iterator { - self.programs.values_mut().filter(|p| { - p.kind() == *program_type && p.if_index() == *if_index && p.direction() == *direction - }) - } - - // Sets the positions of programs that are to be attached via a dispatcher. - // Positions are set based on order of priority. Ties are broken based on: - // - Already attached programs are preferred - // - Program name. Lowest lexical order wins. - fn set_program_positions(&mut self, program: &mut Program, is_add: bool) { - let program_type = program.kind(); - let if_index = program.if_index(); - let direction = program.direction(); - - let mut extensions = self - .programs - .values_mut() - .filter(|p| { - p.kind() == program_type && p.if_index() == if_index && p.direction() == direction - }) - .collect::>(); - - if is_add { - // add program we're loading - extensions.push(program); - } - - extensions.sort_by_key(|b| { - ( - b.priority(), - b.attached().unwrap(), - b.data() - .expect("All Bpfman programs should have ProgramData") - .name() - .to_owned(), - ) - }); - for (i, v) in extensions.iter_mut().enumerate() { - v.set_position(Some(i)); - } - } - - fn get_programs_iter(&self) -> impl Iterator { - self.programs.values().map(|p| { - let kernel_info = p - .data() - .expect("All Bpfman programs should have ProgramData") - .kernel_info() - .expect("Loaded Bpfman programs should have kernel information"); - - (kernel_info.id, p) - }) - } -} - -pub(crate) struct DispatcherMap { - dispatchers: HashMap, -} - -impl DispatcherMap { - fn new() -> Self { - DispatcherMap { - dispatchers: HashMap::new(), - } - } - - fn remove(&mut self, id: &DispatcherId) -> Option { - self.dispatchers.remove(id) - } - - fn insert(&mut self, id: DispatcherId, dis: Dispatcher) -> Option { - self.dispatchers.insert(id, dis) - } - - /// Returns the number of extension programs currently attached to the dispatcher that - /// would be used to attach the provided [`Program`]. - fn attached_programs(&self, did: &DispatcherId) -> usize { - if let Some(d) = self.dispatchers.get(did) { - d.num_extensions() - } else { - 0 - } - } -} - -impl BpfManager { - pub(crate) fn new( - config: Config, - commands: Receiver, - image_manager: Sender, - ) -> Self { - Self { - config, - dispatchers: DispatcherMap::new(), - programs: ProgramMap::new(), - maps: HashMap::new(), - commands, - image_manager, - } - } - - pub(crate) async fn rebuild_state(&mut self) -> Result<(), anyhow::Error> { - debug!("BpfManager::rebuild_state()"); - let mut programs_dir = read_dir(RTDIR_PROGRAMS).await?; - while let Some(entry) = programs_dir.next_entry().await? { - let id = entry.file_name().to_string_lossy().parse().unwrap(); - let mut program = Program::load(id) - .map_err(|e| BpfmanError::Error(format!("cant read program state {e}")))?; - // TODO: Should probably check for pinned prog on bpffs rather than assuming they are attached - program.set_attached(); - debug!("rebuilding state for program {}", id); - self.rebuild_map_entry(id, &mut program).await; - self.programs.insert(id, program); - } - self.rebuild_dispatcher_state(ProgramType::Xdp, None, RTDIR_XDP_DISPATCHER) - .await?; - self.rebuild_dispatcher_state(ProgramType::Tc, Some(Ingress), RTDIR_TC_INGRESS_DISPATCHER) - .await?; - self.rebuild_dispatcher_state(ProgramType::Tc, Some(Egress), RTDIR_TC_EGRESS_DISPATCHER) - .await?; - - Ok(()) - } - - pub(crate) async fn rebuild_dispatcher_state( - &mut self, - program_type: ProgramType, - direction: Option, - path: &str, - ) -> Result<(), anyhow::Error> { - let mut dispatcher_dir = read_dir(path).await?; - while let Some(entry) = dispatcher_dir.next_entry().await? { - let name = entry.file_name(); - let parts: Vec<&str> = name.to_str().unwrap().split('_').collect(); - if parts.len() != 2 { - continue; - } - let if_index: u32 = parts[0].parse().unwrap(); - let revision: u32 = parts[1].parse().unwrap(); - match program_type { - ProgramType::Xdp => { - let dispatcher = XdpDispatcher::load(if_index, revision).unwrap(); - self.dispatchers.insert( - DispatcherId::Xdp(DispatcherInfo(if_index, None)), - Dispatcher::Xdp(dispatcher), - ); - } - ProgramType::Tc => { - let direction = direction.expect("direction required for tc programs"); - - let dispatcher = TcDispatcher::load(if_index, direction, revision).unwrap(); - let did = DispatcherId::Tc(DispatcherInfo(if_index, Some(direction))); - - self.dispatchers.insert( - DispatcherId::Tc(DispatcherInfo(if_index, Some(direction))), - Dispatcher::Tc(dispatcher), - ); - - // This is just used to collect and sort the dispatcher's - // programs in `rebuild_multiattach_dispatcher`. - // TODO(astoycos) rebuilding dispacthers need to be actual helpers - // on BpfManager.Dispatchers not BpfManager itself. - let fake_prog_filter = Program::Tc(TcProgram { - data: ProgramData::default(), - priority: 0, - if_index: Some(if_index), - iface: String::new(), - proceed_on: TcProceedOn::default(), - direction, - current_position: None, - attached: false, - }); - - self.rebuild_multiattach_dispatcher(fake_prog_filter, did) - .await?; - } - _ => return Err(anyhow!("invalid program type {:?}", program_type)), - } - } - - Ok(()) - } - - pub(crate) async fn add_program( - &mut self, - mut program: Program, - ) -> Result { - let map_owner_id = program.data()?.map_owner_id(); - // Set map_pin_path if we're using another program's maps - if let Some(map_owner_id) = map_owner_id { - let map_pin_path = self.is_map_owner_id_valid(map_owner_id)?; - program - .data_mut()? - .set_map_pin_path(Some(map_pin_path.clone())); - } - - program - .data_mut()? - .set_program_bytes(self.image_manager.clone()) - .await?; - - let result = match program { - Program::Xdp(_) | Program::Tc(_) => { - program.set_if_index(get_ifindex(&program.if_name().unwrap())?); - - self.add_multi_attach_program(&mut program).await - } - Program::Tracepoint(_) | Program::Kprobe(_) | Program::Uprobe(_) => { - self.add_single_attach_program(&mut program).await - } - Program::Unsupported(_) => panic!("Cannot add unsupported program"), - }; - - // Program bytes MUST be cleared after load. - program.data_mut()?.clear_program_bytes(); - - match result { - Ok(id) => { - info!( - "Added {} program with name: {} and id: {id}", - program.kind(), - program.data()?.name() - ); - - // Now that program is successfully loaded, update the id, maps hash table, - // and allow access to all maps by bpfman group members. - self.save_map(&mut program, id, map_owner_id).await?; - - // Only add program to bpfManager if we've completed all mutations and it's successfully loaded. - self.programs.insert(id, program.to_owned()); - - Ok(program) - } - Err(e) => { - // Cleanup any directories associated with the map_pin_path. - // Data and map_pin_path may or may not exist depending on where the original - // error occured, so don't error if not there and preserve original error. - if let Ok(data) = program.data() { - if let Some(pin_path) = data.map_pin_path() { - let _ = self.cleanup_map_pin_path(pin_path, map_owner_id).await; - } - } - Err(e) - } - } - } - - pub(crate) async fn add_multi_attach_program( - &mut self, - program: &mut Program, - ) -> Result { - debug!("BpfManager::add_multi_attach_program()"); - let name = program.data()?.name(); - - // This load is just to verify the BPF Function Name is valid. - // The actual load is performed in the XDP or TC logic. - // don't pin maps here. - let mut ext_loader = BpfLoader::new() - .allow_unsupported_maps() - .extension(name) - .load(program.data()?.program_bytes())?; - - match ext_loader.program_mut(name) { - Some(_) => Ok(()), - None => Err(BpfmanError::BpfFunctionNameNotValid(name.to_owned())), - }?; - - let did = program - .dispatcher_id() - .ok_or(BpfmanError::DispatcherNotRequired)?; - - let next_available_id = self.dispatchers.attached_programs(&did); - if next_available_id >= 10 { - return Err(BpfmanError::TooManyPrograms); - } - - debug!("next_available_id={next_available_id}"); - - let program_type = program.kind(); - let if_index = program.if_index(); - let if_name = program.if_name().unwrap(); - let direction = program.direction(); - - self.programs.set_program_positions(program, true); - - let mut programs: Vec<&mut Program> = self - .programs - .programs_mut(&program_type, &if_index, &direction) - .collect::>(); - - // add the program that's being loaded - programs.push(program); - - let old_dispatcher = self.dispatchers.remove(&did); - let if_config = if let Some(ref i) = self.config.interfaces { - i.get(&if_name) - } else { - None - }; - let next_revision = if let Some(ref old) = old_dispatcher { - old.next_revision() - } else { - 1 - }; - - let dispatcher = Dispatcher::new( - if_config, - &mut programs, - next_revision, - old_dispatcher, - self.image_manager.clone(), - ) - .await - .or_else(|e| { - // If kernel ID was never set there's no pins to cleanup here so just continue - if let Some(info) = program.kernel_info() { - program - .delete(info.id) - .map_err(BpfmanError::BpfmanProgramDeleteError)?; - } - Err(e) - })?; - - self.dispatchers.insert(did, dispatcher); - let id = program - .kernel_info() - .expect("kernel info should be set after load") - .id; - - program.set_attached(); - program - .save(id) - .map_err(|e| BpfmanError::Error(format!("unable to save program state: {e}")))?; - - Ok(id) - } - - pub(crate) async fn add_single_attach_program( - &mut self, - p: &mut Program, - ) -> Result { - debug!("BpfManager::add_single_attach_program()"); - let name = p.data()?.name(); - let mut bpf = BpfLoader::new(); - - for (key, value) in p.data()?.global_data() { - bpf.set_global(key, value.as_slice(), true); - } - - // If map_pin_path is set already it means we need to use a pin - // path which should already exist on the system. - if let Some(map_pin_path) = p.data()?.map_pin_path() { - debug!( - "single-attach program {name} is using maps from {:?}", - map_pin_path - ); - bpf.map_pin_path(map_pin_path); - } - - let mut loader = bpf - .allow_unsupported_maps() - .load(p.data()?.program_bytes())?; - - let raw_program = loader - .program_mut(name) - .ok_or(BpfmanError::BpfFunctionNameNotValid(name.to_owned()))?; - - let res = match p { - Program::Tracepoint(ref mut program) => { - let parts: Vec<&str> = program.tracepoint.split('/').collect(); - if parts.len() != 2 { - return Err(BpfmanError::InvalidAttach(program.tracepoint.to_string())); - } - let category = parts[0].to_owned(); - let name = parts[1].to_owned(); - - let tracepoint: &mut TracePoint = raw_program.try_into()?; - - tracepoint.load()?; - program - .data - .set_kernel_info(Some(tracepoint.info()?.try_into()?)); - - let link_id = tracepoint.attach(&category, &name)?; - - let owned_link: TracePointLink = tracepoint.take_link(link_id)?; - let fd_link: FdLink = owned_link - .try_into() - .expect("unable to get owned tracepoint attach link"); - - let id = program.data.id().expect("id should be set after load"); - - fd_link - .pin(format!("{RTDIR_FS}/prog_{}_link", id)) - .map_err(BpfmanError::UnableToPinLink)?; - - tracepoint - .pin(format!("{RTDIR_FS}/prog_{id}")) - .map_err(BpfmanError::UnableToPinProgram)?; - - Ok(id) - } - Program::Kprobe(ref mut program) => { - let requested_probe_type = match program.retprobe { - true => Kretprobe, - false => Kprobe, - }; - - if requested_probe_type == Kretprobe && program.offset != 0 { - return Err(BpfmanError::Error(format!( - "offset not allowed for {Kretprobe}" - ))); - } - - let kprobe: &mut KProbe = raw_program.try_into()?; - kprobe.load()?; - - // verify that the program loaded was the same type as the - // user requested - let loaded_probe_type = ProbeType::from(kprobe.kind()); - if requested_probe_type != loaded_probe_type { - return Err(BpfmanError::Error(format!( - "expected {requested_probe_type}, loaded program is {loaded_probe_type}" - ))); - } - - program - .data - .set_kernel_info(Some(kprobe.info()?.try_into()?)); - - let link_id = kprobe.attach(program.fn_name.as_str(), program.offset)?; - - let owned_link: KProbeLink = kprobe.take_link(link_id)?; - let fd_link: FdLink = owned_link - .try_into() - .expect("unable to get owned kprobe attach link"); - - let id = program.data.id().expect("id should be set after load"); - - fd_link - .pin(format!("{RTDIR_FS}/prog_{}_link", id)) - .map_err(BpfmanError::UnableToPinLink)?; - - kprobe - .pin(format!("{RTDIR_FS}/prog_{id}")) - .map_err(BpfmanError::UnableToPinProgram)?; - - Ok(id) - } - Program::Uprobe(ref mut program) => { - let requested_probe_type = match program.retprobe { - true => Uretprobe, - false => Uprobe, - }; - - let uprobe: &mut UProbe = raw_program.try_into()?; - uprobe.load()?; - - // verify that the program loaded was the same type as the - // user requested - let loaded_probe_type = ProbeType::from(uprobe.kind()); - if requested_probe_type != loaded_probe_type { - return Err(BpfmanError::Error(format!( - "expected {requested_probe_type}, loaded program is {loaded_probe_type}" - ))); - } - - program - .data - .set_kernel_info(Some(uprobe.info()?.try_into()?)); - - let link_id = uprobe.attach( - program.fn_name.as_deref(), - program.offset, - program.target.clone(), - program.pid, - )?; - - let owned_link: UProbeLink = uprobe.take_link(link_id)?; - let fd_link: FdLink = owned_link - .try_into() - .expect("unable to get owned uprobe attach link"); - - let id = program.data.id().expect("id should be set after load"); - - fd_link - .pin(format!("{RTDIR_FS}/prog_{}_link", id)) - .map_err(BpfmanError::UnableToPinLink)?; - - uprobe - .pin(format!("{RTDIR_FS}/prog_{id}")) - .map_err(BpfmanError::UnableToPinProgram)?; - - Ok(id) - } - _ => panic!("not a supported single attach program"), - }; - - match res { - Ok(id) => { - // If this program is the map(s) owner pin all maps (except for .rodata and .bss) by name. - if p.data()?.map_pin_path().is_none() { - let map_pin_path = calc_map_pin_path(id); - p.data_mut()?.set_map_pin_path(Some(map_pin_path.clone())); - create_map_pin_path(&map_pin_path).await?; - - for (name, map) in loader.maps_mut() { - if !should_map_be_pinned(name) { - continue; - } - debug!( - "Pinning map: {name} to path: {}", - map_pin_path.join(name).display() - ); - map.pin(map_pin_path.join(name)) - .map_err(BpfmanError::UnableToPinMap)?; - } - } - - p.save(id) - // we might want to log or ignore this error instead of returning here... - // because otherwise it will hide the original error (from res above) - .map_err(|_| { - BpfmanError::Error("unable to persist program data".to_string()) - })?; - } - Err(_) => { - // If kernel ID was never set there's no pins to cleanup here so just continue - if let Some(info) = p.kernel_info() { - p.delete(info.id) - .map_err(BpfmanError::BpfmanProgramDeleteError)?; - }; - } - }; - - res - } - - pub(crate) async fn remove_program(&mut self, id: u32) -> Result<(), BpfmanError> { - info!("Removing program with id: {id}"); - let mut prog = match self.programs.remove(&id) { - Some(p) => p, - None => { - return Err(BpfmanError::Error(format!( - "Program {0} does not exist or was not created by bpfman", - id, - ))); - } - }; - - let map_owner_id = prog.data()?.map_owner_id(); - - prog.delete(id) - .map_err(BpfmanError::BpfmanProgramDeleteError)?; - - match prog { - Program::Xdp(_) | Program::Tc(_) => self.remove_multi_attach_program(&mut prog).await?, - Program::Tracepoint(_) - | Program::Kprobe(_) - | Program::Uprobe(_) - | Program::Unsupported(_) => (), - } - - self.delete_map(id, map_owner_id).await?; - Ok(()) - } - - pub(crate) async fn remove_multi_attach_program( - &mut self, - program: &mut Program, - ) -> Result<(), BpfmanError> { - debug!("BpfManager::remove_multi_attach_program()"); - - let did = program - .dispatcher_id() - .ok_or(BpfmanError::DispatcherNotRequired)?; - - let next_available_id = self.dispatchers.attached_programs(&did) - 1; - debug!("next_available_id = {next_available_id}"); - - let mut old_dispatcher = self.dispatchers.remove(&did); - - if let Some(ref mut old) = old_dispatcher { - if next_available_id == 0 { - // Delete the dispatcher - return old.delete(true); - } - } - - self.programs.set_program_positions(program, false); - - let program_type = program.kind(); - let if_index = program.if_index(); - let if_name = program.if_name().unwrap(); - let direction = program.direction(); - - // Intentionally don't add filter program here - let mut programs: Vec<&mut Program> = self - .programs - .programs_mut(&program_type, &if_index, &direction) - .collect(); - - let if_config = if let Some(ref i) = self.config.interfaces { - i.get(&if_name) - } else { - None - }; - let next_revision = if let Some(ref old) = old_dispatcher { - old.next_revision() - } else { - 1 - }; - debug!("next_revision = {next_revision}"); - let dispatcher = Dispatcher::new( - if_config, - &mut programs, - next_revision, - old_dispatcher, - self.image_manager.clone(), - ) - .await?; - self.dispatchers.insert(did, dispatcher); - Ok(()) - } - - pub(crate) async fn rebuild_multiattach_dispatcher( - &mut self, - mut filter_prog: Program, - did: DispatcherId, - ) -> Result<(), BpfmanError> { - let program_type = filter_prog.kind(); - let if_index = filter_prog.if_index(); - let direction = filter_prog.direction(); - - debug!("BpfManager::rebuild_multiattach_dispatcher() for program type {program_type} on if_index {if_index:?}"); - let mut old_dispatcher = self.dispatchers.remove(&did); - - if let Some(ref mut old) = old_dispatcher { - debug!("Rebuild Multiattach Dispatcher for {did:?}"); - self.programs.set_program_positions(&mut filter_prog, false); - - let mut programs: Vec<&mut Program> = self - .programs - .programs_mut(&program_type, &if_index, &direction) - .collect(); - - debug!("programs loaded: {}", programs.len()); - - // The following checks should have been done when the dispatcher was built, but check again to confirm - if programs.is_empty() { - return old.delete(true); - } else if programs.len() > 10 { - return Err(BpfmanError::TooManyPrograms); - } - - let if_name = old.if_name(); - let if_config = if let Some(ref i) = self.config.interfaces { - i.get(&if_name) - } else { - None - }; - - let next_revision = if let Some(ref old) = old_dispatcher { - old.next_revision() - } else { - 1 - }; - - let dispatcher = Dispatcher::new( - if_config, - &mut programs, - next_revision, - old_dispatcher, - self.image_manager.clone(), - ) - .await?; - self.dispatchers.insert(did, dispatcher); - } else { - debug!("No dispatcher found in rebuild_multiattach_dispatcher() for {did:?}"); - } - Ok(()) - } - - pub(crate) fn list_programs(&mut self) -> Result, BpfmanError> { - debug!("BpfManager::list_programs()"); - - // Get an iterator for the bpfman load programs, a hash map indexed by program id. - let mut bpfman_progs: HashMap = self.programs.get_programs_iter().collect(); - - // Call Aya to get ALL the loaded eBPF programs, and loop through each one. - loaded_programs() - .map(|p| { - let prog = p.map_err(BpfmanError::BpfProgramError)?; - let prog_id = prog.id(); - - // If the program was loaded by bpfman (check the hash map), then us it. - // Otherwise, convert the data returned from Aya into an Unsupported Program Object. - match bpfman_progs.remove(&prog_id) { - Some(p) => Ok(p.to_owned()), - None => Ok(Program::Unsupported(prog.try_into()?)), - } - }) - .collect() - } - - pub(crate) fn get_program(&mut self, id: u32) -> Result { - debug!("Getting program with id: {id}"); - // If the program was loaded by bpfman, then use it. - // Otherwise, call Aya to get ALL the loaded eBPF programs, and convert the data - // returned from Aya into an Unsupported Program Object. - match self.programs.get(&id) { - Some(p) => Ok(p.to_owned()), - None => loaded_programs() - .find_map(|p| { - let prog = p.ok()?; - if prog.id() == id { - Some(Program::Unsupported(prog.try_into().ok()?)) - } else { - None - } - }) - .ok_or(BpfmanError::Error(format!( - "Program {0} does not exist", - id - ))), - } - } - - async fn pull_bytecode(&self, args: PullBytecodeArgs) -> anyhow::Result<()> { - let (tx, rx) = oneshot::channel(); - self.image_manager - .send(ImageManagerCommand::Pull { - image: args.image.image_url, - pull_policy: args.image.image_pull_policy.clone(), - username: args.image.username.clone(), - password: args.image.password.clone(), - resp: tx, - }) - .await?; - let res = match rx.await? { - Ok(_) => { - info!("Successfully pulled bytecode"); - Ok(()) - } - Err(e) => Err(e).map_err(|e| BpfmanError::BpfBytecodeError(e.into())), - }; - let _ = args.responder.send(res); - Ok(()) - } - - pub(crate) async fn process_commands(&mut self) { - loop { - // Start receiving messages - select! { - biased; - _ = shutdown_handler() => { - info!("Signal received to stop command processing"); - break; - } - Some(cmd) = self.commands.recv() => { - match cmd { - Command::Load(args) => { - let prog = self.add_program(args.program).await; - // Ignore errors as they'll be propagated to caller in the RPC status - let _ = args.responder.send(prog); - }, - Command::Unload(args) => self.unload_command(args).await.unwrap(), - Command::List { responder } => { - let progs = self.list_programs(); - // Ignore errors as they'll be propagated to caller in the RPC status - let _ = responder.send(progs); - } - Command::Get(args) => { - let prog = self.get_program(args.id); - // Ignore errors as they'll be propagated to caller in the RPC status - let _ = args.responder.send(prog); - }, - Command::PullBytecode (args) => self.pull_bytecode(args).await.unwrap(), - } - } - } - } - info!("Stopping processing commands"); - } - - async fn unload_command(&mut self, args: UnloadArgs) -> anyhow::Result<()> { - let res = self.remove_program(args.id).await; - // Ignore errors as they'll be propagated to caller in the RPC status - let _ = args.responder.send(res); - Ok(()) - } - - // This function checks to see if the user provided map_owner_id is valid. - fn is_map_owner_id_valid(&mut self, map_owner_id: u32) -> Result { - let map_pin_path = calc_map_pin_path(map_owner_id); - - if self.maps.contains_key(&map_owner_id) { - // Return the map_pin_path - return Ok(map_pin_path); - } - Err(BpfmanError::Error( - "map_owner_id does not exists".to_string(), - )) - } - - // This function is called if the program's map directory was created, - // but the eBPF program failed to load. save_map() has not been called, - // so self.maps has not been updated for this program. - // If the user provided a ID of program to share a map with, - // then map the directory is still in use and there is nothing to do. - // Otherwise, the map directory was created so it must - // deleted. - async fn cleanup_map_pin_path( - &mut self, - map_pin_path: &Path, - map_owner_id: Option, - ) -> Result<(), BpfmanError> { - if map_owner_id.is_none() { - let _ = remove_dir_all(map_pin_path) - .await - .map_err(|e| BpfmanError::Error(format!("can't delete map dir: {e}"))); - Ok(()) - } else { - Ok(()) - } - } - - // This function writes the map to the map hash table. If this eBPF - // program is the map owner, then a new entry is add to the map hash - // table and permissions on the directory are updated to grant bpfman - // user group access to all the maps in the directory. If this eBPF - // program is not the owner, then the eBPF program ID is added to - // the Used-By array. - - // TODO this should probably be program.save_map not bpfmanager.save_map - async fn save_map( - &mut self, - program: &mut Program, - id: u32, - map_owner_id: Option, - ) -> Result<(), BpfmanError> { - let data = program.data_mut()?; - - match map_owner_id { - Some(m) => { - if let Some(map) = self.maps.get_mut(&m) { - map.used_by.push(id); - - // This program has no been inserted yet, so set map_used_by to - // newly updated list. - data.set_maps_used_by(Some(map.used_by.clone())); - - // Update all the programs using the same map with the updated map_used_by. - for used_by_id in map.used_by.iter() { - if let Some(program) = self.programs.get_mut(used_by_id) { - if let Ok(data) = program.data_mut() { - data.set_maps_used_by(Some(map.used_by.clone())); - } else { - return Err(BpfmanError::Error( - "unable to retrieve data for {id}".to_string(), - )); - } - } - } - } else { - return Err(BpfmanError::Error( - "map_owner_id does not exists".to_string(), - )); - } - } - None => { - let map = BpfMap { used_by: vec![id] }; - - self.maps.insert(id, map); - - // Update this program with the updated map_used_by - data.set_maps_used_by(Some(vec![id])); - - // Set the permissions on the map_pin_path directory. - if let Some(map_pin_path) = data.map_pin_path() { - if let Some(path) = map_pin_path.to_str() { - debug!("bpf set dir permissions for {}", path); - set_dir_permissions(path, MAPS_MODE).await; - } else { - return Err(BpfmanError::Error(format!( - "invalid map_pin_path {} for {}", - map_pin_path.display(), - id - ))); - } - } else { - return Err(BpfmanError::Error(format!( - "map_pin_path should be set for {}", - id - ))); - } - } - } - - Ok(()) - } - - // This function cleans up a map entry when an eBPF program is - // being unloaded. If the eBPF program is the map owner, then - // the map is removed from the hash table and the associated - // directory is removed. If this eBPF program is referencing a - // map from another eBPF program, then this eBPF programs ID - // is removed from the UsedBy array. - async fn delete_map(&mut self, id: u32, map_owner_id: Option) -> Result<(), BpfmanError> { - let index = match map_owner_id { - Some(i) => i, - None => id, - }; - - if let Some(map) = self.maps.get_mut(&index.clone()) { - if let Some(index) = map.used_by.iter().position(|value| *value == id) { - map.used_by.swap_remove(index); - } - - if map.used_by.is_empty() { - // No more programs using this map, so remove the entry from the map list. - let path = calc_map_pin_path(index); - self.maps.remove(&index.clone()); - remove_dir_all(path) - .await - .map_err(|e| BpfmanError::Error(format!("can't delete map dir: {e}")))?; - } else { - // Update all the programs still using the same map with the updated map_used_by. - for id in map.used_by.iter() { - if let Some(program) = self.programs.get_mut(id) { - if let Ok(data) = program.data_mut() { - data.set_maps_used_by(Some(map.used_by.clone())); - } - } - } - } - } else { - return Err(BpfmanError::Error( - "map_pin_path does not exists".to_string(), - )); - } - - Ok(()) - } - - async fn rebuild_map_entry(&mut self, id: u32, program: &mut Program) { - let map_owner_id = match program.data() { - Ok(data) => data.map_owner_id(), - Err(_) => { - warn!("unable to retrieve data for {id} retrieving map_owner_id on rebuild"); - return; - } - }; - let index = match map_owner_id { - Some(i) => i, - None => id, - }; - - if let Some(map) = self.maps.get_mut(&index) { - map.used_by.push(id); - - // This program has not been inserted yet, so update it with the - // updated map_used_by. - if let Ok(data) = program.data_mut() { - data.set_maps_used_by(Some(map.used_by.clone())); - } else { - warn!("unable to retrieve data for {id} during rebuild of maps"); - } - - // Update all the other programs using the same map with the updated map_used_by. - for used_by_id in map.used_by.iter() { - // program may not exist yet on rebuild, so ignore if not there - if let Some(prog) = self.programs.get_mut(used_by_id) { - if let Ok(data) = prog.data_mut() { - data.set_maps_used_by(Some(map.used_by.clone())); - } else { - warn!("unable to retrieve data for {used_by_id} when setting map_used_by on rebuild"); - } - } - } - } else { - let map = BpfMap { used_by: vec![id] }; - self.maps.insert(index, map); - - if let Ok(data) = program.data_mut() { - data.set_maps_used_by(Some(vec![id])); - } else { - warn!("unable to retrieve data for {id} during rebuild of maps"); - } - } - } -} - -// map_pin_path is a the directory the maps are located. Currently, it -// is a fixed bpfman location containing the map_index, which is a ID. -// The ID is either the programs ID, or the ID of another program -// that map_owner_id references. -pub fn calc_map_pin_path(id: u32) -> PathBuf { - PathBuf::from(format!("{RTDIR_FS_MAPS}/{}", id)) -} - -// Create the map_pin_path for a given program. -pub async fn create_map_pin_path(p: &Path) -> Result<(), BpfmanError> { - create_dir_all(p) - .await - .map_err(|e| BpfmanError::Error(format!("can't create map dir: {e}"))) -} diff --git a/bpfman/src/cli/get.rs b/bpfman/src/cli/get.rs deleted file mode 100644 index 9c7e4a706..000000000 --- a/bpfman/src/cli/get.rs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use bpfman_api::{ - config::Config, - v1::{bpfman_client::BpfmanClient, GetRequest}, -}; -use clap::Args; - -use crate::cli::{select_channel, table::ProgTable}; - -#[derive(Args, Debug)] -pub(crate) struct GetArgs { - /// Required: Program id to get. - id: u32, -} - -pub(crate) fn execute_get(args: &GetArgs, config: &mut Config) -> Result<(), anyhow::Error> { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { - let channel = select_channel(config).expect("failed to select channel"); - let mut client = BpfmanClient::new(channel); - let request = tonic::Request::new(GetRequest { id: args.id }); - let response = client.get(request).await?.into_inner(); - - ProgTable::new_get_bpfman(&response.info)?.print(); - ProgTable::new_get_unsupported(&response.kernel_info)?.print(); - Ok::<(), anyhow::Error>(()) - }) -} diff --git a/bpfman/src/cli/image.rs b/bpfman/src/cli/image.rs deleted file mode 100644 index bf948f561..000000000 --- a/bpfman/src/cli/image.rs +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use base64::{engine::general_purpose, Engine}; -use bpfman_api::{ - config::Config, - v1::{bpfman_client::BpfmanClient, BytecodeImage, PullBytecodeRequest}, - ImagePullPolicy, -}; -use clap::{Args, Subcommand}; - -use crate::cli::select_channel; - -#[derive(Subcommand, Debug)] -pub(crate) enum ImageSubCommand { - /// Pull an eBPF bytecode image from a remote registry. - Pull(PullBytecodeArgs), -} - -impl ImageSubCommand { - pub(crate) fn execute(&self, config: &mut Config) -> anyhow::Result<()> { - match self { - ImageSubCommand::Pull(args) => execute_pull(args, config), - } - } -} - -#[derive(Args, Debug)] -pub(crate) struct PullBytecodeArgs { - /// Required: Container Image URL. - /// Example: --image-url quay.io/bpfman-bytecode/xdp_pass:latest - #[clap(short, long, verbatim_doc_comment)] - pub(crate) image_url: String, - - /// Optional: Registry auth for authenticating with the specified image registry. - /// This should be base64 encoded from the ':' string just like - /// it's stored in the docker/podman host config. - /// Example: --registry_auth "YnjrcKw63PhDcQodiU9hYxQ2" - #[clap(short, long, verbatim_doc_comment)] - registry_auth: Option, - - /// Optional: Pull policy for remote images. - /// - /// [possible values: Always, IfNotPresent, Never] - #[clap(short, long, verbatim_doc_comment, default_value = "IfNotPresent")] - pull_policy: String, -} - -impl TryFrom<&PullBytecodeArgs> for BytecodeImage { - type Error = anyhow::Error; - - fn try_from(value: &PullBytecodeArgs) -> Result { - let pull_policy: ImagePullPolicy = value.pull_policy.as_str().try_into()?; - let (username, password) = match &value.registry_auth { - Some(a) => { - let auth_raw = general_purpose::STANDARD.decode(a)?; - let auth_string = String::from_utf8(auth_raw)?; - let (username, password) = auth_string.split_once(':').unwrap(); - (username.to_owned(), password.to_owned()) - } - None => ("".to_owned(), "".to_owned()), - }; - - Ok(BytecodeImage { - url: value.image_url.clone(), - image_pull_policy: pull_policy.into(), - username: Some(username), - password: Some(password), - }) - } -} - -pub(crate) fn execute_pull(args: &PullBytecodeArgs, config: &mut Config) -> anyhow::Result<()> { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { - let channel = select_channel(config).expect("failed to select channel"); - let mut client = BpfmanClient::new(channel); - let image: BytecodeImage = args.try_into()?; - let request = tonic::Request::new(PullBytecodeRequest { image: Some(image) }); - let _response = client.pull_bytecode(request).await?; - Ok::<(), anyhow::Error>(()) - }) -} diff --git a/bpfman/src/cli/list.rs b/bpfman/src/cli/list.rs deleted file mode 100644 index 50627c14c..000000000 --- a/bpfman/src/cli/list.rs +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use anyhow::bail; -use bpfman_api::{ - config::Config, - v1::{bpfman_client::BpfmanClient, ListRequest}, - ProgramType, -}; -use clap::Args; - -use crate::cli::{parse_key_val, select_channel, table::ProgTable}; - -#[derive(Args, Debug)] -pub(crate) struct ListArgs { - /// Optional: List a specific program type - /// Example: --program-type xdp - /// - /// [possible values: unspec, socket-filter, kprobe, tc, sched-act, - /// tracepoint, xdp, perf-event, cgroup-skb, - /// cgroup-sock, lwt-in, lwt-out, lwt-xmit, sock-ops, - /// sk-skb, cgroup-device, sk-msg, raw-tracepoint, - /// cgroup-sock-addr, lwt-seg6-local, lirc-mode2, - /// sk-reuseport, flow-dissector, cgroup-sysctl, - /// raw-tracepoint-writable, cgroup-sockopt, tracing, - /// struct-ops, ext, lsm, sk-lookup, syscall] - #[clap(short, long, verbatim_doc_comment, hide_possible_values = true)] - program_type: Option, - - /// Optional: List programs which contain a specific set of metadata labels - /// that were applied when the program was loaded with `--metadata` parameter. - /// Format: = - /// - /// Example: --metadata-selector owner=acme - #[clap(short, long, verbatim_doc_comment, value_parser=parse_key_val, value_delimiter = ',')] - metadata_selector: Option>, - - /// Optional: List all programs. - #[clap(short, long, verbatim_doc_comment)] - all: bool, -} - -pub(crate) fn execute_list(args: &ListArgs, config: &mut Config) -> anyhow::Result<()> { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { - let channel = select_channel(config).unwrap(); - let mut client = BpfmanClient::new(channel); - let prog_type_filter = args.program_type.map(|p| p as u32); - - let request = tonic::Request::new(ListRequest { - program_type: prog_type_filter, - // Transform metadata from a vec of tuples to an owned map. - match_metadata: args - .metadata_selector - .clone() - .unwrap_or_default() - .iter() - .map(|(k, v)| (k.to_owned(), v.to_owned())) - .collect(), - bpfman_programs_only: Some(!args.all), - }); - let response = client.list(request).await?.into_inner(); - let mut table = ProgTable::new_list(); - - for r in response.results { - if let Err(e) = table.add_response_prog(r) { - bail!(e) - } - } - table.print(); - Ok::<(), anyhow::Error>(()) - }) -} diff --git a/bpfman/src/cli/load/file.rs b/bpfman/src/cli/load/file.rs deleted file mode 100644 index a2e02bcd5..000000000 --- a/bpfman/src/cli/load/file.rs +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use bpfman_api::{ - config::Config, - v1::{bpfman_client::BpfmanClient, bytecode_location::Location, BytecodeLocation, LoadRequest}, -}; -use clap::Args; - -use crate::cli::{ - load::programs::{parse_global, parse_global_arg, GlobalArg, LoadCommands}, - parse_key_val, select_channel, - table::ProgTable, -}; - -#[derive(Args, Debug)] -pub(crate) struct LoadFileArgs { - /// Required: Location of local bytecode file - /// Example: --path /run/bpfman/examples/go-xdp-counter/bpf_bpfel.o - #[clap(short, long, verbatim_doc_comment)] - pub(crate) path: String, - - /// Required: The name of the function that is the entry point for the BPF program. - #[clap(short, long)] - name: String, - - /// Optional: Global variables to be set when program is loaded. - /// Format: = - /// - /// This is a very low level primitive. The caller is responsible for formatting - /// the byte string appropriately considering such things as size, endianness, - /// alignment and packing of data structures. - #[clap(short, long, verbatim_doc_comment, num_args(1..), value_parser=parse_global_arg)] - global: Option>, - - /// Optional: Specify Key/Value metadata to be attached to a program when it - /// is loaded by bpfman. - /// Format: = - /// - /// This can later be used to `list` a certain subset of programs which contain - /// the specified metadata. - /// Example: --metadata owner=acme - #[clap(short, long, verbatim_doc_comment, value_parser=parse_key_val, value_delimiter = ',')] - metadata: Option>, - - /// Optional: Program id of loaded eBPF program this eBPF program will share a map with. - /// Only used when multiple eBPF programs need to share a map. - /// Example: --map-owner-id 63178 - #[clap(long, verbatim_doc_comment)] - map_owner_id: Option, - - #[clap(subcommand)] - pub(crate) command: LoadCommands, -} - -pub(crate) fn execute_load_file(args: &LoadFileArgs, config: &mut Config) -> anyhow::Result<()> { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { - let channel = select_channel(config).expect("failed to select channel"); - let mut client = BpfmanClient::new(channel); - - let bytecode = Some(BytecodeLocation { - location: Some(Location::File(args.path.clone())), - }); - - let attach = args.command.get_attach_type()?; - - let request = tonic::Request::new(LoadRequest { - bytecode, - name: args.name.to_string(), - program_type: args.command.get_prog_type() as u32, - attach, - metadata: args - .metadata - .clone() - .unwrap_or_default() - .iter() - .map(|(k, v)| (k.to_owned(), v.to_owned())) - .collect(), - global_data: parse_global(&args.global), - uuid: None, - map_owner_id: args.map_owner_id, - }); - let response = client.load(request).await?.into_inner(); - - ProgTable::new_get_bpfman(&response.info)?.print(); - ProgTable::new_get_unsupported(&response.kernel_info)?.print(); - Ok::<(), anyhow::Error>(()) - }) -} diff --git a/bpfman/src/cli/load/image.rs b/bpfman/src/cli/load/image.rs deleted file mode 100644 index ebfa62bdf..000000000 --- a/bpfman/src/cli/load/image.rs +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use bpfman_api::{ - config::Config, - v1::{ - bpfman_client::BpfmanClient, bytecode_location::Location, BytecodeImage, BytecodeLocation, - LoadRequest, - }, -}; -use clap::Args; - -use crate::cli::{ - image::PullBytecodeArgs, - load::programs::{parse_global, parse_global_arg, GlobalArg, LoadCommands}, - parse_key_val, select_channel, - table::ProgTable, -}; - -#[derive(Args, Debug)] -pub(crate) struct LoadImageArgs { - /// Specify how the bytecode image should be pulled. - #[command(flatten)] - pub(crate) pull_args: PullBytecodeArgs, - - /// Optional: The name of the function that is the entry point for the BPF program. - /// If not provided, the program name defined as part of the bytecode image will be used. - #[clap(short, long, verbatim_doc_comment, default_value = "")] - name: String, - - /// Optional: Global variables to be set when program is loaded. - /// Format: = - /// - /// This is a very low level primitive. The caller is responsible for formatting - /// the byte string appropriately considering such things as size, endianness, - /// alignment and packing of data structures. - #[clap(short, long, verbatim_doc_comment, num_args(1..), value_parser=parse_global_arg)] - global: Option>, - - /// Optional: Specify Key/Value metadata to be attached to a program when it - /// is loaded by bpfman. - /// Format: = - /// - /// This can later be used to list a certain subset of programs which contain - /// the specified metadata. - /// Example: --metadata owner=acme - #[clap(short, long, verbatim_doc_comment, value_parser=parse_key_val, value_delimiter = ',')] - metadata: Option>, - - /// Optional: Program id of loaded eBPF program this eBPF program will share a map with. - /// Only used when multiple eBPF programs need to share a map. - /// Example: --map-owner-id 63178 - #[clap(long, verbatim_doc_comment)] - map_owner_id: Option, - - #[clap(subcommand)] - pub(crate) command: LoadCommands, -} - -pub(crate) fn execute_load_image(args: &LoadImageArgs, config: &mut Config) -> anyhow::Result<()> { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { - let channel = select_channel(config).expect("failed to select channel"); - let mut client = BpfmanClient::new(channel); - - let bytecode = Some(BytecodeLocation { - location: Some(Location::Image(BytecodeImage::try_from(&args.pull_args)?)), - }); - - let attach = args.command.get_attach_type()?; - - let request = tonic::Request::new(LoadRequest { - bytecode, - name: args.name.to_string(), - program_type: args.command.get_prog_type() as u32, - attach, - metadata: args - .metadata - .clone() - .unwrap_or_default() - .iter() - .map(|(k, v)| (k.to_owned(), v.to_owned())) - .collect(), - global_data: parse_global(&args.global), - uuid: None, - map_owner_id: args.map_owner_id, - }); - let response = client.load(request).await?.into_inner(); - - ProgTable::new_get_bpfman(&response.info)?.print(); - ProgTable::new_get_unsupported(&response.kernel_info)?.print(); - Ok::<(), anyhow::Error>(()) - }) -} diff --git a/bpfman/src/cli/load/mod.rs b/bpfman/src/cli/load/mod.rs deleted file mode 100644 index 839fec798..000000000 --- a/bpfman/src/cli/load/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use bpfman_api::config::Config; -use clap::Subcommand; - -mod file; -mod image; -mod programs; - -use file::{execute_load_file, LoadFileArgs}; -use image::{execute_load_image, LoadImageArgs}; - -#[derive(Subcommand, Debug)] -pub(crate) enum LoadSubcommand { - /// Load an eBPF program from a local .o file. - File(LoadFileArgs), - /// Load an eBPF program packaged in a OCI container image from a given registry. - Image(LoadImageArgs), -} - -impl LoadSubcommand { - pub(crate) fn execute(&self, config: &mut Config) -> anyhow::Result<()> { - match self { - LoadSubcommand::File(l) => execute_load_file(l, config), - LoadSubcommand::Image(l) => execute_load_image(l, config), - } - } -} diff --git a/bpfman/src/cli/load/programs.rs b/bpfman/src/cli/load/programs.rs deleted file mode 100644 index c98a3f647..000000000 --- a/bpfman/src/cli/load/programs.rs +++ /dev/null @@ -1,268 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use std::collections::HashMap; - -use anyhow::bail; -use bpfman_api::{ - v1::{ - attach_info::Info, AttachInfo, KprobeAttachInfo, TcAttachInfo, TracepointAttachInfo, - UprobeAttachInfo, XdpAttachInfo, - }, - ProgramType, TcProceedOn, XdpProceedOn, -}; -use clap::Subcommand; -use hex::FromHex; - -#[derive(Clone, Debug)] -pub(crate) struct GlobalArg { - name: String, - value: Vec, -} - -#[derive(Subcommand, Debug)] -pub(crate) enum LoadCommands { - /// Install an eBPF program on the XDP hook point for a given interface. - Xdp { - /// Required: Interface to load program on. - #[clap(short, long)] - iface: String, - - /// Required: Priority to run program in chain. Lower value runs first. - #[clap(short, long)] - priority: i32, - - /// Optional: Proceed to call other programs in chain on this exit code. - /// Multiple values supported by repeating the parameter. - /// Example: --proceed-on "pass" --proceed-on "drop" - /// - /// [possible values: aborted, drop, pass, tx, redirect, dispatcher_return] - /// - /// [default: pass, dispatcher_return] - #[clap(long, verbatim_doc_comment, num_args(1..))] - proceed_on: Vec, - }, - /// Install an eBPF program on the TC hook point for a given interface. - Tc { - /// Required: Direction to apply program. - /// - /// [possible values: ingress, egress] - #[clap(short, long, verbatim_doc_comment)] - direction: String, - - /// Required: Interface to load program on. - #[clap(short, long)] - iface: String, - - /// Required: Priority to run program in chain. Lower value runs first. - #[clap(short, long)] - priority: i32, - - /// Optional: Proceed to call other programs in chain on this exit code. - /// Multiple values supported by repeating the parameter. - /// Example: --proceed-on "ok" --proceed-on "pipe" - /// - /// [possible values: unspec, ok, reclassify, shot, pipe, stolen, queued, - /// repeat, redirect, trap, dispatcher_return] - /// - /// [default: ok, pipe, dispatcher_return] - #[clap(long, verbatim_doc_comment, num_args(1..))] - proceed_on: Vec, - }, - /// Install an eBPF program on a Tracepoint. - Tracepoint { - /// Required: The tracepoint to attach to. - /// Example: --tracepoint "sched/sched_switch" - #[clap(short, long, verbatim_doc_comment)] - tracepoint: String, - }, - /// Install an eBPF kprobe or kretprobe - Kprobe { - /// Required: Function to attach the kprobe to. - #[clap(short, long)] - fn_name: String, - - /// Optional: Offset added to the address of the function for kprobe. - /// Not allowed for kretprobes. - #[clap(short, long, verbatim_doc_comment)] - offset: Option, - - /// Optional: Whether the program is a kretprobe. - /// - /// [default: false] - #[clap(short, long, verbatim_doc_comment)] - retprobe: bool, - - /// Optional: Namespace to attach the kprobe in. (NOT CURRENTLY SUPPORTED) - #[clap(short, long)] - namespace: Option, - }, - /// Install an eBPF uprobe or uretprobe - Uprobe { - /// Optional: Function to attach the uprobe to. - #[clap(short, long)] - fn_name: Option, - - /// Optional: Offset added to the address of the target function (or - /// beginning of target if no function is identified). Offsets are - /// supported for uretprobes, but use with caution because they can - /// result in unintended side effects. - #[clap(short, long, verbatim_doc_comment)] - offset: Option, - - /// Required: Library name or the absolute path to a binary or library. - /// Example: --target "libc". - #[clap(short, long, verbatim_doc_comment)] - target: String, - - /// Optional: Whether the program is a uretprobe. - /// - /// [default: false] - #[clap(short, long, verbatim_doc_comment)] - retprobe: bool, - - /// Optional: Only execute uprobe for given process identification number (PID). - /// If PID is not provided, uprobe executes for all PIDs. - #[clap(short, long, verbatim_doc_comment)] - pid: Option, - - /// Optional: Namespace to attach the uprobe in. (NOT CURRENTLY SUPPORTED) - #[clap(short, long)] - namespace: Option, - }, -} - -impl LoadCommands { - pub(crate) fn get_prog_type(&self) -> ProgramType { - match self { - LoadCommands::Xdp { .. } => ProgramType::Xdp, - LoadCommands::Tc { .. } => ProgramType::Tc, - LoadCommands::Tracepoint { .. } => ProgramType::Tracepoint, - LoadCommands::Kprobe { .. } => ProgramType::Probe, - LoadCommands::Uprobe { .. } => ProgramType::Probe, - } - } - - pub(crate) fn get_attach_type(&self) -> Result, anyhow::Error> { - match self { - LoadCommands::Xdp { - iface, - priority, - proceed_on, - } => { - let proc_on = match XdpProceedOn::from_strings(proceed_on) { - Ok(p) => p, - Err(e) => bail!("error parsing proceed_on {e}"), - }; - Ok(Some(AttachInfo { - info: Some(Info::XdpAttachInfo(XdpAttachInfo { - priority: *priority, - iface: iface.to_string(), - position: 0, - proceed_on: proc_on.as_action_vec(), - })), - })) - } - LoadCommands::Tc { - direction, - iface, - priority, - proceed_on, - } => { - match direction.as_str() { - "ingress" | "egress" => (), - other => bail!("{} is not a valid direction", other), - }; - let proc_on = match TcProceedOn::from_strings(proceed_on) { - Ok(p) => p, - Err(e) => bail!("error parsing proceed_on {e}"), - }; - Ok(Some(AttachInfo { - info: Some(Info::TcAttachInfo(TcAttachInfo { - priority: *priority, - iface: iface.to_string(), - position: 0, - direction: direction.to_string(), - proceed_on: proc_on.as_action_vec(), - })), - })) - } - LoadCommands::Tracepoint { tracepoint } => Ok(Some(AttachInfo { - info: Some(Info::TracepointAttachInfo(TracepointAttachInfo { - tracepoint: tracepoint.to_string(), - })), - })), - LoadCommands::Kprobe { - fn_name, - offset, - retprobe, - namespace, - } => { - if namespace.is_some() { - bail!("kprobe namespace option not supported yet"); - } - let offset = offset.unwrap_or(0); - Ok(Some(AttachInfo { - info: Some(Info::KprobeAttachInfo(KprobeAttachInfo { - fn_name: fn_name.to_string(), - offset, - retprobe: *retprobe, - namespace: namespace.clone(), - })), - })) - } - LoadCommands::Uprobe { - fn_name, - offset, - target, - retprobe, - pid, - namespace, - } => { - if namespace.is_some() { - bail!("uprobe namespace option not supported yet"); - } - let offset = offset.unwrap_or(0); - Ok(Some(AttachInfo { - info: Some(Info::UprobeAttachInfo(UprobeAttachInfo { - fn_name: fn_name.clone(), - offset, - target: target.clone(), - retprobe: *retprobe, - pid: *pid, - namespace: namespace.clone(), - })), - })) - } - } - } -} - -pub(crate) fn parse_global(global: &Option>) -> HashMap> { - let mut global_data: HashMap> = HashMap::new(); - - if let Some(global) = global { - for g in global.iter() { - global_data.insert(g.name.to_string(), g.value.clone()); - } - } - - global_data -} - -pub(crate) fn parse_global_arg(global_arg: &str) -> Result { - let mut parts = global_arg.split('='); - - let name_str = parts.next().ok_or(std::io::ErrorKind::InvalidInput)?; - - let value_str = parts.next().ok_or(std::io::ErrorKind::InvalidInput)?; - let value = Vec::::from_hex(value_str).map_err(|_e| std::io::ErrorKind::InvalidInput)?; - if value.is_empty() { - return Err(std::io::ErrorKind::InvalidInput.into()); - } - - Ok(GlobalArg { - name: name_str.to_string(), - value, - }) -} diff --git a/bpfman/src/cli/mod.rs b/bpfman/src/cli/mod.rs deleted file mode 100644 index d5d2e3373..000000000 --- a/bpfman/src/cli/mod.rs +++ /dev/null @@ -1,114 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -mod get; -mod image; -mod list; -mod load; -mod system; -mod table; -mod unload; - -use std::fs; - -use bpfman_api::{ - config::{self, Config}, - util::directories::CFGPATH_BPFMAN_CONFIG, -}; -use clap::{Parser, Subcommand}; -use log::warn; -use tokio::net::UnixStream; -use tonic::transport::{Channel, Endpoint, Uri}; -use tower::service_fn; - -use crate::cli::{ - get::{execute_get, GetArgs}, - image::ImageSubCommand, - list::{execute_list, ListArgs}, - load::LoadSubcommand, - system::SystemSubcommand, - unload::{execute_unload, UnloadArgs}, -}; - -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -pub(crate) struct Cli { - #[command(subcommand)] - pub(crate) command: Commands, -} - -#[derive(Subcommand, Debug)] -pub(crate) enum Commands { - /// Load an eBPF program from a local .o file. - #[command(subcommand)] - Load(LoadSubcommand), - /// Unload an eBPF program using the program id. - Unload(UnloadArgs), - /// List all eBPF programs loaded via bpfman. - List(ListArgs), - /// Get an eBPF program using the program id. - Get(GetArgs), - /// eBPF Bytecode Image related commands. - #[command(subcommand)] - Image(ImageSubCommand), - /// Run bpfman as a service. - #[command(subcommand)] - System(SystemSubcommand), -} - -impl Commands { - pub(crate) fn execute(&self) -> Result<(), anyhow::Error> { - let mut config = if let Ok(c) = fs::read_to_string(CFGPATH_BPFMAN_CONFIG) { - c.parse().unwrap_or_else(|_| { - warn!("Unable to parse config file, using defaults"); - Config::default() - }) - } else { - warn!("Unable to read config file, using defaults"); - Config::default() - }; - - match self { - Commands::Load(l) => l.execute(&mut config), - Commands::Unload(args) => execute_unload(args, &mut config), - Commands::List(args) => execute_list(args, &mut config), - Commands::Get(args) => execute_get(args, &mut config), - Commands::Image(i) => i.execute(&mut config), - Commands::System(s) => s.execute(&config), - } - } -} - -fn select_channel(config: &mut Config) -> Option { - let candidate = config - .grpc - .endpoints - .iter_mut() - .find(|e| matches!(e, config::Endpoint::Unix { path: _, enabled } if *enabled)); - if candidate.is_none() { - warn!("No enabled unix endpoints found in config"); - return None; - } - let path = match candidate.as_ref().unwrap() { - config::Endpoint::Unix { path, enabled: _ } => path.clone(), - }; - - let address = Endpoint::try_from(format!("unix:/{path}")); - if let Err(e) = address { - warn!("Failed to parse unix endpoint: {e:?}"); - if let Some(config::Endpoint::Unix { path: _, enabled }) = candidate { - *enabled = false; - } - return select_channel(config); - }; - let address = address.unwrap(); - let channel = address - .connect_with_connector_lazy(service_fn(move |_: Uri| UnixStream::connect(path.clone()))); - Some(channel) -} - -/// Parse a single key-value pair -pub(crate) fn parse_key_val(s: &str) -> Result<(String, String), std::io::Error> { - let pos = s.find('=').ok_or(std::io::ErrorKind::InvalidInput)?; - Ok((s[..pos].to_string(), s[pos + 1..].to_string())) -} diff --git a/bpfman/src/cli/system/mod.rs b/bpfman/src/cli/system/mod.rs deleted file mode 100644 index fba98205c..000000000 --- a/bpfman/src/cli/system/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use bpfman_api::config::Config; -use clap::Subcommand; - -mod service; -use service::{execute_service, ServiceArgs}; - -#[derive(Subcommand, Debug)] -pub(crate) enum SystemSubcommand { - /// Load an eBPF program from a local .o file. - Service(ServiceArgs), -} - -impl SystemSubcommand { - pub(crate) fn execute(&self, config: &Config) -> anyhow::Result<()> { - match self { - SystemSubcommand::Service(args) => execute_service(args, config), - } - } -} diff --git a/bpfman/src/cli/system/service.rs b/bpfman/src/cli/system/service.rs deleted file mode 100644 index e1dff85da..000000000 --- a/bpfman/src/cli/system/service.rs +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use std::{ - env, - fs::{create_dir_all, File}, - io::{BufRead, BufReader}, - str::FromStr, -}; - -use anyhow::{bail, Context}; -use bpfman_api::config::Config; -use clap::Args; -use log::info; -use nix::{ - libc::RLIM_INFINITY, - sys::resource::{setrlimit, Resource}, -}; -use systemd_journal_logger::{connected_to_journal, JournalLog}; - -use crate::{ - serve::serve, - utils::{create_bpffs, set_dir_permissions}, - BPFMAN_ENV_LOG_LEVEL, -}; - -#[derive(Args, Debug)] -pub(crate) struct ServiceArgs { - /// Enable CSI support. - #[clap(long)] - csi_support: bool, -} - -pub(crate) fn execute_service(args: &ServiceArgs, config: &Config) -> anyhow::Result<()> { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { - if connected_to_journal() { - // If bpfman is running as a service, log to journald. - JournalLog::default() - .with_extra_fields(vec![("VERSION", env!("CARGO_PKG_VERSION"))]) - .install() - .unwrap(); - manage_journal_log_level(); - log::info!("Log using journald"); - } else { - // Otherwise fall back to logging to standard error. - env_logger::init(); - log::info!("Log using env_logger"); - } - - has_cap(caps::CapSet::Effective, caps::Capability::CAP_BPF); - has_cap(caps::CapSet::Effective, caps::Capability::CAP_SYS_ADMIN); - - setrlimit(Resource::RLIMIT_MEMLOCK, RLIM_INFINITY, RLIM_INFINITY).unwrap(); - - // Create directories associated with bpfman - use bpfman_api::util::directories::*; - create_dir_all(RTDIR).context("unable to create runtime directory")?; - create_dir_all(RTDIR_FS).context("unable to create mountpoint")?; - create_dir_all(RTDIR_TC_INGRESS_DISPATCHER) - .context("unable to create dispatcher directory")?; - create_dir_all(RTDIR_TC_EGRESS_DISPATCHER) - .context("unable to create dispatcher directory")?; - create_dir_all(RTDIR_XDP_DISPATCHER) - .context("unable to create dispatcher directory")?; - create_dir_all(RTDIR_PROGRAMS).context("unable to create programs directory")?; - - if !is_bpffs_mounted()? { - create_bpffs(RTDIR_FS)?; - } - create_dir_all(RTDIR_FS_XDP).context("unable to create xdp distpacher dir")?; - create_dir_all(RTDIR_FS_TC_INGRESS) - .context("unable to create tc ingress dispatcher dir")?; - create_dir_all(RTDIR_FS_TC_EGRESS) - .context("unable to create tc egress dispatcher dir")?; - create_dir_all(RTDIR_FS_MAPS).context("unable to create maps directory")?; - create_dir_all(RTDIR_BPFMAN_CSI).context("unable to create CSI directory")?; - create_dir_all(RTDIR_BPFMAN_CSI_FS).context("unable to create socket directory")?; - - create_dir_all(CFGDIR_STATIC_PROGRAMS) - .context("unable to create static programs directory")?; - - create_dir_all(STDIR_BYTECODE_IMAGE_CONTENT_STORE) - .context("unable to create bytecode image store directory")?; - - set_dir_permissions(CFGDIR, CFGDIR_MODE).await; - set_dir_permissions(RTDIR, RTDIR_MODE).await; - set_dir_permissions(STDIR, STDIR_MODE).await; - - serve(config, CFGDIR_STATIC_PROGRAMS, args.csi_support).await?; - Ok(()) - }) -} - -fn manage_journal_log_level() { - // env_logger uses the environment variable RUST_LOG to set the log - // level. Parse RUST_LOG to set the log level for journald. - log::set_max_level(log::LevelFilter::Error); - if env::var(BPFMAN_ENV_LOG_LEVEL).is_ok() { - let rust_log = log::LevelFilter::from_str(&env::var(BPFMAN_ENV_LOG_LEVEL).unwrap()); - match rust_log { - Ok(value) => log::set_max_level(value), - Err(e) => log::error!("Invalid Log Level: {}", e), - } - } -} - -fn has_cap(cset: caps::CapSet, cap: caps::Capability) { - info!("Has {}: {}", cap, caps::has_cap(None, cset, cap).unwrap()); -} - -fn is_bpffs_mounted() -> Result { - let file = File::open("/proc/mounts").context("Failed to open /proc/mounts")?; - let reader = BufReader::new(file); - for l in reader.lines() { - match l { - Ok(line) => { - let parts: Vec<&str> = line.split(' ').collect(); - if parts.len() != 6 { - bail!("expected 6 parts in proc mount") - } - if parts[0] == "none" && parts[1].contains("bpfman") && parts[2] == "bpf" { - return Ok(true); - } - } - Err(e) => bail!("problem reading lines {}", e), - } - } - Ok(false) -} diff --git a/bpfman/src/cli/table.rs b/bpfman/src/cli/table.rs deleted file mode 100644 index 4a2ca03d1..000000000 --- a/bpfman/src/cli/table.rs +++ /dev/null @@ -1,304 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use anyhow::bail; -use bpfman_api::{ - v1::{ - attach_info::Info, bytecode_location::Location, list_response::ListResult, - KernelProgramInfo, KprobeAttachInfo, ProgramInfo, TcAttachInfo, TracepointAttachInfo, - UprobeAttachInfo, XdpAttachInfo, - }, - ImagePullPolicy, - ProbeType::{Kprobe, Kretprobe, Uprobe, Uretprobe}, - ProgramType, TcProceedOn, XdpProceedOn, -}; -use comfy_table::{Cell, Color, Table}; -use hex::encode_upper; -pub(crate) struct ProgTable(Table); - -impl ProgTable { - pub(crate) fn new_get_bpfman(r: &Option) -> Result { - let mut table = Table::new(); - - table.load_preset(comfy_table::presets::NOTHING); - table.set_header(vec![Cell::new("Bpfman State") - .add_attribute(comfy_table::Attribute::Bold) - .add_attribute(comfy_table::Attribute::Underlined) - .fg(Color::Green)]); - - if r.is_none() { - table.add_row(vec!["NONE"]); - return Ok(ProgTable(table)); - } - let info = r.clone().unwrap(); - - if info.bytecode.is_none() { - table.add_row(vec!["NONE"]); - return Ok(ProgTable(table)); - } - - if info.name.clone().is_empty() { - table.add_row(vec!["Name:", "None"]); - } else { - table.add_row(vec!["Name:", &info.name.clone()]); - } - - match info.bytecode.clone().unwrap().location.clone() { - Some(l) => match l { - Location::Image(i) => { - table.add_row(vec!["Image URL:", &i.url]); - table.add_row(vec!["Pull Policy:", &format!{ "{}", TryInto::::try_into(i.image_pull_policy)?}]); - } - Location::File(p) => { - table.add_row(vec!["Path:", &p]); - } - }, - // not a bpfman program - None => { - table.add_row(vec!["NONE"]); - return Ok(ProgTable(table)); - } - } - - if info.global_data.is_empty() { - table.add_row(vec!["Global:", "None"]); - } else { - let mut first = true; - for (key, value) in info.global_data.clone() { - let data = &format! {"{key}={}", encode_upper(value)}; - if first { - first = false; - table.add_row(vec!["Global:", data]); - } else { - table.add_row(vec!["", data]); - } - } - } - - if info.metadata.is_empty() { - table.add_row(vec!["Metadata:", "None"]); - } else { - let mut first = true; - for (key, value) in info.metadata.clone() { - let data = &format! {"{key}={value}"}; - if first { - first = false; - table.add_row(vec!["Metadata:", data]); - } else { - table.add_row(vec!["", data]); - } - } - } - - if info.map_pin_path.clone().is_empty() { - table.add_row(vec!["Map Pin Path:", "None"]); - } else { - table.add_row(vec!["Map Pin Path:", &info.map_pin_path.clone()]); - } - - match info.map_owner_id { - Some(id) => table.add_row(vec!["Map Owner ID:", &id.to_string()]), - None => table.add_row(vec!["Map Owner ID:", "None"]), - }; - - if info.map_used_by.clone().is_empty() { - table.add_row(vec!["Maps Used By:", "None"]); - } else { - let mut first = true; - for prog_id in info.clone().map_used_by { - if first { - first = false; - table.add_row(vec!["Maps Used By:", &prog_id]); - } else { - table.add_row(vec!["", &prog_id]); - } - } - }; - - if info.attach.is_some() { - match info.attach.clone().unwrap().info.unwrap() { - Info::XdpAttachInfo(XdpAttachInfo { - priority, - iface, - position, - proceed_on, - }) => { - let proc_on = match XdpProceedOn::from_int32s(proceed_on) { - Ok(p) => p, - Err(e) => bail!("error parsing proceed_on {e}"), - }; - - table.add_row(vec!["Priority:", &priority.to_string()]); - table.add_row(vec!["Iface:", &iface]); - table.add_row(vec!["Position:", &position.to_string()]); - table.add_row(vec!["Proceed On:", &format!("{proc_on}")]); - } - Info::TcAttachInfo(TcAttachInfo { - priority, - iface, - position, - direction, - proceed_on, - }) => { - let proc_on = match TcProceedOn::from_int32s(proceed_on) { - Ok(p) => p, - Err(e) => bail!("error parsing proceed_on {e}"), - }; - - table.add_row(vec!["Priority:", &priority.to_string()]); - table.add_row(vec!["Iface:", &iface]); - table.add_row(vec!["Position:", &position.to_string()]); - table.add_row(vec!["Direction:", &direction]); - table.add_row(vec!["Proceed On:", &format!("{proc_on}")]); - } - Info::TracepointAttachInfo(TracepointAttachInfo { tracepoint }) => { - table.add_row(vec!["Tracepoint:", &tracepoint]); - } - Info::KprobeAttachInfo(KprobeAttachInfo { - fn_name, - offset, - retprobe, - namespace, - }) => { - let probe_type = match retprobe { - true => Kretprobe, - false => Kprobe, - }; - - table.add_row(vec!["Probe Type:", &format!["{probe_type}"]]); - table.add_row(vec!["Function Name:", &fn_name]); - table.add_row(vec!["Offset:", &offset.to_string()]); - table.add_row(vec!["Namespace", &namespace.unwrap_or("".to_string())]); - } - Info::UprobeAttachInfo(UprobeAttachInfo { - fn_name, - offset, - target, - retprobe, - pid, - namespace, - }) => { - let probe_type = match retprobe { - true => Uretprobe, - false => Uprobe, - }; - - table.add_row(vec!["Probe Type:", &format!["{probe_type}"]]); - table.add_row(vec!["Function Name:", &fn_name.unwrap_or("".to_string())]); - table.add_row(vec!["Offset:", &offset.to_string()]); - table.add_row(vec!["Target:", &target]); - table.add_row(vec!["PID", &pid.unwrap_or(0).to_string()]); - table.add_row(vec!["Namespace", &namespace.unwrap_or("".to_string())]); - } - } - } - - Ok(ProgTable(table)) - } - - pub(crate) fn new_get_unsupported( - r: &Option, - ) -> Result { - let mut table = Table::new(); - - table.load_preset(comfy_table::presets::NOTHING); - table.set_header(vec![Cell::new("Kernel State") - .add_attribute(comfy_table::Attribute::Bold) - .add_attribute(comfy_table::Attribute::Underlined) - .fg(Color::Green)]); - - if r.is_none() { - table.add_row(vec!["NONE"]); - return Ok(ProgTable(table)); - } - let kernel_info = r.clone().unwrap(); - - let name = if kernel_info.name.clone().is_empty() { - "None".to_string() - } else { - kernel_info.name.clone() - }; - - let rows = vec![ - vec!["ID:".to_string(), kernel_info.id.to_string()], - vec!["Name:".to_string(), name], - vec![ - "Type:".to_string(), - format!("{}", ProgramType::try_from(kernel_info.program_type)?), - ], - vec!["Loaded At:".to_string(), kernel_info.loaded_at.clone()], - vec!["Tag:".to_string(), kernel_info.tag.clone()], - vec![ - "GPL Compatible:".to_string(), - kernel_info.gpl_compatible.to_string(), - ], - vec!["Map IDs:".to_string(), format!("{:?}", kernel_info.map_ids)], - vec!["BTF ID:".to_string(), kernel_info.btf_id.to_string()], - vec![ - "Size Translated (bytes):".to_string(), - kernel_info.bytes_xlated.to_string(), - ], - vec!["JITted:".to_string(), kernel_info.jited.to_string()], - vec![ - "Size JITted:".to_string(), - kernel_info.bytes_jited.to_string(), - ], - vec![ - "Kernel Allocated Memory (bytes):".to_string(), - kernel_info.bytes_memlock.to_string(), - ], - vec![ - "Verified Instruction Count:".to_string(), - kernel_info.verified_insns.to_string(), - ], - ]; - table.add_rows(rows); - - Ok(ProgTable(table)) - } - - pub(crate) fn new_list() -> Self { - let mut table = Table::new(); - - table.load_preset(comfy_table::presets::NOTHING); - table.set_header(vec!["Program ID", "Name", "Type", "Load Time"]); - ProgTable(table) - } - - pub(crate) fn add_row_list( - &mut self, - id: String, - name: String, - type_: String, - load_time: String, - ) { - self.0.add_row(vec![id, name, type_, load_time]); - } - - pub(crate) fn add_response_prog(&mut self, r: ListResult) -> anyhow::Result<()> { - if r.kernel_info.is_none() { - self.0.add_row(vec!["NONE"]); - return Ok(()); - } - let kernel_info = r.kernel_info.unwrap(); - - self.add_row_list( - kernel_info.id.to_string(), - kernel_info.name, - (ProgramType::try_from(kernel_info.program_type)?).to_string(), - kernel_info.loaded_at, - ); - - Ok(()) - } - - pub(crate) fn print(&self) { - println!("{self}\n") - } -} - -impl std::fmt::Display for ProgTable { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/bpfman/src/cli/unload.rs b/bpfman/src/cli/unload.rs deleted file mode 100644 index d8fdc9342..000000000 --- a/bpfman/src/cli/unload.rs +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use bpfman_api::{ - config::Config, - v1::{bpfman_client::BpfmanClient, UnloadRequest}, -}; -use clap::Args; - -use crate::cli::select_channel; - -#[derive(Args, Debug)] -pub(crate) struct UnloadArgs { - /// Required: Program id to be unloaded. - id: u32, -} - -pub(crate) fn execute_unload(args: &UnloadArgs, config: &mut Config) -> Result<(), anyhow::Error> { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { - let channel = select_channel(config).expect("failed to select channel"); - let mut client = BpfmanClient::new(channel); - let request = tonic::Request::new(UnloadRequest { id: args.id }); - let _response = client.unload(request).await?.into_inner(); - Ok::<(), anyhow::Error>(()) - }) -} diff --git a/bpfman/src/command.rs b/bpfman/src/command.rs deleted file mode 100644 index b9f0c284d..000000000 --- a/bpfman/src/command.rs +++ /dev/null @@ -1,780 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -//! Commands between the RPC thread and the BPF thread -use std::{ - collections::HashMap, - fmt, fs, - io::BufReader, - path::{Path, PathBuf}, -}; - -use aya::programs::ProgramInfo as AyaProgInfo; -use bpfman_api::{ - util::directories::{RTDIR_FS, RTDIR_PROGRAMS}, - v1::{ - attach_info::Info, bytecode_location::Location as V1Location, AttachInfo, BytecodeLocation, - KernelProgramInfo as V1KernelProgramInfo, KprobeAttachInfo, ProgramInfo as V1ProgramInfo, - TcAttachInfo, TracepointAttachInfo, UprobeAttachInfo, XdpAttachInfo, - }, - ParseError, ProgramType, TcProceedOn, XdpProceedOn, -}; -use chrono::{prelude::DateTime, Local}; -use log::info; -use serde::{Deserialize, Serialize}; -use tokio::sync::{mpsc::Sender, oneshot}; - -use crate::{ - errors::BpfmanError, - multiprog::{DispatcherId, DispatcherInfo}, - oci_utils::image_manager::{BytecodeImage, Command as ImageManagerCommand}, -}; - -/// Provided by the requester and used by the manager task to send -/// the command response back to the requester. -type Responder = oneshot::Sender; - -/// Multiple different commands are multiplexed over a single channel. -#[derive(Debug)] -pub(crate) enum Command { - /// Load a program - Load(LoadArgs), - Unload(UnloadArgs), - List { - responder: Responder, BpfmanError>>, - }, - Get(GetArgs), - PullBytecode(PullBytecodeArgs), -} - -#[derive(Debug)] -pub(crate) struct LoadArgs { - pub(crate) program: Program, - pub(crate) responder: Responder>, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) enum Program { - Xdp(XdpProgram), - Tc(TcProgram), - Tracepoint(TracepointProgram), - Kprobe(KprobeProgram), - Uprobe(UprobeProgram), - Unsupported(KernelProgramInfo), -} - -#[derive(Debug)] -pub(crate) struct UnloadArgs { - pub(crate) id: u32, - pub(crate) responder: Responder>, -} - -#[derive(Debug)] -pub(crate) struct GetArgs { - pub(crate) id: u32, - pub(crate) responder: Responder>, -} - -#[derive(Debug)] -pub(crate) struct PullBytecodeArgs { - pub(crate) image: BytecodeImage, - pub(crate) responder: Responder>, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) enum Location { - Image(BytecodeImage), - File(String), -} - -// TODO astoycos remove this impl, as it's only needed for a hack in the rebuild -// dispatcher code. -impl Default for Location { - fn default() -> Self { - Location::File(String::new()) - } -} - -impl Location { - async fn get_program_bytes( - &self, - image_manager: Sender, - ) -> Result<(Vec, String), BpfmanError> { - match self { - Location::File(l) => Ok((crate::utils::read(l).await?, "".to_owned())), - Location::Image(l) => { - let (tx, rx) = oneshot::channel(); - image_manager - .send(ImageManagerCommand::Pull { - image: l.image_url.clone(), - pull_policy: l.image_pull_policy.clone(), - username: l.username.clone(), - password: l.password.clone(), - resp: tx, - }) - .await - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))?; - let (path, bpf_function_name) = rx - .await - .map_err(BpfmanError::RpcError)? - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))?; - - let (tx, rx) = oneshot::channel(); - image_manager - .send(ImageManagerCommand::GetBytecode { path, resp: tx }) - .await - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))?; - let bytecode = rx - .await - .map_err(BpfmanError::RpcError)? - .map_err(|e| BpfmanError::Error(format!("Bytecode loading error: {e}")))?; - - Ok((bytecode, bpf_function_name)) - } - } - } -} - -#[derive(Debug, Serialize, Hash, Deserialize, Eq, PartialEq, Copy, Clone)] -pub(crate) enum Direction { - Ingress = 1, - Egress = 2, -} - -impl TryFrom for Direction { - type Error = ParseError; - - fn try_from(v: String) -> Result { - match v.as_str() { - "ingress" => Ok(Self::Ingress), - "egress" => Ok(Self::Egress), - m => Err(ParseError::InvalidDirection { - direction: m.to_string(), - }), - } - } -} - -impl std::fmt::Display for Direction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Direction::Ingress => f.write_str("in"), - Direction::Egress => f.write_str("eg"), - } - } -} - -/// KernelProgramInfo stores information about ALL bpf programs loaded -/// on a system. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub(crate) struct KernelProgramInfo { - pub(crate) id: u32, - pub(crate) name: String, - pub(crate) program_type: u32, - pub(crate) loaded_at: String, - pub(crate) tag: String, - pub(crate) gpl_compatible: bool, - pub(crate) map_ids: Vec, - pub(crate) btf_id: u32, - pub(crate) bytes_xlated: u32, - pub(crate) jited: bool, - pub(crate) bytes_jited: u32, - pub(crate) bytes_memlock: u32, - pub(crate) verified_insns: u32, -} - -impl TryFrom for KernelProgramInfo { - type Error = BpfmanError; - - fn try_from(prog: AyaProgInfo) -> Result { - Ok(KernelProgramInfo { - id: prog.id(), - name: prog - .name_as_str() - .expect("Program name is not valid unicode") - .to_string(), - program_type: prog.program_type(), - loaded_at: DateTime::::from(prog.loaded_at()) - .format("%Y-%m-%dT%H:%M:%S%z") - .to_string(), - tag: format!("{:x}", prog.tag()), - gpl_compatible: prog.gpl_compatible(), - map_ids: prog.map_ids().map_err(BpfmanError::BpfProgramError)?, - btf_id: prog.btf_id().map_or(0, |n| n.into()), - bytes_xlated: prog.size_translated(), - jited: prog.size_jitted() != 0, - bytes_jited: prog.size_jitted(), - bytes_memlock: prog.memory_locked().map_err(BpfmanError::BpfProgramError)?, - verified_insns: prog.verified_instruction_count(), - }) - } -} - -impl TryFrom<&Program> for V1ProgramInfo { - type Error = BpfmanError; - - fn try_from(program: &Program) -> Result { - let data = program.data()?; - - let bytecode = match program.location() { - Some(l) => match l { - crate::command::Location::Image(m) => { - Some(BytecodeLocation { - location: Some(V1Location::Image(bpfman_api::v1::BytecodeImage { - url: m.get_url().to_string(), - image_pull_policy: m.get_pull_policy().to_owned() as i32, - // Never dump Plaintext Credentials - username: Some(String::new()), - password: Some(String::new()), - })), - }) - } - crate::command::Location::File(m) => Some(BytecodeLocation { - location: Some(V1Location::File(m.to_string())), - }), - }, - None => None, - }; - - let attach_info = AttachInfo { - info: match program.clone() { - Program::Xdp(p) => Some(Info::XdpAttachInfo(XdpAttachInfo { - priority: p.priority, - iface: p.iface, - position: p.current_position.unwrap_or(0) as i32, - proceed_on: p.proceed_on.as_action_vec(), - })), - Program::Tc(p) => Some(Info::TcAttachInfo(TcAttachInfo { - priority: p.priority, - iface: p.iface, - position: p.current_position.unwrap_or(0) as i32, - direction: p.direction.to_string(), - proceed_on: p.proceed_on.as_action_vec(), - })), - Program::Tracepoint(p) => Some(Info::TracepointAttachInfo(TracepointAttachInfo { - tracepoint: p.tracepoint, - })), - Program::Kprobe(p) => Some(Info::KprobeAttachInfo(KprobeAttachInfo { - fn_name: p.fn_name, - offset: p.offset, - retprobe: p.retprobe, - namespace: p.namespace, - })), - Program::Uprobe(p) => Some(Info::UprobeAttachInfo(UprobeAttachInfo { - fn_name: p.fn_name, - offset: p.offset, - target: p.target, - retprobe: p.retprobe, - pid: p.pid, - namespace: p.namespace, - })), - Program::Unsupported(_) => None, - }, - }; - - // Populate the Program Info with bpfman data - Ok(V1ProgramInfo { - name: data.name().to_owned(), - bytecode, - attach: Some(attach_info), - global_data: data.global_data().to_owned(), - map_owner_id: data.map_owner_id(), - map_pin_path: data - .map_pin_path() - .map_or(String::new(), |v| v.to_str().unwrap().to_string()), - map_used_by: data - .maps_used_by() - .map_or(vec![], |m| m.iter().map(|m| m.to_string()).collect()), - metadata: data.metadata().to_owned(), - }) - } -} - -impl TryFrom<&Program> for V1KernelProgramInfo { - type Error = BpfmanError; - - fn try_from(program: &Program) -> Result { - // Get the Kernel Info. - let kernel_info = program.kernel_info().ok_or(BpfmanError::Error( - "program kernel info not available".to_string(), - ))?; - - // Populate the Kernel Info. - Ok(V1KernelProgramInfo { - id: kernel_info.id, - name: kernel_info.name.to_owned(), - program_type: program.kind() as u32, - loaded_at: kernel_info.loaded_at.to_owned(), - tag: kernel_info.tag.to_owned(), - gpl_compatible: kernel_info.gpl_compatible, - map_ids: kernel_info.map_ids.to_owned(), - btf_id: kernel_info.btf_id, - bytes_xlated: kernel_info.bytes_xlated, - jited: kernel_info.jited, - bytes_jited: kernel_info.bytes_jited, - bytes_memlock: kernel_info.bytes_memlock, - verified_insns: kernel_info.verified_insns, - }) - } -} - -/// ProgramInfo stores information about bpf programs that are loaded and managed -/// by bpfman. -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -pub(crate) struct ProgramData { - // known at load time, set by user - name: String, - location: Location, - metadata: HashMap, - global_data: HashMap>, - map_owner_id: Option, - - // populated after load - kernel_info: Option, - map_pin_path: Option, - maps_used_by: Option>, - - // program_bytes is used to temporarily cache the raw program data during - // the loading process. It MUST be cleared following a load so that there - // is not a long lived copy of the program data living on the heap. - #[serde(skip_serializing, skip_deserializing)] - program_bytes: Vec, -} - -impl ProgramData { - pub(crate) fn new( - location: Location, - name: String, - metadata: HashMap, - global_data: HashMap>, - map_owner_id: Option, - ) -> Self { - Self { - name, - location, - metadata, - global_data, - map_owner_id, - program_bytes: Vec::new(), - kernel_info: None, - map_pin_path: None, - maps_used_by: None, - } - } - - pub(crate) fn name(&self) -> &str { - &self.name - } - - pub(crate) fn id(&self) -> Option { - // use as_ref here so we don't consume self. - self.kernel_info.as_ref().map(|i| i.id) - } - - pub(crate) fn set_kernel_info(&mut self, info: Option) { - self.kernel_info = info - } - - pub(crate) fn kernel_info(&self) -> Option<&KernelProgramInfo> { - self.kernel_info.as_ref() - } - - pub(crate) fn global_data(&self) -> &HashMap> { - &self.global_data - } - - pub(crate) fn metadata(&self) -> &HashMap { - &self.metadata - } - - pub(crate) fn set_map_pin_path(&mut self, path: Option) { - self.map_pin_path = path - } - - pub(crate) fn map_pin_path(&self) -> Option<&Path> { - self.map_pin_path.as_deref() - } - - pub(crate) fn map_owner_id(&self) -> Option { - self.map_owner_id - } - - pub(crate) fn set_maps_used_by(&mut self, used_by: Option>) { - self.maps_used_by = used_by - } - - pub(crate) fn maps_used_by(&self) -> Option<&Vec> { - self.maps_used_by.as_ref() - } - - pub(crate) fn program_bytes(&self) -> &[u8] { - &self.program_bytes - } - - // In order to ensure that the program bytes, which can be a large amount - // of data is only stored for as long as needed, make sure to call - // clear_program_bytes following a load. - pub(crate) fn clear_program_bytes(&mut self) { - self.program_bytes = Vec::new(); - } - - pub(crate) async fn set_program_bytes( - &mut self, - image_manager: Sender, - ) -> Result<(), BpfmanError> { - match self.location.get_program_bytes(image_manager).await { - Err(e) => Err(e), - Ok((v, s)) => { - match &self.location { - Location::Image(l) => { - info!( - "Loading program bytecode from container image: {}", - l.get_url() - ); - // If program name isn't provided and we're loading from a container - // image use the program name provided in the image metadata, otherwise - // always use the provided program name. - let provided_name = self.name.clone(); - - if provided_name.is_empty() { - self.name = s; - } else if s != provided_name { - return Err(BpfmanError::BytecodeMetaDataMismatch { - image_prog_name: s, - provided_prog_name: provided_name, - }); - } - } - Location::File(l) => { - info!("Loading program bytecode from file: {}", l); - } - } - self.program_bytes = v; - Ok(()) - } - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) struct XdpProgram { - pub(crate) data: ProgramData, - // known at load time - pub(crate) priority: i32, - pub(crate) iface: String, - pub(crate) proceed_on: XdpProceedOn, - // populated after load - #[serde(skip)] - pub(crate) current_position: Option, - pub(crate) if_index: Option, - pub(crate) attached: bool, -} - -impl XdpProgram { - pub(crate) fn new( - data: ProgramData, - priority: i32, - iface: String, - proceed_on: XdpProceedOn, - ) -> Self { - Self { - data, - priority, - iface, - proceed_on, - current_position: None, - if_index: None, - attached: false, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) struct TcProgram { - pub(crate) data: ProgramData, - // known at load time - pub(crate) priority: i32, - pub(crate) iface: String, - pub(crate) proceed_on: TcProceedOn, - pub(crate) direction: Direction, - // populated after load - #[serde(skip)] - pub(crate) current_position: Option, - pub(crate) if_index: Option, - pub(crate) attached: bool, -} - -impl TcProgram { - pub(crate) fn new( - data: ProgramData, - priority: i32, - iface: String, - proceed_on: TcProceedOn, - direction: Direction, - ) -> Self { - Self { - data, - priority, - iface, - proceed_on, - direction, - current_position: None, - if_index: None, - attached: false, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) struct TracepointProgram { - pub(crate) data: ProgramData, - // known at load time - pub(crate) tracepoint: String, -} - -impl TracepointProgram { - pub(crate) fn new(data: ProgramData, tracepoint: String) -> Self { - Self { data, tracepoint } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) struct KprobeProgram { - pub(crate) data: ProgramData, - // Known at load time - pub(crate) fn_name: String, - pub(crate) offset: u64, - pub(crate) retprobe: bool, - pub(crate) namespace: Option, -} - -impl KprobeProgram { - pub(crate) fn new( - data: ProgramData, - fn_name: String, - offset: u64, - retprobe: bool, - namespace: Option, - ) -> Self { - Self { - data, - fn_name, - offset, - retprobe, - namespace, - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) struct UprobeProgram { - pub(crate) data: ProgramData, - // Known at load time - pub(crate) fn_name: Option, - pub(crate) offset: u64, - pub(crate) target: String, - pub(crate) retprobe: bool, - pub(crate) pid: Option, - pub(crate) namespace: Option, -} - -impl UprobeProgram { - pub(crate) fn new( - data: ProgramData, - fn_name: Option, - offset: u64, - target: String, - retprobe: bool, - pid: Option, - namespace: Option, - ) -> Self { - Self { - data, - fn_name, - offset, - target, - retprobe, - pid, - namespace, - } - } -} - -impl Program { - pub(crate) fn kind(&self) -> ProgramType { - match self { - Program::Xdp(_) => ProgramType::Xdp, - Program::Tc(_) => ProgramType::Tc, - Program::Tracepoint(_) => ProgramType::Tracepoint, - Program::Kprobe(_) => ProgramType::Probe, - Program::Uprobe(_) => ProgramType::Probe, - Program::Unsupported(i) => i.program_type.try_into().unwrap(), - } - } - - pub(crate) fn dispatcher_id(&self) -> Option { - match self { - Program::Xdp(p) => Some(DispatcherId::Xdp(DispatcherInfo( - p.if_index.expect("if_index should be known at this point"), - None, - ))), - Program::Tc(p) => Some(DispatcherId::Tc(DispatcherInfo( - p.if_index.expect("if_index should be known at this point"), - Some(p.direction), - ))), - _ => None, - } - } - - pub(crate) fn data_mut(&mut self) -> Result<&mut ProgramData, BpfmanError> { - match self { - Program::Xdp(p) => Ok(&mut p.data), - Program::Tracepoint(p) => Ok(&mut p.data), - Program::Tc(p) => Ok(&mut p.data), - Program::Kprobe(p) => Ok(&mut p.data), - Program::Uprobe(p) => Ok(&mut p.data), - Program::Unsupported(_) => Err(BpfmanError::Error( - "Unsupported program type has no ProgramData".to_string(), - )), - } - } - - pub(crate) fn data(&self) -> Result<&ProgramData, BpfmanError> { - match self { - Program::Xdp(p) => Ok(&p.data), - Program::Tracepoint(p) => Ok(&p.data), - Program::Tc(p) => Ok(&p.data), - Program::Kprobe(p) => Ok(&p.data), - Program::Uprobe(p) => Ok(&p.data), - Program::Unsupported(_) => Err(BpfmanError::Error( - "Unsupported program type has no ProgramData".to_string(), - )), - } - } - - pub(crate) fn attached(&self) -> Option { - match self { - Program::Xdp(p) => Some(p.attached), - Program::Tc(p) => Some(p.attached), - _ => None, - } - } - - pub(crate) fn set_attached(&mut self) { - match self { - Program::Xdp(p) => p.attached = true, - Program::Tc(p) => p.attached = true, - _ => (), - } - } - - pub(crate) fn set_position(&mut self, pos: Option) { - match self { - Program::Xdp(p) => p.current_position = pos, - Program::Tc(p) => p.current_position = pos, - _ => (), - } - } - - pub(crate) fn kernel_info(&self) -> Option<&KernelProgramInfo> { - match self { - Program::Xdp(p) => p.data.kernel_info.as_ref(), - Program::Tc(p) => p.data.kernel_info.as_ref(), - Program::Tracepoint(p) => p.data.kernel_info.as_ref(), - Program::Kprobe(p) => p.data.kernel_info.as_ref(), - Program::Uprobe(p) => p.data.kernel_info.as_ref(), - // KernelProgramInfo will never be nil for Unsupported programs - Program::Unsupported(p) => Some(p), - } - } - - pub(crate) fn save(&self, id: u32) -> Result<(), anyhow::Error> { - let path = format!("{RTDIR_PROGRAMS}/{id}"); - serde_json::to_writer(&fs::File::create(path)?, &self)?; - Ok(()) - } - - pub(crate) fn delete(&self, id: u32) -> Result<(), anyhow::Error> { - let path = format!("{RTDIR_PROGRAMS}/{id}"); - if PathBuf::from(&path).exists() { - fs::remove_file(path)?; - } - - let path = format!("{RTDIR_FS}/prog_{id}"); - if PathBuf::from(&path).exists() { - fs::remove_file(path)?; - } - let path = format!("{RTDIR_FS}/prog_{id}_link"); - if PathBuf::from(&path).exists() { - fs::remove_file(path)?; - } - Ok(()) - } - - pub(crate) fn load(id: u32) -> Result { - let path = format!("{RTDIR_PROGRAMS}/{id}"); - let file = fs::File::open(path)?; - let reader = BufReader::new(file); - let prog = serde_json::from_reader(reader)?; - Ok(prog) - } - - pub(crate) fn if_index(&self) -> Option { - match self { - Program::Xdp(p) => p.if_index, - Program::Tc(p) => p.if_index, - _ => None, - } - } - - pub(crate) fn set_if_index(&mut self, if_index: u32) { - match self { - Program::Xdp(p) => p.if_index = Some(if_index), - Program::Tc(p) => p.if_index = Some(if_index), - _ => (), - } - } - - pub(crate) fn if_name(&self) -> Option { - match self { - Program::Xdp(p) => Some(p.iface.clone()), - Program::Tc(p) => Some(p.iface.clone()), - _ => None, - } - } - - pub(crate) fn priority(&self) -> Option { - match self { - Program::Xdp(p) => Some(p.priority), - Program::Tc(p) => Some(p.priority), - _ => None, - } - } - - pub(crate) fn location(&self) -> Option<&Location> { - match self { - Program::Xdp(p) => Some(&p.data.location), - Program::Tracepoint(p) => Some(&p.data.location), - Program::Tc(p) => Some(&p.data.location), - Program::Kprobe(p) => Some(&p.data.location), - Program::Uprobe(p) => Some(&p.data.location), - Program::Unsupported(_) => None, - } - } - - pub(crate) fn direction(&self) -> Option { - match self { - Program::Tc(p) => Some(p.direction), - _ => None, - } - } -} - -// BpfMap represents a single map pin path used by a Program. It has to be a -// separate object becuase it's lifetime is slightly different from a Program. -// More specifically a BpfMap can outlive a Program if other Programs are using -// it. -#[derive(Debug, Clone)] -pub(crate) struct BpfMap { - pub(crate) used_by: Vec, -} diff --git a/bpfman/src/config.rs b/bpfman/src/config.rs new file mode 100644 index 000000000..ad4287320 --- /dev/null +++ b/bpfman/src/config.rs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use std::{collections::HashMap, str::FromStr}; + +use aya::programs::XdpFlags; +use serde::{Deserialize, Serialize}; + +use crate::errors::ParseError; + +#[derive(Debug, Deserialize, Default, Clone)] +pub(crate) struct Config { + interfaces: Option>, + #[serde(default)] + signing: Option, + database: Option, +} + +impl Config { + pub(crate) fn interfaces(&self) -> &Option> { + &self.interfaces + } + + pub(crate) fn signing(&self) -> &Option { + &self.signing + } + + pub(crate) fn database(&self) -> &Option { + &self.database + } +} +#[derive(Debug, Deserialize, Clone)] +pub struct SigningConfig { + pub allow_unsigned: bool, +} + +impl Default for SigningConfig { + fn default() -> Self { + Self { + // Allow unsigned programs by default + allow_unsigned: true, + } + } +} + +#[derive(Debug, Deserialize, Clone)] +pub struct DatabaseConfig { + pub max_retries: u32, + pub millisec_delay: u64, +} + +impl Default for DatabaseConfig { + fn default() -> Self { + Self { + // Maximum numbers of times to attempt to open the database after a failed attempt + max_retries: 10, + // Number of milli-seconds to wait between failed database attempts + millisec_delay: 1000, + } + } +} + +impl FromStr for Config { + type Err = ParseError; + + fn from_str(s: &str) -> Result { + toml::from_str(s).map_err(ParseError::ConfigParseError) + } +} + +#[derive(Debug, Deserialize, Copy, Clone)] +pub(crate) struct InterfaceConfig { + xdp_mode: XdpMode, +} + +impl InterfaceConfig { + pub(crate) fn xdp_mode(&self) -> &XdpMode { + &self.xdp_mode + } +} + +#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub(crate) enum XdpMode { + Skb, + Drv, + Hw, +} + +impl XdpMode { + pub(crate) fn as_flags(&self) -> XdpFlags { + match self { + XdpMode::Skb => XdpFlags::SKB_MODE, + XdpMode::Drv => XdpFlags::DRV_MODE, + XdpMode::Hw => XdpFlags::HW_MODE, + } + } +} + +impl TryFrom for XdpMode { + type Error = ParseError; + + fn try_from(mode: u32) -> Result { + match mode { + 0 => Ok(XdpMode::Skb), + 1 => Ok(XdpMode::Drv), + 2 => Ok(XdpMode::Hw), + _ => Err(ParseError::InvalidXdpMode { + mode: mode.to_string(), + }), + } + } +} + +impl std::fmt::Display for XdpMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + XdpMode::Skb => write!(f, "skb"), + XdpMode::Drv => write!(f, "drv"), + XdpMode::Hw => write!(f, "hw"), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_config_from_invalid_string() { + assert!(Config::from_str("i am a teapot").is_err()); + } + + #[test] + fn test_config_single_iface() { + let input = r#" + [interfaces] + [interfaces.eth0] + xdp_mode = "drv" + "#; + let config: Config = toml::from_str(input).expect("error parsing toml input"); + match config.interfaces { + Some(i) => { + assert!(i.contains_key("eth0")); + assert_eq!(i.get("eth0").unwrap().xdp_mode, XdpMode::Drv) + } + None => panic!("expected interfaces to be present"), + } + } + + #[test] + fn test_config_multiple_iface() { + let input = r#" + [interfaces] + [interfaces.eth0] + xdp_mode = "drv" + [interfaces.eth1] + xdp_mode = "hw" + [interfaces.eth2] + xdp_mode = "skb" + "#; + let config: Config = toml::from_str(input).expect("error parsing toml input"); + match config.interfaces { + Some(i) => { + assert_eq!(i.len(), 3); + assert!(i.contains_key("eth0")); + assert_eq!(i.get("eth0").unwrap().xdp_mode, XdpMode::Drv); + assert!(i.contains_key("eth1")); + assert_eq!(i.get("eth1").unwrap().xdp_mode, XdpMode::Hw); + assert!(i.contains_key("eth2")); + assert_eq!(i.get("eth2").unwrap().xdp_mode, XdpMode::Skb); + } + None => panic!("expected interfaces to be present"), + } + } +} diff --git a/bpfman/src/errors.rs b/bpfman/src/errors.rs index be5bbe854..97c55ae6d 100644 --- a/bpfman/src/errors.rs +++ b/bpfman/src/errors.rs @@ -3,6 +3,9 @@ use thiserror::Error; use tokio::sync::oneshot; +use url::ParseError as urlParseError; + +use crate::oci_utils::ImageError; #[derive(Debug, Error)] pub enum BpfmanError { @@ -31,7 +34,7 @@ pub enum BpfmanError { #[error("dispatcher not required")] DispatcherNotRequired, #[error(transparent)] - BpfBytecodeError(#[from] anyhow::Error), + BpfBytecodeError(#[from] ImageError), #[error("Bytecode image bpf function name: {image_prog_name} isn't equal to the provided bpf function name {provided_prog_name}")] BytecodeMetaDataMismatch { image_prog_name: String, @@ -40,7 +43,46 @@ pub enum BpfmanError { #[error("Unable to delete program {0}")] BpfmanProgramDeleteError(#[source] anyhow::Error), #[error(transparent)] - RpcError(#[from] oneshot::error::RecvError), + RpcRecvError(#[from] oneshot::error::RecvError), + // Use anyhow::Error here since the real error contains a generic reflecting + // the failed sent item's type + #[error(transparent)] + RpcSendError(#[from] anyhow::Error), #[error("Failed to pin map {0}")] UnableToPinMap(#[source] aya::pin::PinError), + #[error("Unable to attach {program_type} in container with pid {container_pid}")] + ContainerAttachError { + program_type: String, + container_pid: i32, + }, + #[error("{0}: {1}")] + DatabaseError(String, String), + #[error("Internal error occurred. {0}")] + InternalError(String), + #[error(transparent)] + BtfError(#[from] aya::BtfError), + #[error("Failed to acquire database lock, please try again later")] + DatabaseLockError, +} + +#[derive(Error, Debug)] +pub enum ParseError { + #[error("{program} is not a valid program type")] + InvalidProgramType { program: String }, + #[error("{proceedon} is not a valid proceed-on value")] + InvalidProceedOn { proceedon: String }, + #[error("not a valid direction: {direction}")] + InvalidDirection { direction: String }, + #[error("Failed to Parse bytecode location: {0}")] + BytecodeLocationParseFailure(#[source] urlParseError), + #[error("Invalid bytecode location: {location}")] + InvalidBytecodeLocation { location: String }, + #[error("Invalid bytecode image pull policy: {pull_policy}")] + InvalidBytecodeImagePullPolicy { pull_policy: String }, + #[error("{probe} is not a valid probe type")] + InvalidProbeType { probe: String }, + #[error("Invalid XdpMode: {mode}")] + InvalidXdpMode { mode: String }, + #[error("Error parsing config file: {0}")] + ConfigParseError(#[from] toml::de::Error), } diff --git a/bpfman/src/lib.rs b/bpfman/src/lib.rs new file mode 100644 index 000000000..c16c5bda7 --- /dev/null +++ b/bpfman/src/lib.rs @@ -0,0 +1,1151 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +use std::{ + collections::HashMap, + fs::{create_dir_all, remove_dir_all}, + path::{Path, PathBuf}, +}; + +use aya::{ + programs::{ + fentry::FEntryLink, fexit::FExitLink, kprobe::KProbeLink, links::FdLink, loaded_programs, + trace_point::TracePointLink, uprobe::UProbeLink, FEntry, FExit, KProbe, TracePoint, UProbe, + }, + BpfLoader, Btf, +}; +use log::{debug, info, warn}; +use sled::{Config as SledConfig, Db}; +use tokio::time::{sleep, Duration}; +use utils::initialize_bpfman; + +use crate::{ + config::Config, + directories::*, + errors::BpfmanError, + multiprog::{ + Dispatcher, DispatcherId, DispatcherInfo, TC_DISPATCHER_PREFIX, XDP_DISPATCHER_PREFIX, + }, + oci_utils::image_manager::ImageManager, + types::{ + BytecodeImage, Direction, ListFilter, + ProbeType::{self, *}, + Program, ProgramData, ProgramType, PROGRAM_PREFIX, + }, + utils::{ + bytes_to_string, bytes_to_u32, get_error_msg_from_stderr, get_ifindex, open_config_file, + set_dir_permissions, should_map_be_pinned, sled_insert, + }, +}; + +mod config; +mod dispatcher_config; +pub mod errors; +mod multiprog; +mod oci_utils; +mod static_program; +pub mod types; +pub mod utils; + +const MAPS_MODE: u32 = 0o0660; +const MAP_PREFIX: &str = "map_"; +const MAPS_USED_BY_PREFIX: &str = "map_used_by_"; +pub(crate) const BPFMAN_ENV_LOG_LEVEL: &str = "RUST_LOG"; + +pub(crate) mod directories { + // The following directories are used by bpfman. They should be created by bpfman service + // via the bpfman.service settings. They will be manually created in the case where bpfman + // is not being run as a service. + // + // ConfigurationDirectory: /etc/bpfman/ + pub(crate) const CFGDIR_MODE: u32 = 0o6750; + pub(crate) const CFGDIR: &str = "/etc/bpfman"; + pub(crate) const CFGDIR_STATIC_PROGRAMS: &str = "/etc/bpfman/programs.d"; + pub(crate) const CFGPATH_BPFMAN_CONFIG: &str = "/etc/bpfman/bpfman.toml"; + + // RuntimeDirectory: /run/bpfman/ + pub(crate) const RTDIR_MODE: u32 = 0o6770; + pub(crate) const RTDIR: &str = "/run/bpfman"; + pub(crate) const RTDIR_XDP_DISPATCHER: &str = "/run/bpfman/dispatchers/xdp"; + pub(crate) const RTDIR_TC_INGRESS_DISPATCHER: &str = "/run/bpfman/dispatchers/tc-ingress"; + pub(crate) const RTDIR_TC_EGRESS_DISPATCHER: &str = "/run/bpfman/dispatchers/tc-egress"; + pub(crate) const RTDIR_FS: &str = "/run/bpfman/fs"; + pub(crate) const RTDIR_FS_TC_INGRESS: &str = "/run/bpfman/fs/tc-ingress"; + pub(crate) const RTDIR_FS_TC_EGRESS: &str = "/run/bpfman/fs/tc-egress"; + pub(crate) const RTDIR_FS_XDP: &str = "/run/bpfman/fs/xdp"; + pub(crate) const RTDIR_FS_MAPS: &str = "/run/bpfman/fs/maps"; + pub(crate) const RTDIR_PROGRAMS: &str = "/run/bpfman/programs"; + // The TUF repository is used to store Rekor and Fulcio public keys. + pub(crate) const RTDIR_TUF: &str = "/run/bpfman/tuf"; + // StateDirectory: /var/lib/bpfman/ + pub(crate) const STDIR_MODE: u32 = 0o6770; + pub(crate) const STDIR: &str = "/var/lib/bpfman"; + #[cfg(not(test))] + pub(crate) const STDIR_DB: &str = "/var/lib/bpfman/db"; +} + +#[cfg(not(test))] +pub(crate) fn get_db_config() -> SledConfig { + SledConfig::default().path(STDIR_DB) +} + +#[cfg(test)] +pub(crate) fn get_db_config() -> SledConfig { + SledConfig::default().temporary(true) +} + +/// Loads an ebpf program. +pub async fn add_program(mut program: Program) -> Result { + let (config, root_db) = &setup().await?; + let mut image_manager = init_image_manager().await; + // This is only required in the add_program api + program.get_data_mut().load(root_db)?; + + let map_owner_id = program.get_data().get_map_owner_id()?; + // Set map_pin_path if we're using another program's maps + if let Some(map_owner_id) = map_owner_id { + let map_pin_path = is_map_owner_id_valid(root_db, map_owner_id)?; + program.get_data_mut().set_map_pin_path(&map_pin_path)?; + } + + program + .get_data_mut() + .set_program_bytes(root_db, &mut image_manager) + .await?; + + let result = match program { + Program::Xdp(_) | Program::Tc(_) => { + program.set_if_index(get_ifindex(&program.if_name().unwrap())?)?; + + add_multi_attach_program(root_db, &mut program, &mut image_manager, config).await + } + Program::Tracepoint(_) + | Program::Kprobe(_) + | Program::Uprobe(_) + | Program::Fentry(_) + | Program::Fexit(_) => add_single_attach_program(root_db, &mut program), + Program::Unsupported(_) => panic!("Cannot add unsupported program"), + }; + + match result { + Ok(id) => { + info!( + "Added {} program with name: {} and id: {id}", + program.kind(), + program.get_data().get_name()? + ); + + // Now that program is successfully loaded, update the id, maps hash table, + // and allow access to all maps by bpfman group members. + save_map(root_db, &mut program, id, map_owner_id)?; + + // Swap the db tree to be persisted with the unique program ID generated + // by the kernel. + program.get_data_mut().swap_tree(root_db, id)?; + + Ok(program) + } + Err(e) => { + // Cleanup any directories associated with the map_pin_path. + // map_pin_path may or may not exist depending on where the original + // error occured, so don't error if not there and preserve original error. + if let Some(pin_path) = program.get_data().get_map_pin_path()? { + let _ = cleanup_map_pin_path(&pin_path, map_owner_id); + } + + // Cleanup any program that failed to create. Ignore any delete errors. + let _ = program.delete(root_db); + + Err(e) + } + } +} + +/// Unloads and ebpf program. +pub async fn remove_program(id: u32) -> Result<(), BpfmanError> { + let (config, root_db) = &setup().await?; + + info!("Removing program with id: {id}"); + let prog = match get(root_db, &id) { + Some(p) => p, + None => { + return Err(BpfmanError::Error(format!( + "Program {0} does not exist or was not created by bpfman", + id, + ))); + } + }; + + let map_owner_id = prog.get_data().get_map_owner_id()?; + + match prog { + Program::Xdp(_) | Program::Tc(_) => { + let did = prog + .dispatcher_id()? + .ok_or(BpfmanError::DispatcherNotRequired)?; + let program_type = prog.kind(); + let if_index = prog.if_index()?; + let if_name = prog.if_name().unwrap(); + let direction = prog.direction()?; + + prog.delete(root_db) + .map_err(BpfmanError::BpfmanProgramDeleteError)?; + + remove_multi_attach_program( + root_db, + config, + did, + program_type, + if_index, + if_name, + direction, + ) + .await? + } + Program::Tracepoint(_) + | Program::Kprobe(_) + | Program::Uprobe(_) + | Program::Fentry(_) + | Program::Fexit(_) + | Program::Unsupported(_) => { + prog.delete(root_db) + .map_err(BpfmanError::BpfmanProgramDeleteError)?; + } + } + + delete_map(root_db, id, map_owner_id)?; + + Ok(()) +} + +/// Lists the currently loaded ebpf programs. +pub async fn list_programs(filter: ListFilter) -> Result, BpfmanError> { + let (_, root_db) = &setup().await?; + + debug!("BpfManager::list_programs()"); + + // Get an iterator for the bpfman load programs, a hash map indexed by program id. + let mut bpfman_progs: HashMap = get_programs_iter(root_db).collect(); + + // Call Aya to get ALL the loaded eBPF programs, and loop through each one. + Ok(loaded_programs() + .filter_map(|p| p.ok()) + .map(|prog| { + let prog_id = prog.id(); + + // If the program was loaded by bpfman (check the hash map), then use it. + // Otherwise, convert the data returned from Aya into an Unsupported Program Object. + match bpfman_progs.remove(&prog_id) { + Some(p) => p.to_owned(), + None => { + let db_tree = root_db + .open_tree(prog_id.to_string()) + .expect("Unable to open program database tree for listing programs"); + + let mut data = ProgramData::new_empty(db_tree); + if let Err(e) = data.set_kernel_info(&prog) { + warn!("Unable to set kernal info for prog {prog_id}, error: {e}"); + }; + + Program::Unsupported(data) + } + } + }) + .filter(|p| filter.matches(p)) + .collect()) +} + +/// Fetches more information regarding a currently loaded ebpf program. +pub async fn get_program(id: u32) -> Result { + let (_, root_db) = &setup().await?; + + debug!("Getting program with id: {id}"); + // If the program was loaded by bpfman, then use it. + // Otherwise, call Aya to get ALL the loaded eBPF programs, and convert the data + // returned from Aya into an Unsupported Program Object. + match get(root_db, &id) { + Some(p) => Ok(p.to_owned()), + None => loaded_programs() + .find_map(|p| { + let prog = p.ok()?; + if prog.id() == id { + let db_tree = root_db + .open_tree(prog.id().to_string()) + .expect("Unable to open program database tree for listing programs"); + + let mut data = ProgramData::new_empty(db_tree); + data.set_kernel_info(&prog) + .expect("unable to set kernel info"); + + Some(Program::Unsupported(data)) + } else { + None + } + }) + .ok_or(BpfmanError::Error(format!( + "Program {0} does not exist", + id + ))), + } +} + +/// Pulls an ebpf bytecode image from a remote OCI container registry. +pub async fn pull_bytecode(image: BytecodeImage) -> anyhow::Result<()> { + let (_, root_db) = &setup().await?; + let image_manager = &mut init_image_manager().await; + + image_manager + .get_image( + root_db, + &image.image_url, + image.image_pull_policy.clone(), + image.username.clone(), + image.password.clone(), + ) + .await?; + Ok(()) +} + +pub(crate) async fn init_database(sled_config: SledConfig) -> Result { + let database_config = open_config_file().database().to_owned().unwrap_or_default(); + for _ in 1..database_config.max_retries { + if let Ok(db) = sled_config.open() { + debug!("Successfully opened database"); + return Ok(db); + } else { + info!( + "Database lock is already held, retrying after {} milliseconds", + database_config.millisec_delay + ); + sleep(Duration::from_millis(database_config.millisec_delay)).await; + } + } + Err(BpfmanError::DatabaseLockError) +} + +// Make sure to call init_image_manger if the command requires interaction with +// an OCI based container registry. It should ONLY be used where needed, to +// explicitly control when bpfman blocks for network calls to both sigstore's +// cosign tuf registries and container registries. +pub(crate) async fn init_image_manager() -> ImageManager { + let config = open_config_file(); + ImageManager::new(config.signing().as_ref().map_or(true, |s| s.allow_unsigned)) + .await + .expect("failed to initialize image manager") +} + +fn get_dispatcher(id: &DispatcherId, root_db: &Db) -> Option { + let tree_name_prefix = match id { + DispatcherId::Xdp(DispatcherInfo(if_index, _)) => { + format!("{}_{}", XDP_DISPATCHER_PREFIX, if_index) + } + DispatcherId::Tc(DispatcherInfo(if_index, Some(direction))) => { + format!("{}_{}_{}", TC_DISPATCHER_PREFIX, if_index, direction) + } + _ => { + return None; + } + }; + + root_db + .tree_names() + .into_iter() + .find(|p| bytes_to_string(p).contains(&tree_name_prefix)) + .map(|p| { + let tree = root_db.open_tree(p).expect("unable to open database tree"); + Dispatcher::new_from_db(tree) + }) +} + +/// Returns the number of extension programs currently attached to the dispatcher that +/// would be used to attach the provided [`Program`]. +fn num_attached_programs(did: &DispatcherId, root_db: &Db) -> usize { + if let Some(d) = get_dispatcher(did, root_db) { + d.num_extensions() + } else { + 0 + } +} + +fn get(root_db: &Db, id: &u32) -> Option { + let prog_tree: sled::IVec = (PROGRAM_PREFIX.to_string() + &id.to_string()) + .as_bytes() + .into(); + if root_db.tree_names().contains(&prog_tree) { + let tree = root_db + .open_tree(prog_tree) + .expect("unable to open database tree"); + Some(Program::new_from_db(*id, tree).expect("Failed to build program from database")) + } else { + None + } +} + +fn filter( + root_db: &'_ Db, + program_type: ProgramType, + if_index: Option, + direction: Option, +) -> impl Iterator + '_ { + root_db + .tree_names() + .into_iter() + .filter(|p| bytes_to_string(p).contains(PROGRAM_PREFIX)) + .map(|p| { + let id = bytes_to_string(&p) + .split('_') + .last() + .unwrap() + .parse::() + .unwrap(); + let tree = root_db.open_tree(p).expect("unable to open database tree"); + Program::new_from_db(id, tree).expect("Failed to build program from database") + }) + .filter(move |p| { + p.kind() == program_type + && p.if_index().unwrap() == if_index + && p.direction().unwrap() == direction + }) +} + +// Adds a new program and sets the positions of programs that are to be attached via a dispatcher. +// Positions are set based on order of priority. Ties are broken based on: +// - Already attached programs are preferred +// - Program name. Lowest lexical order wins. +fn add_and_set_program_positions(root_db: &Db, program: Program) { + let program_type = program.kind(); + let if_index = program.if_index().unwrap(); + let direction = program.direction().unwrap(); + + let mut extensions = + filter(root_db, program_type, if_index, direction).collect::>(); + + extensions.sort_by_key(|b| { + ( + b.priority().unwrap(), + b.attached(), + b.get_data().get_name().unwrap().to_owned(), + ) + }); + for (i, v) in extensions.iter_mut().enumerate() { + v.set_position(i).expect("unable to set program position"); + } +} + +// Sets the positions of programs that are to be attached via a dispatcher. +// Positions are set based on order of priority. Ties are broken based on: +// - Already attached programs are preferred +// - Program name. Lowest lexical order wins. +fn set_program_positions( + root_db: &Db, + program_type: ProgramType, + if_index: u32, + direction: Option, +) { + let mut extensions = + filter(root_db, program_type, Some(if_index), direction).collect::>(); + + extensions.sort_by_key(|b| { + ( + b.priority().unwrap(), + b.attached(), + b.get_data().get_name().unwrap().to_owned(), + ) + }); + for (i, v) in extensions.iter_mut().enumerate() { + v.set_position(i).expect("unable to set program position"); + } +} + +fn get_programs_iter(root_db: &Db) -> impl Iterator + '_ { + root_db + .tree_names() + .into_iter() + .filter(|p| bytes_to_string(p).contains(PROGRAM_PREFIX)) + .map(|p| { + let id = bytes_to_string(&p) + .split('_') + .last() + .unwrap() + .parse::() + .unwrap(); + let tree = root_db.open_tree(p).expect("unable to open database tree"); + ( + id, + Program::new_from_db(id, tree).expect("Failed to build program from database"), + ) + }) +} + +async fn setup() -> Result<(Config, Db), BpfmanError> { + initialize_bpfman()?; + + Ok(( + open_config_file(), + init_database(get_db_config()) + .await + .expect("Unable to open root database"), + )) +} + +async fn add_multi_attach_program( + root_db: &Db, + program: &mut Program, + image_manager: &mut ImageManager, + config: &Config, +) -> Result { + debug!("BpfManager::add_multi_attach_program()"); + let name = &program.get_data().get_name()?; + + // This load is just to verify the BPF Function Name is valid. + // The actual load is performed in the XDP or TC logic. + // don't pin maps here. + let mut ext_loader = BpfLoader::new() + .allow_unsupported_maps() + .extension(name) + .load(&program.get_data().get_program_bytes()?)?; + + match ext_loader.program_mut(name) { + Some(_) => Ok(()), + None => Err(BpfmanError::BpfFunctionNameNotValid(name.to_owned())), + }?; + + let did = program + .dispatcher_id()? + .ok_or(BpfmanError::DispatcherNotRequired)?; + + let next_available_id = num_attached_programs(&did, root_db); + if next_available_id >= 10 { + return Err(BpfmanError::TooManyPrograms); + } + + debug!("next_available_id={next_available_id}"); + + let program_type = program.kind(); + let if_index = program.if_index()?; + let if_name = program.if_name().unwrap().to_string(); + let direction = program.direction()?; + + add_and_set_program_positions(root_db, program.clone()); + + let mut programs: Vec = + filter(root_db, program_type, if_index, direction).collect::>(); + + let old_dispatcher = get_dispatcher(&did, root_db); + + let if_config = if let Some(ref i) = config.interfaces() { + i.get(&if_name) + } else { + None + }; + let next_revision = if let Some(ref old) = old_dispatcher { + old.next_revision() + } else { + 1 + }; + + Dispatcher::new( + root_db, + if_config, + &mut programs, + next_revision, + old_dispatcher, + image_manager, + ) + .await + .or_else(|e| { + // If kernel ID was never set there's no pins to cleanup here so just continue + if program.get_data().get_id().is_ok() { + program + .delete(root_db) + .map_err(BpfmanError::BpfmanProgramDeleteError)?; + } + Err(e) + })?; + + let id = program.get_data().get_id()?; + program.set_attached(); + + Ok(id) +} + +pub(crate) fn add_single_attach_program(root_db: &Db, p: &mut Program) -> Result { + debug!("BpfManager::add_single_attach_program()"); + let name = &p.get_data().get_name()?; + let mut bpf = BpfLoader::new(); + + let data = &p.get_data().get_global_data()?; + for (key, value) in data { + bpf.set_global(key, value.as_slice(), true); + } + + // If map_pin_path is set already it means we need to use a pin + // path which should already exist on the system. + if let Some(map_pin_path) = p.get_data().get_map_pin_path()? { + debug!( + "single-attach program {name} is using maps from {:?}", + map_pin_path + ); + bpf.map_pin_path(map_pin_path); + } + + let mut loader = bpf + .allow_unsupported_maps() + .load(&p.get_data().get_program_bytes()?)?; + + let raw_program = loader + .program_mut(name) + .ok_or(BpfmanError::BpfFunctionNameNotValid(name.to_owned()))?; + + let res = match p { + Program::Tracepoint(ref mut program) => { + let tracepoint = program.get_tracepoint()?; + let parts: Vec<&str> = tracepoint.split('/').collect(); + if parts.len() != 2 { + return Err(BpfmanError::InvalidAttach( + program.get_tracepoint()?.to_string(), + )); + } + let category = parts[0].to_owned(); + let name = parts[1].to_owned(); + + let tracepoint: &mut TracePoint = raw_program.try_into()?; + + tracepoint.load()?; + program + .get_data_mut() + .set_kernel_info(&tracepoint.info()?)?; + + let id = program.data.get_id()?; + + let link_id = tracepoint.attach(&category, &name)?; + + let owned_link: TracePointLink = tracepoint.take_link(link_id)?; + let fd_link: FdLink = owned_link + .try_into() + .expect("unable to get owned tracepoint attach link"); + + fd_link + .pin(format!("{RTDIR_FS}/prog_{}_link", id)) + .map_err(BpfmanError::UnableToPinLink)?; + + tracepoint + .pin(format!("{RTDIR_FS}/prog_{}", id)) + .map_err(BpfmanError::UnableToPinProgram)?; + + Ok(id) + } + Program::Kprobe(ref mut program) => { + let requested_probe_type = match program.get_retprobe()? { + true => Kretprobe, + false => Kprobe, + }; + + if requested_probe_type == Kretprobe && program.get_offset()? != 0 { + return Err(BpfmanError::Error(format!( + "offset not allowed for {Kretprobe}" + ))); + } + + let kprobe: &mut KProbe = raw_program.try_into()?; + kprobe.load()?; + + // verify that the program loaded was the same type as the + // user requested + let loaded_probe_type = ProbeType::from(kprobe.kind()); + if requested_probe_type != loaded_probe_type { + return Err(BpfmanError::Error(format!( + "expected {requested_probe_type}, loaded program is {loaded_probe_type}" + ))); + } + + program.get_data_mut().set_kernel_info(&kprobe.info()?)?; + + let id = program.data.get_id()?; + + let link_id = kprobe.attach(program.get_fn_name()?, program.get_offset()?)?; + + let owned_link: KProbeLink = kprobe.take_link(link_id)?; + let fd_link: FdLink = owned_link + .try_into() + .expect("unable to get owned kprobe attach link"); + + fd_link + .pin(format!("{RTDIR_FS}/prog_{}_link", id)) + .map_err(BpfmanError::UnableToPinLink)?; + + kprobe + .pin(format!("{RTDIR_FS}/prog_{}", id)) + .map_err(BpfmanError::UnableToPinProgram)?; + + Ok(id) + } + Program::Uprobe(ref mut program) => { + let requested_probe_type = match program.get_retprobe()? { + true => Uretprobe, + false => Uprobe, + }; + + let uprobe: &mut UProbe = raw_program.try_into()?; + uprobe.load()?; + + // verify that the program loaded was the same type as the + // user requested + let loaded_probe_type = ProbeType::from(uprobe.kind()); + if requested_probe_type != loaded_probe_type { + return Err(BpfmanError::Error(format!( + "expected {requested_probe_type}, loaded program is {loaded_probe_type}" + ))); + } + + program.get_data_mut().set_kernel_info(&uprobe.info()?)?; + + let id = program.data.get_id()?; + + let program_pin_path = format!("{RTDIR_FS}/prog_{}", id); + let fn_name = program.get_fn_name()?; + + uprobe + .pin(program_pin_path.clone()) + .map_err(BpfmanError::UnableToPinProgram)?; + + match program.get_container_pid()? { + None => { + // Attach uprobe in same container as the bpfman process + let link_id = uprobe.attach( + fn_name.as_deref(), + program.get_offset()?, + program.get_target()?, + None, + )?; + + let owned_link: UProbeLink = uprobe.take_link(link_id)?; + let fd_link: FdLink = owned_link + .try_into() + .expect("unable to get owned uprobe attach link"); + + fd_link + .pin(format!("{RTDIR_FS}/prog_{}_link", id)) + .map_err(BpfmanError::UnableToPinLink)?; + } + Some(p) => { + // Attach uprobe in different container from the bpfman process + let offset = program.get_offset()?.to_string(); + let container_pid = p.to_string(); + let mut prog_args = vec![ + "uprobe".to_string(), + "--program-pin-path".to_string(), + program_pin_path, + "--offset".to_string(), + offset, + "--target".to_string(), + program.get_target()?.to_string(), + "--container-pid".to_string(), + container_pid, + ]; + + if let Some(fn_name) = &program.get_fn_name()? { + prog_args.extend(["--fn-name".to_string(), fn_name.to_string()]) + } + + if program.get_retprobe()? { + prog_args.push("--retprobe".to_string()); + } + + if let Some(pid) = program.get_pid()? { + prog_args.extend(["--pid".to_string(), pid.to_string()]) + } + + debug!("calling bpfman-ns to attach uprobe in pid: {:?}", p); + + // Figure out where the bpfman-ns binary is located + let bpfman_ns_path = if Path::new("./target/debug/bpfman-ns").exists() { + // If we're running natively from the bpfman + // directory, use the binary in the target/debug + // directory + "./target/debug/bpfman-ns" + } else if Path::new("./bpfman-ns").exists() { + // If we're running on kubernetes, the bpfman-ns + // binary will be in the current directory + "./bpfman-ns" + } else { + // look for bpfman-ns in the PATH + "bpfman-ns" + }; + + let output = std::process::Command::new(bpfman_ns_path) + .args(prog_args) + .output(); + + match output { + Ok(o) => { + if !o.status.success() { + info!( + "Error from bpfman-ns: {:?}", + get_error_msg_from_stderr(&o.stderr) + ); + return Err(BpfmanError::ContainerAttachError { + program_type: "uprobe".to_string(), + container_pid: program.get_container_pid()?.unwrap(), + }); + }; + } + Err(e) => { + info!("bpfman-ns returned error: {:?}", e); + return Err(BpfmanError::ContainerAttachError { + program_type: "uprobe".to_string(), + container_pid: program.get_container_pid()?.unwrap(), + }); + } + }; + } + }; + + Ok(id) + } + Program::Fentry(ref mut program) => { + let fn_name = program.get_fn_name()?; + let btf = Btf::from_sys_fs()?; + let fentry: &mut FEntry = raw_program.try_into()?; + fentry + .load(&fn_name, &btf) + .map_err(BpfmanError::BpfProgramError)?; + program.get_data_mut().set_kernel_info(&fentry.info()?)?; + + let id = program.data.get_id()?; + let link_id = fentry.attach()?; + let owned_link: FEntryLink = fentry.take_link(link_id)?; + let fd_link: FdLink = owned_link.into(); + fd_link + .pin(format!("{RTDIR_FS}/prog_{}_link", id)) + .map_err(BpfmanError::UnableToPinLink)?; + + fentry + .pin(format!("{RTDIR_FS}/prog_{}", id)) + .map_err(BpfmanError::UnableToPinProgram)?; + + Ok(id) + } + Program::Fexit(ref mut program) => { + let fn_name = program.get_fn_name()?; + let btf = Btf::from_sys_fs()?; + let fexit: &mut FExit = raw_program.try_into()?; + fexit + .load(&fn_name, &btf) + .map_err(BpfmanError::BpfProgramError)?; + program.get_data_mut().set_kernel_info(&fexit.info()?)?; + + let id = program.data.get_id()?; + let link_id = fexit.attach()?; + let owned_link: FExitLink = fexit.take_link(link_id)?; + let fd_link: FdLink = owned_link.into(); + fd_link + .pin(format!("{RTDIR_FS}/prog_{}_link", id)) + .map_err(BpfmanError::UnableToPinLink)?; + + fexit + .pin(format!("{RTDIR_FS}/prog_{}", id)) + .map_err(BpfmanError::UnableToPinProgram)?; + + Ok(id) + } + _ => panic!("not a supported single attach program"), + }; + + match res { + Ok(id) => { + // If this program is the map(s) owner pin all maps (except for .rodata and .bss) by name. + if p.get_data().get_map_pin_path()?.is_none() { + let map_pin_path = calc_map_pin_path(id); + p.get_data_mut().set_map_pin_path(&map_pin_path)?; + create_map_pin_path(&map_pin_path)?; + + for (name, map) in loader.maps_mut() { + if !should_map_be_pinned(name) { + continue; + } + debug!( + "Pinning map: {name} to path: {}", + map_pin_path.join(name).display() + ); + map.pin(map_pin_path.join(name)) + .map_err(BpfmanError::UnableToPinMap)?; + } + } + } + Err(_) => { + // If kernel ID was never set there's no pins to cleanup here so just continue + if p.get_data().get_id().is_ok() { + p.delete(root_db) + .map_err(BpfmanError::BpfmanProgramDeleteError)?; + }; + } + }; + + res +} + +async fn remove_multi_attach_program( + root_db: &Db, + config: &Config, + did: DispatcherId, + program_type: ProgramType, + if_index: Option, + if_name: String, + direction: Option, +) -> Result<(), BpfmanError> { + debug!("BpfManager::remove_multi_attach_program()"); + let mut image_manager = init_image_manager().await; + + let next_available_id = num_attached_programs(&did, root_db) - 1; + debug!("next_available_id = {next_available_id}"); + + let mut old_dispatcher = get_dispatcher(&did, root_db); + + if let Some(ref mut old) = old_dispatcher { + if next_available_id == 0 { + // Delete the dispatcher + return old.delete(root_db, true); + } + } + + set_program_positions(root_db, program_type, if_index.unwrap(), direction); + + // Intentionally don't add filter program here + let mut programs: Vec = filter(root_db, program_type, if_index, direction).collect(); + + let if_config = if let Some(ref i) = config.interfaces() { + i.get(&if_name) + } else { + None + }; + let next_revision = if let Some(ref old) = old_dispatcher { + old.next_revision() + } else { + 1 + }; + debug!("next_revision = {next_revision}"); + + Dispatcher::new( + root_db, + if_config, + &mut programs, + next_revision, + old_dispatcher, + &mut image_manager, + ) + .await?; + + Ok(()) +} + +// This function checks to see if the user provided map_owner_id is valid. +fn is_map_owner_id_valid(root_db: &Db, map_owner_id: u32) -> Result { + let map_pin_path = calc_map_pin_path(map_owner_id); + let name: &sled::IVec = &format!("{}{}", MAP_PREFIX, map_owner_id).as_bytes().into(); + + if root_db.tree_names().contains(name) { + // Return the map_pin_path + return Ok(map_pin_path); + } + Err(BpfmanError::Error( + "map_owner_id does not exists".to_string(), + )) +} + +// This function is called if the program's map directory was created, +// but the eBPF program failed to load. save_map() has not been called, +// so self.maps has not been updated for this program. +// If the user provided a ID of program to share a map with, +// then map the directory is still in use and there is nothing to do. +// Otherwise, the map directory was created so it must +// deleted. +fn cleanup_map_pin_path(map_pin_path: &Path, map_owner_id: Option) -> Result<(), BpfmanError> { + if map_owner_id.is_none() && map_pin_path.exists() { + let _ = remove_dir_all(map_pin_path) + .map_err(|e| BpfmanError::Error(format!("can't delete map dir: {e}"))); + Ok(()) + } else { + Ok(()) + } +} + +// This function writes the map to the map hash table. If this eBPF +// program is the map owner, then a new entry is add to the map hash +// table and permissions on the directory are updated to grant bpfman +// user group access to all the maps in the directory. If this eBPF +// program is not the owner, then the eBPF program ID is added to +// the Used-By array. +fn save_map( + root_db: &Db, + program: &mut Program, + id: u32, + map_owner_id: Option, +) -> Result<(), BpfmanError> { + let data = program.get_data_mut(); + + match map_owner_id { + Some(m) => { + if let Some(map) = get_map(m, root_db) { + push_maps_used_by(map.clone(), id)?; + let used_by = get_maps_used_by(map)?; + + // This program has no been inserted yet, so set map_used_by to + // newly updated list. + data.set_maps_used_by(used_by.clone())?; + + // Update all the programs using the same map with the updated map_used_by. + for used_by_id in used_by.iter() { + if let Some(mut program) = get(root_db, used_by_id) { + program.get_data_mut().set_maps_used_by(used_by.clone())?; + } + } + } else { + return Err(BpfmanError::Error( + "map_owner_id does not exist".to_string(), + )); + } + } + None => { + let db_tree = root_db + .open_tree(format!("{}{}", MAP_PREFIX, id)) + .expect("Unable to open map db tree"); + + set_maps_used_by(db_tree, vec![id])?; + + // Update this program with the updated map_used_by + data.set_maps_used_by(vec![id])?; + + // Set the permissions on the map_pin_path directory. + if let Some(map_pin_path) = data.get_map_pin_path()? { + if let Some(path) = map_pin_path.to_str() { + debug!("bpf set dir permissions for {}", path); + set_dir_permissions(path, MAPS_MODE); + } else { + return Err(BpfmanError::Error(format!( + "invalid map_pin_path {} for {}", + map_pin_path.display(), + id + ))); + } + } else { + return Err(BpfmanError::Error(format!( + "map_pin_path should be set for {}", + id + ))); + } + } + } + + Ok(()) +} + +// This function cleans up a map entry when an eBPF program is +// being unloaded. If the eBPF program is the map owner, then +// the map is removed from the hash table and the associated +// directory is removed. If this eBPF program is referencing a +// map from another eBPF program, then this eBPF programs ID +// is removed from the UsedBy array. +fn delete_map(root_db: &Db, id: u32, map_owner_id: Option) -> Result<(), BpfmanError> { + let index = match map_owner_id { + Some(i) => i, + None => id, + }; + + if let Some(map) = get_map(index, root_db) { + let mut used_by = get_maps_used_by(map.clone())?; + + if let Some(index) = used_by.iter().position(|value| *value == id) { + used_by.swap_remove(index); + } + + clear_maps_used_by(map.clone()); + set_maps_used_by(map.clone(), used_by.clone())?; + + if used_by.is_empty() { + let path: PathBuf = calc_map_pin_path(index); + // No more programs using this map, so remove the entry from the map list. + root_db + .drop_tree(MAP_PREFIX.to_string() + &index.to_string()) + .expect("unable to drop maps tree"); + remove_dir_all(path) + .map_err(|e| BpfmanError::Error(format!("can't delete map dir: {e}")))?; + } else { + // Update all the programs still using the same map with the updated map_used_by. + for id in used_by.iter() { + if let Some(mut program) = get(root_db, id) { + program.get_data_mut().set_maps_used_by(used_by.clone())?; + } + } + } + } else { + return Err(BpfmanError::Error( + "map_pin_path does not exists".to_string(), + )); + } + + Ok(()) +} + +// map_pin_path is a the directory the maps are located. Currently, it +// is a fixed bpfman location containing the map_index, which is a ID. +// The ID is either the programs ID, or the ID of another program +// that map_owner_id references. +pub(crate) fn calc_map_pin_path(id: u32) -> PathBuf { + PathBuf::from(format!("{RTDIR_FS_MAPS}/{}", id)) +} + +// Create the map_pin_path for a given program. +pub(crate) fn create_map_pin_path(p: &Path) -> Result<(), BpfmanError> { + create_dir_all(p).map_err(|e| BpfmanError::Error(format!("can't create map dir: {e}"))) +} + +// set_maps_used_by differs from other setters in that it's explicitly idempotent. +pub(crate) fn set_maps_used_by(db_tree: sled::Tree, ids: Vec) -> Result<(), BpfmanError> { + ids.iter().enumerate().try_for_each(|(i, v)| { + sled_insert( + &db_tree, + format!("{MAPS_USED_BY_PREFIX}{i}").as_str(), + &v.to_ne_bytes(), + ) + }) +} + +// set_maps_used_by differs from other setters in that it's explicitly idempotent. +fn push_maps_used_by(db_tree: sled::Tree, id: u32) -> Result<(), BpfmanError> { + let existing_maps_used_by = get_maps_used_by(db_tree.clone())?; + + sled_insert( + &db_tree, + format!("{MAPS_USED_BY_PREFIX}{}", existing_maps_used_by.len() + 1).as_str(), + &id.to_ne_bytes(), + ) +} + +fn get_maps_used_by(db_tree: sled::Tree) -> Result, BpfmanError> { + db_tree + .scan_prefix(MAPS_USED_BY_PREFIX) + .map(|n| n.map(|(_, v)| bytes_to_u32(v.to_vec()))) + .map(|n| { + n.map_err(|e| { + BpfmanError::DatabaseError("Failed to get maps used by".to_string(), e.to_string()) + }) + }) + .collect() +} + +pub(crate) fn clear_maps_used_by(db_tree: sled::Tree) { + db_tree.scan_prefix(MAPS_USED_BY_PREFIX).for_each(|n| { + db_tree + .remove(n.unwrap().0) + .expect("unable to clear maps used by"); + }); +} + +fn get_map(id: u32, root_db: &Db) -> Option { + root_db + .tree_names() + .into_iter() + .find(|n| bytes_to_string(n) == format!("{}{}", MAP_PREFIX, id)) + .map(|n| root_db.open_tree(n).expect("unable to open map tree")) +} diff --git a/bpfman/src/main.rs b/bpfman/src/main.rs deleted file mode 100644 index a2e905081..000000000 --- a/bpfman/src/main.rs +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use clap::Parser; - -mod bpf; -mod cli; -mod command; -mod dispatcher_config; -mod errors; -mod multiprog; -mod oci_utils; -mod rpc; -mod serve; -mod static_program; -mod storage; -mod utils; - -const BPFMAN_ENV_LOG_LEVEL: &str = "RUST_LOG"; - -fn main() -> anyhow::Result<()> { - let cli = cli::Cli::parse(); - cli.command.execute() -} diff --git a/bpfman/src/multiprog/mod.rs b/bpfman/src/multiprog/mod.rs index fc1dad522..9359c6af5 100644 --- a/bpfman/src/multiprog/mod.rs +++ b/bpfman/src/multiprog/mod.rs @@ -4,21 +4,23 @@ mod tc; mod xdp; -use bpfman_api::{ - config::{InterfaceConfig, XdpMode}, - ProgramType, -}; use log::debug; +use sled::Db; pub use tc::TcDispatcher; -use tokio::sync::mpsc::Sender; pub use xdp::XdpDispatcher; use crate::{ - command::{Direction, Program}, + config::{InterfaceConfig, XdpMode}, errors::BpfmanError, - oci_utils::image_manager::Command as ImageManagerCommand, + oci_utils::image_manager::ImageManager, + types::{Direction, Program, ProgramType}, + utils::bytes_to_string, }; +pub(crate) const TC_DISPATCHER_PREFIX: &str = "tc_dispatcher_"; +pub(crate) const XDP_DISPATCHER_PREFIX: &str = "xdp_dispatcher_"; + +#[derive(Debug)] pub(crate) enum Dispatcher { Xdp(XdpDispatcher), Tc(TcDispatcher), @@ -26,55 +28,47 @@ pub(crate) enum Dispatcher { impl Dispatcher { pub async fn new( + root_db: &Db, config: Option<&InterfaceConfig>, - programs: &mut [&mut Program], + programs: &mut [Program], revision: u32, old_dispatcher: Option, - image_manager: Sender, + image_manager: &mut ImageManager, ) -> Result { debug!("Dispatcher::new()"); let p = programs .first() .ok_or_else(|| BpfmanError::Error("No programs to load".to_string()))?; let if_index = p - .if_index() + .if_index()? .ok_or_else(|| BpfmanError::Error("missing ifindex".to_string()))?; - let if_name = p - .if_name() - .ok_or_else(|| BpfmanError::Error("missing ifname".to_string()))?; - let direction = p.direction(); + let if_name = p.if_name()?; + let direction = p.direction()?; let xdp_mode = if let Some(c) = config { - c.xdp_mode + c.xdp_mode() } else { - XdpMode::Skb + &XdpMode::Skb }; let d = match p.kind() { ProgramType::Xdp => { - let x = XdpDispatcher::new( - xdp_mode, - &if_index, - if_name, - programs, - revision, - old_dispatcher, - image_manager, - ) - .await?; + let mut x = + XdpDispatcher::new(root_db, xdp_mode, if_index, if_name.to_string(), revision)?; + + x.load(root_db, programs, old_dispatcher, image_manager) + .await?; Dispatcher::Xdp(x) } ProgramType::Tc => { - let direction = direction - .ok_or_else(|| BpfmanError::Error("direction required".to_string()))?; - let t = TcDispatcher::new( - direction, - &if_index, - if_name, - programs, + let mut t = TcDispatcher::new( + root_db, + direction.expect("missing direction"), + if_index, + if_name.to_string(), revision, - old_dispatcher, - image_manager, - ) - .await?; + )?; + + t.load(root_db, programs, old_dispatcher, image_manager) + .await?; Dispatcher::Tc(t) } _ => return Err(BpfmanError::DispatcherNotRequired), @@ -82,42 +76,51 @@ impl Dispatcher { Ok(d) } - pub(crate) fn delete(&mut self, full: bool) -> Result<(), BpfmanError> { + pub(crate) fn new_from_db(db_tree: sled::Tree) -> Dispatcher { + if bytes_to_string(&db_tree.name()).contains("xdp") { + Dispatcher::Xdp(XdpDispatcher::new_from_db(db_tree)) + } else { + Dispatcher::Tc(TcDispatcher::new_from_db(db_tree)) + } + } + + pub(crate) fn delete(&mut self, root_db: &Db, full: bool) -> Result<(), BpfmanError> { debug!("Dispatcher::delete()"); match self { - Dispatcher::Xdp(d) => d.delete(full), - Dispatcher::Tc(d) => d.delete(full), + Dispatcher::Xdp(d) => d.delete(root_db, full), + Dispatcher::Tc(d) => d.delete(root_db, full), } } pub(crate) fn next_revision(&self) -> u32 { let current = match self { - Dispatcher::Xdp(d) => d.revision(), - Dispatcher::Tc(d) => d.revision(), + Dispatcher::Xdp(d) => d + .get_revision() + .expect("failed to get xdp_dispatcher revision"), + Dispatcher::Tc(d) => d + .get_revision() + .expect("failed to get tc_dispatcher revision"), }; current.wrapping_add(1) } - pub(crate) fn if_name(&self) -> String { - match self { - Dispatcher::Xdp(d) => d.if_name(), - Dispatcher::Tc(d) => d.if_name(), - } - } - pub(crate) fn num_extensions(&self) -> usize { match self { - Dispatcher::Xdp(d) => d.num_extensions(), - Dispatcher::Tc(d) => d.num_extensions(), + Dispatcher::Xdp(d) => d + .get_num_extensions() + .expect("failed to get xdp_dispatcher num_extensions"), + Dispatcher::Tc(d) => d + .get_num_extensions() + .expect("failed to get tc_dispatcher num_extensions"), } } } -#[derive(Debug, Hash, Eq, PartialEq)] +#[derive(Debug, Clone, Hash, Eq, PartialEq)] pub(crate) enum DispatcherId { Xdp(DispatcherInfo), Tc(DispatcherInfo), } -#[derive(Debug, Hash, Eq, PartialEq)] +#[derive(Debug, Clone, Hash, Eq, PartialEq)] pub(crate) struct DispatcherInfo(pub u32, pub Option); diff --git a/bpfman/src/multiprog/tc.rs b/bpfman/src/multiprog/tc.rs index 6b1d7f71e..ca1c540a4 100644 --- a/bpfman/src/multiprog/tc.rs +++ b/bpfman/src/multiprog/tc.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of bpfman -use std::{fs, io::BufReader, mem}; +use std::{fs, mem}; use aya::{ programs::{ @@ -11,54 +11,95 @@ use aya::{ }, Bpf, BpfLoader, }; -use bpfman_api::{util::directories::*, ImagePullPolicy}; use futures::stream::TryStreamExt; use log::debug; -use netlink_packet_route::tc::Nla; -use serde::{Deserialize, Serialize}; -use tokio::sync::{mpsc::Sender, oneshot}; +use netlink_packet_route::tc::TcAttribute; +use sled::Db; use crate::{ - bpf::{calc_map_pin_path, create_map_pin_path}, - command::{ - Direction, - Direction::{Egress, Ingress}, - Program, TcProgram, - }, + calc_map_pin_path, create_map_pin_path, + directories::*, dispatcher_config::TcDispatcherConfig, errors::BpfmanError, - multiprog::Dispatcher, - oci_utils::image_manager::{BytecodeImage, Command as ImageManagerCommand}, - utils::should_map_be_pinned, + multiprog::{Dispatcher, TC_DISPATCHER_PREFIX}, + oci_utils::image_manager::ImageManager, + types::{ + BytecodeImage, Direction, + Direction::{Egress, Ingress}, + ImagePullPolicy, Program, TcProgram, + }, + utils::{ + bytes_to_string, bytes_to_u16, bytes_to_u32, bytes_to_usize, should_map_be_pinned, + sled_get, sled_get_option, sled_insert, + }, }; const DEFAULT_PRIORITY: u32 = 50; // Default priority for user programs in the dispatcher const TC_DISPATCHER_PRIORITY: u16 = 50; // Default TC priority for TC Dispatcher -#[derive(Debug, Serialize, Deserialize)] +/// These constants define the key of SLED DB +const REVISION: &str = "revision"; +const IF_INDEX: &str = "if_index"; +const IF_NAME: &str = "if_name"; +const PRIORITY: &str = "priority"; +const DIRECTION: &str = "direction"; +const NUM_EXTENSIONS: &str = "num_extension"; +const PROGRAM_NAME: &str = "program_name"; +const HANDLE: &str = "handle"; + +#[derive(Debug)] pub struct TcDispatcher { - pub(crate) revision: u32, - if_index: u32, - if_name: String, - direction: Direction, - priority: u16, - handle: Option, - num_extensions: usize, - #[serde(skip)] + db_tree: sled::Tree, loader: Option, - program_name: Option, } impl TcDispatcher { - pub(crate) async fn new( + pub(crate) fn new( + root_db: &Db, direction: Direction, - if_index: &u32, + if_index: u32, if_name: String, - programs: &mut [&mut Program], revision: u32, + ) -> Result { + let db_tree = root_db + .open_tree(format!( + "{}_{}_{}_{}", + TC_DISPATCHER_PREFIX, if_index, direction, revision + )) + .expect("Unable to open tc dispatcher database tree"); + + let mut dp = Self { + db_tree, + loader: None, + }; + + dp.set_ifindex(if_index)?; + dp.set_ifname(&if_name)?; + dp.set_direction(direction)?; + dp.set_revision(revision)?; + dp.set_priority(TC_DISPATCHER_PRIORITY)?; + Ok(dp) + } + + // TODO(astoycos) check to ensure the expected fs pins are there. + pub(crate) fn new_from_db(db_tree: sled::Tree) -> Self { + Self { + db_tree, + loader: None, + } + } + + pub(crate) async fn load( + &mut self, + root_db: &Db, + programs: &mut [Program], old_dispatcher: Option, - image_manager: Sender, - ) -> Result { + image_manager: &mut ImageManager, + ) -> Result<(), BpfmanError> { + let if_index = self.get_ifindex()?; + let revision = self.get_revision()?; + let direction = self.get_direction()?; + debug!("TcDispatcher::new() for if_index {if_index}, revision {revision}"); let mut extensions: Vec<&mut TcProgram> = programs .iter_mut() @@ -69,7 +110,7 @@ impl TcDispatcher { .collect(); let mut chain_call_actions = [0; 10]; for v in extensions.iter() { - chain_call_actions[v.current_position.unwrap()] = v.proceed_on.mask() + chain_call_actions[v.get_current_position()?.unwrap()] = v.get_proceed_on()?.mask() } let config = TcDispatcherConfig { @@ -85,32 +126,18 @@ impl TcDispatcher { None, None, ); - let (tx, rx) = oneshot::channel(); - image_manager - .send(ImageManagerCommand::Pull { - image: image.image_url.clone(), - pull_policy: image.image_pull_policy.clone(), - username: image.username.clone(), - password: image.password.clone(), - resp: tx, - }) - .await - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))?; - - let (path, bpf_function_name) = rx - .await - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))? - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))?; - - let (tx, rx) = oneshot::channel(); - image_manager - .send(ImageManagerCommand::GetBytecode { path, resp: tx }) - .await - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))?; - let program_bytes = rx - .await - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))? - .map_err(BpfmanError::BpfBytecodeError)?; + + let (path, bpf_function_name) = image_manager + .get_image( + root_db, + &image.image_url, + image.image_pull_policy.clone(), + image.username.clone(), + image.password.clone(), + ) + .await?; + + let program_bytes = image_manager.get_bytecode_from_image_store(root_db, path)?; let mut loader = BpfLoader::new() .set_global("CONFIG", &config, true) @@ -128,21 +155,13 @@ impl TcDispatcher { let path = format!("{base}/dispatcher_{if_index}_{revision}"); fs::create_dir_all(path).unwrap(); - let mut dispatcher = TcDispatcher { - revision, - if_index: *if_index, - if_name, - direction, - num_extensions: extensions.len(), - priority: TC_DISPATCHER_PRIORITY, - handle: None, - loader: Some(loader), - program_name: Some(bpf_function_name), - }; - dispatcher.attach_extensions(&mut extensions).await?; - dispatcher.attach(old_dispatcher).await?; - dispatcher.save()?; - Ok(dispatcher) + self.loader = Some(loader); + self.set_num_extensions(extensions.len())?; + self.set_program_name(&bpf_function_name)?; + + self.attach_extensions(&mut extensions)?; + self.attach(root_db, old_dispatcher).await?; + Ok(()) } /// has_qdisc returns true if the qdisc_name is found on the if_index. @@ -153,46 +172,56 @@ impl TcDispatcher { let mut qdiscs = handle.qdisc().get().execute(); while let Some(qdisc_message) = qdiscs.try_next().await? { if qdisc_message.header.index == if_index - && qdisc_message.nlas.contains(&Nla::Kind(qdisc_name.clone())) + && qdisc_message + .attributes + .contains(&TcAttribute::Kind(qdisc_name.clone())) { return Ok(true); } } + Ok(false) } - async fn attach(&mut self, old_dispatcher: Option) -> Result<(), BpfmanError> { + async fn attach( + &mut self, + root_db: &Db, + old_dispatcher: Option, + ) -> Result<(), BpfmanError> { + let if_index = self.get_ifindex()?; + let iface = self.get_ifname()?; + let priority = self.get_priority()?; + let revision = self.get_revision()?; + let direction = self.get_direction()?; + let program_name = self.get_program_name()?; + debug!( "TcDispatcher::attach() for if_index {}, revision {}", - self.if_index, self.revision + if_index, revision ); - let iface = self.if_name.clone(); // Aya returns an error when trying to add a qdisc that already exists, which could be ingress or clsact. We // need to make sure that the qdisc installed is the one that we want, i.e. clsact. If the qdisc is an ingress // qdisc, we return an error. If the qdisc is a clsact qdisc, we do nothing. Otherwise, we add a clsact qdisc. // no need to add a new clsact qdisc if one already exists. - if TcDispatcher::has_qdisc("clsact".to_string(), self.if_index as i32).await? { + if TcDispatcher::has_qdisc("clsact".to_string(), if_index as i32).await? { debug!( "clsact qdisc found for if_index {}, no need to add a new clsact qdisc", - self.if_index + if_index ); // if ingress qdisc exists, return error. - } else if TcDispatcher::has_qdisc("ingress".to_string(), self.if_index as i32).await? { - debug!("ingress qdisc found for if_index {}", self.if_index); + } else if TcDispatcher::has_qdisc("ingress".to_string(), if_index as i32).await? { + debug!("ingress qdisc found for if_index {}", if_index); return Err(BpfmanError::InvalidAttach(format!( "Ingress qdisc found for if_index {}", - self.if_index + if_index ))); // otherwise, add a new clsact qdisc. } else { - debug!( - "No qdisc found for if_index {}, adding clsact", - self.if_index - ); + debug!("No qdisc found for if_index {}, adding clsact", if_index); let _ = tc::qdisc_add_clsact(&iface); } @@ -200,11 +229,11 @@ impl TcDispatcher { .loader .as_mut() .ok_or(BpfmanError::NotLoaded)? - .program_mut(self.program_name.clone().unwrap().as_str()) + .program_mut(program_name.as_str()) .unwrap() .try_into()?; - let attach_type = match self.direction { + let attach_type = match direction { Direction::Ingress => TcAttachType::Ingress, Direction::Egress => TcAttachType::Egress, }; @@ -213,13 +242,13 @@ impl TcDispatcher { &iface, attach_type, TcOptions { - priority: self.priority, + priority, ..Default::default() }, )?; let link = new_dispatcher.take_link(link_id)?; - self.handle = Some(link.handle()); + self.set_handle(link.handle())?; mem::forget(link); if let Some(Dispatcher::Tc(mut d)) = old_dispatcher { @@ -227,57 +256,59 @@ impl TcDispatcher { // was attached above, the new dispatcher may get the same handle // as the old one had. If this happens, the new dispatcher will get // detached if we do a full delete, so don't do it. - if d.handle != self.handle { - d.delete(true)?; + if d.get_handle()? != self.get_handle()? { + d.delete(root_db, true)?; } else { - d.delete(false)?; + d.delete(root_db, false)?; } } Ok(()) } - async fn attach_extensions( - &mut self, - extensions: &mut [&mut TcProgram], - ) -> Result<(), BpfmanError> { + fn attach_extensions(&mut self, extensions: &mut [&mut TcProgram]) -> Result<(), BpfmanError> { + let if_index = self.get_ifindex()?; + let revision = self.get_revision()?; + let direction = self.get_direction()?; + let program_name = self.get_program_name()?; + debug!( "TcDispatcher::attach_extensions() for if_index {}, revision {}", - self.if_index, self.revision + if_index, revision ); - let if_index = self.if_index; let dispatcher: &mut SchedClassifier = self .loader .as_mut() .ok_or(BpfmanError::NotLoaded)? - .program_mut(self.program_name.clone().unwrap().as_str()) + .program_mut(program_name.as_str()) .unwrap() .try_into()?; - extensions.sort_by(|a, b| a.current_position.cmp(&b.current_position)); + extensions.sort_by(|a, b| { + a.get_current_position() + .unwrap() + .cmp(&b.get_current_position().unwrap()) + }); for (i, v) in extensions.iter_mut().enumerate() { - if v.attached { - let id = v - .data - .kernel_info() - .expect("TcProgram is loaded kernel_info should be set") - .id; + if v.get_attached()? { + let id = v.data.get_id()?; + debug!("program {id} was already attached loading from pin"); let mut ext = Extension::from_pin(format!("{RTDIR_FS}/prog_{id}"))?; let target_fn = format!("prog{i}"); let new_link_id = ext .attach_to_program(dispatcher.fd().unwrap(), &target_fn) .unwrap(); let new_link: FdLink = ext.take_link(new_link_id)?.into(); - let base = match self.direction { + let base = match direction { Direction::Ingress => RTDIR_FS_TC_INGRESS, Direction::Egress => RTDIR_FS_TC_EGRESS, }; - let path = format!("{base}/dispatcher_{if_index}_{}/link_{id}", self.revision); + let path = format!("{base}/dispatcher_{if_index}_{}/link_{id}", revision); new_link.pin(path).map_err(BpfmanError::UnableToPinLink)?; } else { - let name = v.data.name(); - let global_data = v.data.global_data(); + let name = &v.data.get_name()?; + let global_data = &v.data.get_global_data()?; let mut bpf = BpfLoader::new(); @@ -289,13 +320,13 @@ impl TcDispatcher { // If map_pin_path is set already it means we need to use a pin // path which should already exist on the system. - if let Some(map_pin_path) = v.data.map_pin_path() { + if let Some(map_pin_path) = v.data.get_map_pin_path()? { debug!("tc program {name} is using maps from {:?}", map_pin_path); bpf.map_pin_path(map_pin_path); } let mut loader = bpf - .load(v.data.program_bytes()) + .load(&v.get_data().get_program_bytes()?) .map_err(BpfmanError::BpfLoadError)?; let ext: &mut Extension = loader @@ -306,35 +337,31 @@ impl TcDispatcher { let target_fn = format!("prog{i}"); ext.load(dispatcher.fd()?.try_clone()?, &target_fn)?; - v.data.set_kernel_info(Some(ext.info()?.try_into()?)); + v.data.set_kernel_info(&ext.info()?)?; - let id = v - .data - .kernel_info() - .expect("kernel info should be set after load") - .id; + let id = v.get_data().get_id()?; ext.pin(format!("{RTDIR_FS}/prog_{id}")) .map_err(BpfmanError::UnableToPinProgram)?; let new_link_id = ext.attach()?; let new_link = ext.take_link(new_link_id)?; let fd_link: FdLink = new_link.into(); - let base = match self.direction { + let base = match direction { Direction::Ingress => RTDIR_FS_TC_INGRESS, Direction::Egress => RTDIR_FS_TC_EGRESS, }; fd_link .pin(format!( "{base}/dispatcher_{if_index}_{}/link_{id}", - self.revision, + revision, )) .map_err(BpfmanError::UnableToPinLink)?; // If this program is the map(s) owner pin all maps (except for .rodata and .bss) by name. - if v.data.map_pin_path().is_none() { + if v.data.get_map_pin_path()?.is_none() { let map_pin_path = calc_map_pin_path(id); - v.data.set_map_pin_path(Some(map_pin_path.clone())); - create_map_pin_path(&map_pin_path).await?; + v.data.set_map_pin_path(&map_pin_path.clone())?; + create_map_pin_path(&map_pin_path)?; for (name, map) in loader.maps_mut() { if !should_map_be_pinned(name) { @@ -353,82 +380,56 @@ impl TcDispatcher { Ok(()) } - fn save(&self) -> Result<(), BpfmanError> { - debug!( - "TcDispatcher::save() for if_index {}, revision {}", - self.if_index, self.revision - ); - let base = match self.direction { - Direction::Ingress => RTDIR_TC_INGRESS_DISPATCHER, - Direction::Egress => RTDIR_TC_EGRESS_DISPATCHER, - }; - let path = format!("{base}/{}_{}", self.if_index, self.revision); - serde_json::to_writer(&fs::File::create(path).unwrap(), &self) - .map_err(|e| BpfmanError::Error(format!("can't save state: {e}")))?; - Ok(()) - } + pub(crate) fn delete(&mut self, root_db: &Db, full: bool) -> Result<(), BpfmanError> { + let if_index = self.get_ifindex()?; + let if_name = self.get_ifname()?; + let revision = self.get_revision()?; + let direction = self.get_direction()?; + let handle = self.get_handle()?; + let priority = self.get_priority()?; - pub(crate) fn load( - if_index: u32, - direction: Direction, - revision: u32, - ) -> Result { - debug!("TcDispatcher::load() for if_index {if_index}, revision {revision}"); - let dir = match direction { - Direction::Ingress => RTDIR_TC_INGRESS_DISPATCHER, - Direction::Egress => RTDIR_TC_EGRESS_DISPATCHER, - }; - let path = format!("{dir}/{if_index}_{revision}"); - let file = fs::File::open(path)?; - let reader = BufReader::new(file); - let prog = serde_json::from_reader(reader)?; - // TODO: We should check the bpffs paths here to for pinned links etc... - Ok(prog) - } - - pub(crate) fn delete(&mut self, full: bool) -> Result<(), BpfmanError> { debug!( "TcDispatcher::delete() for if_index {}, revision {}", - self.if_index, self.revision + if_index, revision ); - let base = match self.direction { - Direction::Ingress => RTDIR_TC_INGRESS_DISPATCHER, - Direction::Egress => RTDIR_TC_EGRESS_DISPATCHER, - }; - let path = format!("{base}/{}_{}", self.if_index, self.revision); - fs::remove_file(path) - .map_err(|e| BpfmanError::Error(format!("unable to cleanup state: {e}")))?; - let base = match self.direction { + root_db.drop_tree(self.db_tree.name()).map_err(|e| { + BpfmanError::DatabaseError( + format!( + "unable to drop tc dispatcher tree {:?}", + self.db_tree.name() + ), + e.to_string(), + ) + })?; + + let base = match direction { Direction::Ingress => RTDIR_FS_TC_INGRESS, Direction::Egress => RTDIR_FS_TC_EGRESS, }; - let path = format!("{base}/dispatcher_{}_{}", self.if_index, self.revision); + let path = format!("{base}/dispatcher_{}_{}", if_index, revision); fs::remove_dir_all(path) .map_err(|e| BpfmanError::Error(format!("unable to cleanup state: {e}")))?; if full { // Also detach the old dispatcher. - if let Some(old_handle) = self.handle { - let attach_type = match self.direction { + if let Some(old_handle) = handle { + let attach_type = match direction { Direction::Ingress => TcAttachType::Ingress, Direction::Egress => TcAttachType::Egress, }; - if let Ok(old_link) = SchedClassifierLink::attached( - &self.if_name, - attach_type, - self.priority, - old_handle, - ) { + if let Ok(old_link) = + SchedClassifierLink::attached(&if_name, attach_type, priority, old_handle) + { let detach_result = old_link.detach(); match detach_result { Ok(_) => debug!( - "TC dispatcher {}, {}, {}, {} sucessfully detached", - self.if_name, self.direction, self.priority, old_handle + "TC dispatcher {}, {}, {}, {} successfully detached", + if_name, direction, priority, old_handle ), Err(_) => debug!( "TC dispatcher {}, {}, {}, {} not attached when detach attempted", - self.if_name, self.direction, self.priority, old_handle + if_name, direction, priority, old_handle ), } } @@ -437,15 +438,69 @@ impl TcDispatcher { Ok(()) } - pub(crate) fn if_name(&self) -> String { - self.if_name.clone() + pub(crate) fn set_revision(&mut self, revision: u32) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, REVISION, &revision.to_ne_bytes()) + } + + pub(crate) fn get_revision(&self) -> Result { + sled_get(&self.db_tree, REVISION).map(bytes_to_u32) + } + + pub(crate) fn set_ifindex(&mut self, if_index: u32) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, IF_INDEX, &if_index.to_ne_bytes()) + } + + pub(crate) fn get_ifindex(&self) -> Result { + sled_get(&self.db_tree, IF_INDEX).map(bytes_to_u32) + } + + pub(crate) fn set_ifname(&mut self, if_name: &str) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, IF_NAME, if_name.as_bytes()) + } + + pub(crate) fn get_ifname(&self) -> Result { + sled_get(&self.db_tree, IF_NAME).map(|v| bytes_to_string(&v)) + } + + pub(crate) fn set_priority(&mut self, priority: u16) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, PRIORITY, &priority.to_ne_bytes()) + } + + pub(crate) fn get_priority(&self) -> Result { + sled_get(&self.db_tree, PRIORITY).map(bytes_to_u16) + } + + pub(crate) fn set_direction(&mut self, direction: Direction) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, DIRECTION, &(direction as u32).to_ne_bytes()) + } + + pub(crate) fn get_direction(&self) -> Result { + sled_get(&self.db_tree, DIRECTION).map(|v| { + Direction::try_from(bytes_to_u32(v)).map_err(|e| BpfmanError::Error(e.to_string())) + })? + } + + pub(crate) fn set_num_extensions(&mut self, num_extensions: usize) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, NUM_EXTENSIONS, &num_extensions.to_ne_bytes()) + } + + pub(crate) fn get_num_extensions(&self) -> Result { + sled_get(&self.db_tree, NUM_EXTENSIONS).map(bytes_to_usize) + } + + pub(crate) fn set_program_name(&mut self, program_name: &str) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, PROGRAM_NAME, program_name.as_bytes()) + } + + pub(crate) fn get_program_name(&self) -> Result { + sled_get(&self.db_tree, PROGRAM_NAME).map(|v| bytes_to_string(&v)) } - pub(crate) fn revision(&self) -> u32 { - self.revision + pub(crate) fn set_handle(&mut self, handle: u32) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, HANDLE, &handle.to_ne_bytes()) } - pub(crate) fn num_extensions(&self) -> usize { - self.num_extensions + pub(crate) fn get_handle(&self) -> Result, BpfmanError> { + sled_get_option(&self.db_tree, HANDLE).map(|v| v.map(bytes_to_u32)) } } diff --git a/bpfman/src/multiprog/xdp.rs b/bpfman/src/multiprog/xdp.rs index ff83e8dd4..dddcf654b 100644 --- a/bpfman/src/multiprog/xdp.rs +++ b/bpfman/src/multiprog/xdp.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of bpfman -use std::{fs, io::BufReader, path::PathBuf}; +use std::{fs, path::PathBuf}; use aya::{ programs::{ @@ -10,45 +10,84 @@ use aya::{ }, Bpf, BpfLoader, }; -use bpfman_api::{config::XdpMode, util::directories::*, ImagePullPolicy}; use log::debug; -use serde::{Deserialize, Serialize}; -use tokio::sync::{mpsc::Sender, oneshot}; +use sled::Db; use crate::{ - bpf::{calc_map_pin_path, create_map_pin_path}, - command::{Program, XdpProgram}, + calc_map_pin_path, + config::XdpMode, + create_map_pin_path, + directories::*, dispatcher_config::XdpDispatcherConfig, errors::BpfmanError, - multiprog::Dispatcher, - oci_utils::image_manager::{BytecodeImage, Command as ImageManagerCommand}, - utils::should_map_be_pinned, + multiprog::{Dispatcher, XDP_DISPATCHER_PREFIX}, + oci_utils::image_manager::ImageManager, + types::{BytecodeImage, ImagePullPolicy, Program, XdpProgram}, + utils::{ + bytes_to_string, bytes_to_u32, bytes_to_usize, should_map_be_pinned, sled_get, sled_insert, + }, }; pub(crate) const DEFAULT_PRIORITY: u32 = 50; -#[derive(Debug, Serialize, Deserialize)] +/// These constants define the key of SLED DB +const REVISION: &str = "revision"; +const IF_INDEX: &str = "if_index"; +const IF_NAME: &str = "if_name"; +const MODE: &str = "mode"; +const NUM_EXTENSIONS: &str = "num_extension"; +const PROGRAM_NAME: &str = "program_name"; + +#[derive(Debug)] pub struct XdpDispatcher { - revision: u32, - if_index: u32, - if_name: String, - mode: XdpMode, - num_extensions: usize, - #[serde(skip)] + db_tree: sled::Tree, loader: Option, - program_name: Option, } impl XdpDispatcher { - pub(crate) async fn new( - mode: XdpMode, - if_index: &u32, + pub(crate) fn new( + root_db: &Db, + mode: &XdpMode, + if_index: u32, if_name: String, - programs: &mut [&mut Program], revision: u32, + ) -> Result { + let db_tree = root_db + .open_tree(format!( + "{}_{}_{}", + XDP_DISPATCHER_PREFIX, if_index, revision + )) + .expect("Unable to open xdp dispatcher database tree"); + + let mut dp = Self { + db_tree, + loader: None, + }; + + dp.set_ifindex(if_index)?; + dp.set_ifname(&if_name)?; + dp.set_mode(mode)?; + dp.set_revision(revision)?; + Ok(dp) + } + + // TODO(astoycos) check to ensure the expected fs pins are there. + pub(crate) fn new_from_db(db_tree: sled::Tree) -> Self { + Self { + db_tree, + loader: None, + } + } + + pub(crate) async fn load( + &mut self, + root_db: &Db, + programs: &mut [Program], old_dispatcher: Option, - image_manager: Sender, - ) -> Result { + image_manager: &mut ImageManager, + ) -> Result<(), BpfmanError> { + let if_index = self.get_ifindex()?; + let revision = self.get_revision()?; debug!("XdpDispatcher::new() for if_index {if_index}, revision {revision}"); let mut extensions: Vec<&mut XdpProgram> = programs .iter_mut() @@ -59,9 +98,13 @@ impl XdpDispatcher { .collect(); let mut chain_call_actions = [0; 10]; - extensions.sort_by(|a, b| a.current_position.cmp(&b.current_position)); + extensions.sort_by(|a, b| { + a.get_current_position() + .unwrap() + .cmp(&b.get_current_position().unwrap()) + }); for p in extensions.iter() { - chain_call_actions[p.current_position.unwrap()] = p.proceed_on.mask(); + chain_call_actions[p.get_current_position()?.unwrap()] = p.get_proceed_on()?.mask(); } let config = XdpDispatcherConfig::new( @@ -79,32 +122,19 @@ impl XdpDispatcher { None, None, ); - let (tx, rx) = oneshot::channel(); - image_manager - .send(ImageManagerCommand::Pull { - image: image.image_url.clone(), - pull_policy: image.image_pull_policy.clone(), - username: image.username.clone(), - password: image.password.clone(), - resp: tx, - }) - .await - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))?; - - let (path, bpf_function_name) = rx - .await - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))? - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))?; - - let (tx, rx) = oneshot::channel(); - image_manager - .send(ImageManagerCommand::GetBytecode { path, resp: tx }) - .await - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))?; - let program_bytes = rx - .await - .map_err(|e| BpfmanError::BpfBytecodeError(e.into()))? - .map_err(BpfmanError::BpfBytecodeError)?; + + let (path, bpf_function_name) = image_manager + .get_image( + root_db, + &image.image_url.clone(), + image.image_pull_policy.clone(), + image.username.clone(), + image.password.clone(), + ) + .await?; + + let program_bytes = image_manager.get_bytecode_from_image_store(root_db, path)?; + let mut loader = BpfLoader::new() .set_global("conf", &config, true) .load(&program_bytes)?; @@ -116,36 +146,34 @@ impl XdpDispatcher { let path = format!("{RTDIR_FS_XDP}/dispatcher_{if_index}_{revision}"); fs::create_dir_all(path).unwrap(); - let mut dispatcher = XdpDispatcher { - if_index: *if_index, - if_name, - revision, - mode, - num_extensions: extensions.len(), - loader: Some(loader), - program_name: Some(bpf_function_name), - }; - dispatcher.attach_extensions(&mut extensions).await?; - dispatcher.attach()?; - dispatcher.save()?; + self.loader = Some(loader); + self.set_num_extensions(extensions.len())?; + self.set_program_name(&bpf_function_name)?; + + self.attach_extensions(&mut extensions)?; + self.attach()?; if let Some(mut old) = old_dispatcher { - old.delete(false)?; + old.delete(root_db, false)?; } - Ok(dispatcher) + Ok(()) } pub(crate) fn attach(&mut self) -> Result<(), BpfmanError> { + let if_index = self.get_ifindex()?; + let revision = self.get_revision()?; + let mode = self.get_mode()?; + let program_name = self.get_program_name()?; + debug!( "XdpDispatcher::attach() for if_index {}, revision {}", - self.if_index, self.revision + if_index, revision ); - let if_index = self.if_index; - let iface = self.if_name.clone(); + let iface = self.get_ifname()?; let dispatcher: &mut Xdp = self .loader .as_mut() .ok_or(BpfmanError::NotLoaded)? - .program_mut(self.program_name.clone().unwrap().as_str()) + .program_mut(program_name.as_str()) .unwrap() .try_into()?; @@ -156,7 +184,7 @@ impl XdpDispatcher { .attach_to_link(pinned_link.try_into().unwrap()) .unwrap(); } else { - let flags = self.mode.as_flags(); + let flags = mode.as_flags(); let link = dispatcher.attach(&iface, flags).map_err(|e| { BpfmanError::Error(format!( "dispatcher attach failed on interface {iface}: {e}" @@ -176,30 +204,29 @@ impl XdpDispatcher { Ok(()) } - async fn attach_extensions( - &mut self, - extensions: &mut [&mut XdpProgram], - ) -> Result<(), BpfmanError> { + fn attach_extensions(&mut self, extensions: &mut [&mut XdpProgram]) -> Result<(), BpfmanError> { + let if_index = self.get_ifindex()?; + let revision = self.get_revision()?; + let program_name = self.get_program_name()?; debug!( "XdpDispatcher::attach_extensions() for if_index {}, revision {}", - self.if_index, self.revision + if_index, revision ); - let if_index = self.if_index; let dispatcher: &mut Xdp = self .loader .as_mut() .ok_or(BpfmanError::NotLoaded)? - .program_mut(self.program_name.clone().unwrap().as_str()) + .program_mut(program_name.as_str()) .unwrap() .try_into()?; - extensions.sort_by(|a, b| a.current_position.cmp(&b.current_position)); + extensions.sort_by(|a, b| { + a.get_current_position() + .unwrap() + .cmp(&b.get_current_position().unwrap()) + }); for (i, v) in extensions.iter_mut().enumerate() { - if v.attached { - let id = v - .data - .kernel_info() - .expect("XdpProgram is loaded kernel_info should be set") - .id; + if v.get_attached()? { + let id = v.get_data().get_id()?; let mut ext = Extension::from_pin(format!("{RTDIR_FS}/prog_{id}"))?; let target_fn = format!("prog{i}"); let new_link_id = ext @@ -208,12 +235,12 @@ impl XdpDispatcher { let new_link: FdLink = ext.take_link(new_link_id)?.into(); let path = format!( "{RTDIR_FS_XDP}/dispatcher_{if_index}_{}/link_{id}", - self.revision + revision ); new_link.pin(path).map_err(BpfmanError::UnableToPinLink)?; } else { - let name = v.data.name(); - let global_data = v.data.global_data(); + let name = &v.get_data().get_name()?; + let global_data = &v.get_data().get_global_data()?; let mut bpf = BpfLoader::new(); @@ -225,13 +252,13 @@ impl XdpDispatcher { // If map_pin_path is set already it means we need to use a pin // path which should already exist on the system. - if let Some(map_pin_path) = v.data.map_pin_path() { + if let Some(map_pin_path) = v.get_data().get_map_pin_path()? { debug!("xdp program {name} is using maps from {:?}", map_pin_path); bpf.map_pin_path(map_pin_path); } let mut loader = bpf - .load(v.data.program_bytes()) + .load(&v.get_data().get_program_bytes()?) .map_err(BpfmanError::BpfLoadError)?; let ext: &mut Extension = loader @@ -242,13 +269,9 @@ impl XdpDispatcher { let target_fn = format!("prog{i}"); ext.load(dispatcher.fd()?.try_clone()?, &target_fn)?; - v.data.set_kernel_info(Some(ext.info()?.try_into()?)); + v.get_data_mut().set_kernel_info(&ext.info()?)?; - let id = v - .data - .kernel_info() - .expect("kernel info should be set after load") - .id; + let id = v.get_data().get_id()?; ext.pin(format!("{RTDIR_FS}/prog_{id}")) .map_err(BpfmanError::UnableToPinProgram)?; @@ -258,15 +281,15 @@ impl XdpDispatcher { fd_link .pin(format!( "{RTDIR_FS_XDP}/dispatcher_{if_index}_{}/link_{id}", - self.revision, + revision, )) .map_err(BpfmanError::UnableToPinLink)?; // If this program is the map(s) owner pin all maps (except for .rodata and .bss) by name. - if v.data.map_pin_path().is_none() { + if v.get_data().get_map_pin_path()?.is_none() { let map_pin_path = calc_map_pin_path(id); - v.data.set_map_pin_path(Some(map_pin_path.clone())); - create_map_pin_path(&map_pin_path).await?; + v.get_data_mut().set_map_pin_path(&map_pin_path)?; + create_map_pin_path(&map_pin_path)?; for (name, map) in loader.maps_mut() { if !should_map_be_pinned(name) { @@ -285,59 +308,81 @@ impl XdpDispatcher { Ok(()) } - fn save(&self) -> Result<(), BpfmanError> { + pub(crate) fn delete(&self, root_db: &Db, full: bool) -> Result<(), BpfmanError> { + let if_index = self.get_ifindex()?; + let revision = self.get_revision()?; debug!( - "XdpDispatcher::save() for if_index {}, revision {}", - self.if_index, self.revision - ); - let path = format!("{RTDIR_XDP_DISPATCHER}/{}_{}", self.if_index, self.revision); - serde_json::to_writer(&fs::File::create(path).unwrap(), &self) - .map_err(|e| BpfmanError::Error(format!("can't save state: {e}")))?; - Ok(()) - } - - pub fn load(if_index: u32, revision: u32) -> Result { - debug!("XdpDispatcher::load() for if_index {if_index}, revision {revision}"); - let path = format!("{RTDIR_XDP_DISPATCHER}/{if_index}_{revision}"); - let file = fs::File::open(path)?; - let reader = BufReader::new(file); - let prog = serde_json::from_reader(reader)?; - // TODO: We should check the bpffs paths here to for pinned links etc... - Ok(prog) - } - - pub(crate) fn delete(&self, full: bool) -> Result<(), BpfmanError> { - debug!( - "XdpDispatcher::delete() for if_index {}, revision {}", - self.if_index, self.revision - ); - let path = format!("{RTDIR_XDP_DISPATCHER}/{}_{}", self.if_index, self.revision); - fs::remove_file(path) - .map_err(|e| BpfmanError::Error(format!("unable to cleanup state: {e}")))?; - - let path = format!( - "{RTDIR_FS_XDP}/dispatcher_{}_{}", - self.if_index, self.revision + "XdpDispatcher::delete() for if_index {}, revision {}, full {}", + if_index, revision, full ); + root_db.drop_tree(self.db_tree.name()).map_err(|e| { + BpfmanError::DatabaseError( + format!( + "unable to drop xdp dispatcher tree {:?}", + self.db_tree.name() + ), + e.to_string(), + ) + })?; + + let path = format!("{RTDIR_FS_XDP}/dispatcher_{}_{}", if_index, revision); fs::remove_dir_all(path) .map_err(|e| BpfmanError::Error(format!("unable to cleanup state: {e}")))?; if full { - let path_link = format!("{RTDIR_FS_XDP}/dispatcher_{}_link", self.if_index); + let path_link = format!("{RTDIR_FS_XDP}/dispatcher_{}_link", if_index); fs::remove_file(path_link) .map_err(|e| BpfmanError::Error(format!("unable to cleanup state: {e}")))?; } Ok(()) } - pub(crate) fn if_name(&self) -> String { - self.if_name.clone() + pub(crate) fn set_revision(&mut self, revision: u32) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, REVISION, &revision.to_ne_bytes()) + } + + pub(crate) fn get_revision(&self) -> Result { + sled_get(&self.db_tree, REVISION).map(bytes_to_u32) + } + + pub(crate) fn set_ifindex(&mut self, if_index: u32) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, IF_INDEX, &if_index.to_ne_bytes()) + } + + pub(crate) fn get_ifindex(&self) -> Result { + sled_get(&self.db_tree, IF_INDEX).map(bytes_to_u32) + } + + pub(crate) fn set_ifname(&mut self, if_name: &str) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, IF_NAME, if_name.as_bytes()) + } + + pub(crate) fn get_ifname(&self) -> Result { + sled_get(&self.db_tree, IF_NAME).map(|v| bytes_to_string(&v)) + } + + pub(crate) fn set_mode(&mut self, mode: &XdpMode) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, MODE, &(*mode as u32).to_ne_bytes()) + } + + pub(crate) fn get_mode(&self) -> Result { + sled_get(&self.db_tree, MODE).map(|v| { + XdpMode::try_from(bytes_to_u32(v)).map_err(|e| BpfmanError::Error(e.to_string())) + })? + } + + pub(crate) fn set_num_extensions(&mut self, num_extensions: usize) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, NUM_EXTENSIONS, &num_extensions.to_ne_bytes()) + } + + pub(crate) fn get_num_extensions(&self) -> Result { + sled_get(&self.db_tree, NUM_EXTENSIONS).map(bytes_to_usize) } - pub(crate) fn revision(&self) -> u32 { - self.revision + pub(crate) fn set_program_name(&mut self, program_name: &str) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, PROGRAM_NAME, program_name.as_bytes()) } - pub(crate) fn num_extensions(&self) -> usize { - self.num_extensions + pub(crate) fn get_program_name(&self) -> Result { + sled_get(&self.db_tree, PROGRAM_NAME).map(|v| bytes_to_string(&v)) } } diff --git a/bpfman/src/oci_utils/cosign.rs b/bpfman/src/oci_utils/cosign.rs index 2ec75df8d..b90233970 100644 --- a/bpfman/src/oci_utils/cosign.rs +++ b/bpfman/src/oci_utils/cosign.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of bpfman -use std::str::FromStr; +use std::{path::PathBuf, str::FromStr}; use anyhow::{anyhow, bail}; use log::{debug, info, warn}; @@ -12,37 +12,41 @@ use sigstore::{ }, errors::SigstoreError::RegistryPullManifestError, registry::{Auth, ClientConfig, ClientProtocol, OciReference}, - tuf::SigstoreRepository, }; -use tokio::task::spawn_blocking; pub struct CosignVerifier { - pub client: sigstore::cosign::Client, + pub client: sigstore::cosign::Client<'static>, pub allow_unsigned: bool, } +#[cfg(test)] +fn get_tuf_path() -> Option { + None +} + +#[cfg(not(test))] +fn get_tuf_path() -> Option { + Some(PathBuf::from(crate::directories::RTDIR_TUF)) +} + impl CosignVerifier { pub(crate) async fn new(allow_unsigned: bool) -> Result { - // We must use spawn_blocking here. - // See: https://docs.rs/sigstore/0.7.2/sigstore/oauth/openidflow/index.html - let repo: sigstore::errors::Result = spawn_blocking(|| { - info!("Starting Cosign Verifier, downloading data from Sigstore TUF repository"); - sigstore::tuf::SigstoreRepository::fetch(None) - }) - .await - .map_err(|e| anyhow!("Error spawning blocking task inside of tokio: {}", e))?; - - let repo: SigstoreRepository = repo?; + info!("Starting Cosign Verifier, downloading data from Sigstore TUF repository"); let oci_config = ClientConfig { protocol: ClientProtocol::Https, ..Default::default() }; + // The cosign is a static ref which needs to live for the rest of the program's + // lifecycle so therefore the repo ALSO needs to be static, requiring us + // to leak it here. + let repo: &dyn sigstore::trust::TrustRoot = Box::leak(fetch_sigstore_tuf_data().await?); + let cosign_client = ClientBuilder::default() .with_oci_client_config(oci_config) - .with_rekor_pub_key(repo.rekor_pub_key()) - .with_fulcio_certs(repo.fulcio_certs()) + .with_trust_repository(repo) + .await? .enable_registry_caching() .build()?; @@ -102,3 +106,24 @@ impl CosignVerifier { } } } + +async fn fetch_sigstore_tuf_data() -> anyhow::Result> { + let tuf = sigstore::trust::sigstore::SigstoreTrustRoot::new(get_tuf_path().as_deref()) + .await + .map_err(|e| { + anyhow!( + "Error spawning blocking task to build sigstore repo inside of tokio: {}", + e + ) + })? + .prefetch() + .await + .map_err(|e| { + anyhow!( + "Error spawning blocking task to prefetch tuf data inside of tokio: {}", + e + ) + })?; + + Ok(Box::new(tuf)) +} diff --git a/bpfman/src/oci_utils/image_manager.rs b/bpfman/src/oci_utils/image_manager.rs index 5eb12871c..d1498fa3b 100644 --- a/bpfman/src/oci_utils/image_manager.rs +++ b/bpfman/src/oci_utils/image_manager.rs @@ -1,17 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of bpfman -use std::{ - fs, - fs::{create_dir_all, read_to_string, File, OpenOptions}, - io::{copy, Read, Write}, - path::{Path, PathBuf}, -}; +use std::io::{copy, Read}; -use anyhow::Context; -use bpfman_api::ImagePullPolicy; use flate2::read::GzDecoder; -use log::{debug, info, trace}; +use log::{debug, trace}; use oci_distribution::{ client::{ClientConfig, ClientProtocol}, manifest, @@ -19,25 +12,21 @@ use oci_distribution::{ secrets::RegistryAuth, Client, Reference, }; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use serde_json::Value; use sha2::{Digest, Sha256}; +use sled::Db; use tar::Archive; -use tokio::{ - select, - sync::{ - mpsc::{self, Receiver}, - oneshot, - }, -}; use crate::{ oci_utils::{cosign::CosignVerifier, ImageError}, - serve::shutdown_handler, - utils::read, + types::ImagePullPolicy, }; -#[derive(Debug, Deserialize, Default)] +#[derive(Deserialize)] +#[allow(dead_code)] +// TODO(astoycos) upgrade the oci image spec based on +// https://opencontainers.org/posts/blog/2023-07-07-summary-of-upcoming-changes-in-oci-image-and-distribution-specs-v-1-1/#1-official-guidance-on-how-to-create-and-store-alternative-even-non-container-artifacts pub struct ContainerImageMetadata { #[serde(rename(deserialize = "io.ebpf.program_name"))] pub name: String, @@ -49,96 +38,13 @@ pub struct ContainerImageMetadata { pub filename: String, } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub(crate) struct BytecodeImage { - pub(crate) image_url: String, - pub(crate) image_pull_policy: ImagePullPolicy, - pub(crate) username: Option, - pub(crate) password: Option, -} - -impl BytecodeImage { - pub(crate) fn new( - image_url: String, - image_pull_policy: i32, - username: Option, - password: Option, - ) -> Self { - Self { - image_url, - image_pull_policy: image_pull_policy - .try_into() - .expect("Unable to parse ImagePullPolicy"), - username, - password, - } - } - - pub(crate) fn get_url(&self) -> &str { - &self.image_url - } - - pub(crate) fn get_pull_policy(&self) -> &ImagePullPolicy { - &self.image_pull_policy - } -} - -impl From for BytecodeImage { - fn from(value: bpfman_api::v1::BytecodeImage) -> Self { - // This function is mapping an empty string to None for - // username and password. - let username = if value.username.is_some() { - match value.username.unwrap().as_ref() { - "" => None, - u => Some(u.to_string()), - } - } else { - None - }; - let password = if value.password.is_some() { - match value.password.unwrap().as_ref() { - "" => None, - u => Some(u.to_string()), - } - } else { - None - }; - BytecodeImage::new(value.url, value.image_pull_policy, username, password) - } -} - -pub(crate) struct ImageManager { - base_dir: PathBuf, +pub struct ImageManager { client: Client, cosign_verifier: CosignVerifier, - rx: Receiver, -} - -/// Provided by the requester and used by the manager task to send -/// the command response back to the requester. -type Responder = oneshot::Sender; - -#[derive(Debug)] -pub(crate) enum Command { - Pull { - image: String, - pull_policy: ImagePullPolicy, - username: Option, - password: Option, - resp: Responder>, - }, - GetBytecode { - path: String, - resp: Responder, anyhow::Error>>, - }, } impl ImageManager { - pub(crate) async fn new>( - base_dir: P, - allow_unsigned: bool, - rx: mpsc::Receiver, - ) -> Result { + pub async fn new(allow_unsigned: bool) -> Result { let cosign_verifier = CosignVerifier::new(allow_unsigned).await?; let config = ClientConfig { protocol: ClientProtocol::Https, @@ -146,41 +52,14 @@ impl ImageManager { }; let client = Client::new(config); Ok(Self { - base_dir: base_dir.as_ref().to_path_buf(), cosign_verifier, client, - rx, }) } - pub(crate) async fn run(&mut self) { - loop { - // Start receiving messages - select! { - biased; - _ = shutdown_handler() => { - info!("image_manager: Signal received to stop command processing"); - break; - } - Some(cmd) = self.rx.recv() => { - match cmd { - Command::Pull { image, pull_policy, username, password, resp } => { - let result = self.get_image(&image, pull_policy, username, password).await; - let _ = resp.send(result); - }, - Command::GetBytecode { path, resp } => { - let result = self.get_bytecode_from_image_store(path).await; - let _ = resp.send(result); - } - } - } - } - } - info!("image_manager: Stopped processing commands"); - } - pub(crate) async fn get_image( &mut self, + root_db: &Db, image_url: &str, pull_policy: ImagePullPolicy, username: Option, @@ -195,37 +74,37 @@ impl ImageManager { .verify(image_url, username.as_deref(), password.as_deref()) .await?; - let image_content_path = self.get_image_content_dir(&image); + let image_content_key = get_image_content_key(&image); - // Make sure the actual image manifest exists so that we are sure the content is there - let exists: bool = image_content_path.join("manifest.json").exists(); + let exists: bool = root_db + .contains_key(image_content_key.to_string() + "manifest.json") + .map_err(|e| { + ImageError::DatabaseError("failed to read db".to_string(), e.to_string()) + })?; let image_meta = match pull_policy { ImagePullPolicy::Always => { - self.pull_image(image, image_content_path.clone(), username, password) + self.pull_image(root_db, image, &image_content_key, username, password) .await? } ImagePullPolicy::IfNotPresent => { if exists { - load_image_meta(image_content_path.clone())? + self.load_image_meta(root_db, &image_content_key)? } else { - self.pull_image(image, image_content_path.clone(), username, password) + self.pull_image(root_db, image, &image_content_key, username, password) .await? } } ImagePullPolicy::Never => { if exists { - load_image_meta(image_content_path.clone())? + self.load_image_meta(root_db, &image_content_key)? } else { Err(ImageError::ByteCodeImageNotfound(image.to_string()))? } } }; - Ok(( - image_content_path.into_os_string().into_string().unwrap(), - image_meta.bpf_function_name, - )) + Ok((image_content_key.to_string(), image_meta.bpf_function_name)) } fn get_auth_for_registry( @@ -242,8 +121,9 @@ impl ImageManager { pub async fn pull_image( &mut self, + root_db: &Db, image: Reference, - content_dir: PathBuf, + base_key: &str, username: Option, password: Option, ) -> Result { @@ -254,9 +134,6 @@ impl ImageManager { image.tag().unwrap_or("latest") ); - // prep on disk storage for image - let content_dir = prepare_storage_for_image(content_dir)?; - let auth = self.get_auth_for_registry(image.registry(), username, password); let (image_manifest, _, config_contents) = self @@ -267,22 +144,20 @@ impl ImageManager { trace!("Raw container image manifest {}", image_manifest); - let image_manifest_path = Path::new(&content_dir).join("manifest.json"); + let image_manifest_key = base_key.to_string() + "manifest.json"; - let image_manifest_file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(image_manifest_path.clone()) + let image_manifest_json = serde_json::to_string(&image_manifest) .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))?; - serde_json::to_writer_pretty( - image_manifest_file - .try_clone() - .expect("failed to clone image_manifest_file"), - &image_manifest.clone(), - ) - .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))?; + // inset and flush to disk to avoid races across threads on write. + root_db + .insert(image_manifest_key, image_manifest_json.as_str()) + .map_err(|e| { + ImageError::DatabaseError("failed to write to db".to_string(), e.to_string()) + })?; + root_db.flush().map_err(|e| { + ImageError::DatabaseError("failed to flush db".to_string(), e.to_string()) + })?; let config_sha = &image_manifest .config @@ -290,27 +165,14 @@ impl ImageManager { .split(':') .collect::>()[1]; - let image_config_path = Path::new(&content_dir).join(config_sha); - - let image_config_file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(image_config_path.clone()) - .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))?; + let image_config_path = base_key.to_string() + config_sha; let bytecode_sha = image_manifest.layers[0] .digest .split(':') .collect::>()[1]; - let bytecode_path = Path::new(&content_dir).join(bytecode_sha); - let mut image_bytecode_file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(bytecode_path.clone()) - .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))?; + let bytecode_path = base_key.to_string() + bytecode_sha; let image_config: Value = serde_json::from_str(&config_contents) .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))?; @@ -321,13 +183,14 @@ impl ImageManager { serde_json::from_str(&image_config["config"]["Labels"].to_string()) .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))?; - serde_json::to_writer_pretty( - image_config_file - .try_clone() - .expect("failed to clone image_config_file"), - &image_config, - ) - .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))?; + root_db + .insert(image_config_path, config_contents.as_str()) + .map_err(|e| { + ImageError::DatabaseError("failed to write to db".to_string(), e.to_string()) + })?; + root_db.flush().map_err(|e| { + ImageError::DatabaseError("failed to flush db".to_string(), e.to_string()) + })?; let image_content = self .client @@ -347,60 +210,58 @@ impl ImageManager { .map(|layer| layer.data) .ok_or(ImageError::BytecodeImageExtractFailure)?; - image_bytecode_file - .write_all(image_content.as_slice()) - .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))?; - - // once all file writing is complete set all files to r/o - let mut image_manifest_perms = image_manifest_file - .metadata() - .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))? - .permissions(); - - image_manifest_perms.set_readonly(true); - fs::set_permissions(image_manifest_path, image_manifest_perms) - .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))?; - - let mut image_config_perms = image_config_file - .metadata() - .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))? - .permissions(); - - image_config_perms.set_readonly(true); - fs::set_permissions(image_config_path, image_config_perms) - .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))?; - - let mut bytecode_perms = image_bytecode_file - .metadata() - .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))? - .permissions(); - - bytecode_perms.set_readonly(true); - fs::set_permissions(bytecode_path.clone(), bytecode_perms) - .map_err(|e| ImageError::ByteCodeImageProcessFailure(e.into()))?; + root_db.insert(bytecode_path, image_content).map_err(|e| { + ImageError::DatabaseError("failed to write to db".to_string(), e.to_string()) + })?; + root_db.flush().map_err(|e| { + ImageError::DatabaseError("failed to flush db".to_string(), e.to_string()) + })?; Ok(image_labels) } - pub(crate) async fn get_bytecode_from_image_store( + pub(crate) fn get_bytecode_from_image_store( &self, - content_dir: String, - ) -> Result, anyhow::Error> { - debug!("bytecode is stored as tar+gzip file at {}", content_dir); - let image_dir = Path::new(&content_dir); - // Get image manifest from disk - let manifest = load_image_manifest(image_dir.to_path_buf())?; + root_db: &Db, + base_key: String, + ) -> Result, ImageError> { + let manifest = serde_json::from_str::( + std::str::from_utf8( + &root_db + .get(base_key.clone() + "manifest.json") + .map_err(|e| { + ImageError::DatabaseError("failed to read db".to_string(), e.to_string()) + })? + .expect("Image manifest is empty"), + ) + .unwrap(), + ) + .map_err(|e| { + ImageError::DatabaseError( + "failed to parse image manifest from db".to_string(), + e.to_string(), + ) + })?; let bytecode_sha = &manifest.layers[0].digest; - let bytecode_path = - image_dir.join(bytecode_sha.clone().split(':').collect::>()[1]); + let bytecode_key = base_key + bytecode_sha.clone().split(':').collect::>()[1]; - let mut f = - File::open(bytecode_path.clone()).context("failed to open compressed bytecode")?; + debug!( + "bytecode is stored as tar+gzip file at key {}", + bytecode_key + ); + + let f = root_db + .get(bytecode_key.clone()) + .map_err(|e| ImageError::DatabaseError("failed to read db".to_string(), e.to_string()))? + .ok_or(ImageError::DatabaseError( + "key does not exist in db".to_string(), + String::new(), + ))?; let mut hasher = Sha256::new(); - copy(&mut f, &mut hasher)?; + copy(&mut f.as_ref(), &mut hasher).expect("cannot copy bytecode to hasher"); let hash = hasher.finalize(); let expected_sha = "sha256:".to_owned() + &base16ct::lower::encode_string(&hash); @@ -415,8 +276,7 @@ impl ImageManager { // The data is of OCI media type "application/vnd.oci.image.layer.v1.tar+gzip" or // "application/vnd.docker.image.rootfs.diff.tar.gzip" // decode and unpack to access bytecode - let buf = read(bytecode_path).await?; - let unzipped_tarball = GzDecoder::new(buf.as_slice()); + let unzipped_tarball = GzDecoder::new(f.as_ref()); return Ok(Archive::new(unzipped_tarball) .entries() @@ -435,79 +295,70 @@ impl ImageManager { .to_owned()); } - fn get_image_content_dir(&self, image: &Reference) -> PathBuf { - // Try to get the tag, if it doesn't exist, get the digest - // if neither exist, return "latest" as the tag - let tag = match image.tag() { - Some(t) => t, - _ => match image.digest() { - Some(d) => d, - _ => "latest", - }, - }; + fn load_image_meta( + &self, + root_db: &Db, + image_content_key: &str, + ) -> Result { + let manifest = serde_json::from_str::( + std::str::from_utf8( + &root_db + .get(image_content_key.to_string() + "manifest.json") + .map_err(|e| { + ImageError::DatabaseError("failed to read db".to_string(), e.to_string()) + })? + .expect("Image manifest is empty"), + ) + .unwrap(), + ) + .map_err(|e| { + ImageError::DatabaseError( + "failed to parse db entry to image manifest".to_string(), + e.to_string(), + ) + })?; - Path::new(&format!( - "{}/{}/{}/{}", - self.base_dir.display(), - image.registry(), - image.repository(), - tag - )) - .to_owned() - } -} + let config_sha = &manifest.config.digest.split(':').collect::>()[1]; -fn prepare_storage_for_image(image_dir: PathBuf) -> Result { - debug!( - "Creating oci image content store at: {}", - image_dir.display() - ); - create_dir_all(image_dir.clone()) - .context(format!( - "unable to create repo directory for image URL: {}", - image_dir.display() - )) - .map_err(ImageError::ByteCodeImageProcessFailure)?; - - Ok(image_dir) -} + let image_config_key = image_content_key.to_string() + config_sha; -fn load_image_manifest(image_dir: PathBuf) -> Result { - let manifest_path = image_dir.join("manifest.json"); + let db_content = &root_db + .get(image_config_key) + .map_err(|e| ImageError::DatabaseError("failed to read db".to_string(), e.to_string()))? + .expect("Image manifest is empty"); + + let file_content = std::str::from_utf8(db_content)?; + + let image_config: Value = + serde_json::from_str(file_content).expect("cannot parse image config from database"); + debug!( + "Raw container image config {}", + &image_config["config"]["Labels"].to_string() + ); - // Get image manifest from disk - let file_content = read_to_string(manifest_path).context(format!( - "failed to read image manifest file {}", - image_dir.display() - ))?; - Ok(serde_json::from_str::(&file_content)?) + Ok(serde_json::from_str::( + &image_config["config"]["Labels"].to_string(), + )?) + } } -fn load_image_meta(image_content_path: PathBuf) -> Result { - // Get image config from disk - let image_manifest = load_image_manifest(image_content_path.clone())?; - - let config_sha = &image_manifest - .config - .digest - .split(':') - .collect::>()[1]; - - let image_config_path = image_content_path.join(config_sha); - let file_content = - read_to_string(image_config_path).context("failed to read image config file")?; - - // This should panic since it means that the on disk storage format has changed during runtime. - let image_config: Value = - serde_json::from_str(&file_content).expect("cannot parse image config from disk"); - debug!( - "Raw container image config {}", - &image_config["config"]["Labels"].to_string() - ); - - Ok(serde_json::from_str::( - &image_config["config"]["Labels"].to_string(), - )?) +fn get_image_content_key(image: &Reference) -> String { + // Try to get the tag, if it doesn't exist, get the digest + // if neither exist, return "latest" as the tag + let tag = match image.tag() { + Some(t) => t, + _ => match image.digest() { + Some(d) => d, + _ => "latest", + }, + }; + + format!( + "{}_{}_{}", + image.registry(), + image.repository().replace('/', "_"), + tag + ) } #[cfg(test)] @@ -515,17 +366,17 @@ mod tests { use assert_matches::assert_matches; use super::*; + use crate::{get_db_config, init_database}; #[tokio::test] async fn image_pull_and_bytecode_verify() { - let tmpdir = tempfile::tempdir().unwrap(); - std::env::set_current_dir(&tmpdir).unwrap(); - - let (_tx, rx) = mpsc::channel(32); - let mut mgr = ImageManager::new(tmpdir, true, rx).await.unwrap(); - - let (path, _) = mgr + let root_db = init_database(get_db_config()) + .await + .expect("Unable to open root database for unit test"); + let mut mgr = ImageManager::new(true).await.unwrap(); + let (image_content_key, _) = mgr .get_image( + &root_db, "quay.io/bpfman-bytecode/xdp_pass:latest", ImagePullPolicy::Always, None, @@ -534,26 +385,46 @@ mod tests { .await .expect("failed to pull bytecode"); - assert!(Path::new(&path).exists()); + // Assert that an manifest, config and bytecode key were formed for image. + assert!(root_db.scan_prefix(image_content_key.clone()).count() == 3); let program_bytes = mgr - .get_bytecode_from_image_store(path) - .await + .get_bytecode_from_image_store(&root_db, image_content_key) .expect("failed to get bytecode from image store"); assert!(!program_bytes.is_empty()) } + #[tokio::test] + async fn image_pull_policy_never_failure() { + let mut mgr = ImageManager::new(true).await.unwrap(); + let root_db = init_database(get_db_config()) + .await + .expect("Unable to open root database for unit test"); + + let result = mgr + .get_image( + &root_db, + "quay.io/bpfman-bytecode/xdp_pass:latest", + ImagePullPolicy::Never, + None, + None, + ) + .await; + + assert_matches!(result, Err(ImageError::ByteCodeImageNotfound(_))); + } + #[tokio::test] #[should_panic] async fn private_image_pull_failure() { - let tmpdir = tempfile::tempdir().unwrap(); - std::env::set_current_dir(&tmpdir).unwrap(); - - let (_tx, rx) = mpsc::channel(32); - let mut mgr = ImageManager::new(&tmpdir, true, rx).await.unwrap(); + let mut mgr = ImageManager::new(true).await.unwrap(); + let root_db = init_database(get_db_config()) + .await + .expect("Unable to open root database for unit test"); mgr.get_image( + &root_db, "quay.io/bpfman-bytecode/xdp_pass_private:latest", ImagePullPolicy::Always, None, @@ -565,13 +436,14 @@ mod tests { #[tokio::test] async fn private_image_pull_and_bytecode_verify() { - let tmpdir = tempfile::tempdir().unwrap(); - std::env::set_current_dir(&tmpdir).unwrap(); - let (_tx, rx) = mpsc::channel(32); - let mut mgr = ImageManager::new(&tmpdir, true, rx).await.unwrap(); + let mut mgr = ImageManager::new(true).await.unwrap(); + let root_db = init_database(get_db_config()) + .await + .expect("Unable to open root database for unit test"); - let (path, _) = mgr + let (image_content_key, _) = mgr .get_image( + &root_db, "quay.io/bpfman-bytecode/xdp_pass_private:latest", ImagePullPolicy::Always, Some("bpfman-bytecode+bpfmancreds".to_owned()), @@ -580,11 +452,11 @@ mod tests { .await .expect("failed to pull bytecode"); - assert!(Path::new(&path).exists()); + // Assert that an manifest, config and bytecode key were formed for image. + assert!(root_db.scan_prefix(image_content_key.clone()).count() == 3); let program_bytes = mgr - .get_bytecode_from_image_store(path) - .await + .get_bytecode_from_image_store(&root_db, image_content_key) .expect("failed to get bytecode from image store"); assert!(!program_bytes.is_empty()) @@ -592,14 +464,14 @@ mod tests { #[tokio::test] async fn image_pull_failure() { - let tmpdir = tempfile::tempdir().unwrap(); - std::env::set_current_dir(&tmpdir).unwrap(); - - let (_tx, rx) = mpsc::channel(32); - let mut mgr = ImageManager::new(&tmpdir, true, rx).await.unwrap(); + let mut mgr = ImageManager::new(true).await.unwrap(); + let root_db = init_database(get_db_config()) + .await + .expect("Unable to open root database for unit test"); let result = mgr .get_image( + &root_db, "quay.io/bpfman-bytecode/xdp_pass:latest", ImagePullPolicy::Never, None, @@ -610,34 +482,28 @@ mod tests { assert_matches!(result, Err(ImageError::ByteCodeImageNotfound(_))); } - #[tokio::test] - async fn test_good_image_content_path() { - let tmpdir = tempfile::tempdir().unwrap(); - std::env::set_current_dir(&tmpdir).unwrap(); - + #[test] + fn test_good_image_content_key() { struct Case { input: &'static str, - output: PathBuf, + output: &'static str, } let tt = vec![ - Case{input: "busybox", output: tmpdir.as_ref().to_path_buf().join("docker.io/library/busybox/latest")}, - Case{input:"quay.io/busybox", output: tmpdir.as_ref().to_path_buf().join("quay.io/busybox/latest")}, - Case{input:"docker.io/test:tag", output: tmpdir.as_ref().to_path_buf().join("docker.io/library/test/tag")}, - Case{input:"quay.io/test:5000", output: tmpdir.as_ref().to_path_buf().join("quay.io/test/5000")}, - Case{input:"test.com/repo:tag", output: tmpdir.as_ref().to_path_buf().join("test.com/repo/tag")}, + Case{input: "busybox", output: "docker.io_library_busybox_latest"}, + Case{input:"quay.io/busybox", output: "quay.io_busybox_latest"}, + Case{input:"docker.io/test:tag", output: "docker.io_library_test_tag"}, + Case{input:"quay.io/test:5000", output: "quay.io_test_5000"}, + Case{input:"test.com/repo:tag", output: "test.com_repo_tag"}, Case{ input:"test.com/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - output: tmpdir.as_ref().to_path_buf().join("test.com/repo/sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + output: "test.com_repo_sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", } ]; - let (_tx, rx) = mpsc::channel(32); - let mgr = ImageManager::new(&tmpdir, true, rx).await.unwrap(); - for t in tt { let good_reference: Reference = t.input.parse().unwrap(); - let image_content_path = mgr.get_image_content_dir(&good_reference); - assert_eq!(image_content_path, t.output); + let image_content_key = get_image_content_key(&good_reference); + assert_eq!(image_content_key, t.output); } } } diff --git a/bpfman/src/oci_utils/mod.rs b/bpfman/src/oci_utils/mod.rs index 986ffd42d..6690b15d8 100644 --- a/bpfman/src/oci_utils/mod.rs +++ b/bpfman/src/oci_utils/mod.rs @@ -2,8 +2,8 @@ // Copyright Authors of bpfman pub(crate) mod cosign; -pub(crate) mod image_manager; -pub(crate) use image_manager::ImageManager; +pub mod image_manager; + use thiserror::Error; #[derive(Debug, Error)] @@ -20,4 +20,6 @@ pub enum ImageError { ByteCodeImageProcessFailure(#[from] anyhow::Error), #[error("BytecodeImage not found: {0}")] ByteCodeImageNotfound(String), + #[error("{0}: {1}")] + DatabaseError(String, String), } diff --git a/bpfman/src/rpc.rs b/bpfman/src/rpc.rs deleted file mode 100644 index 246086e8b..000000000 --- a/bpfman/src/rpc.rs +++ /dev/null @@ -1,445 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman -use bpfman_api::{ - v1::{ - attach_info::Info, bpfman_server::Bpfman, bytecode_location::Location, - list_response::ListResult, GetRequest, GetResponse, KprobeAttachInfo, ListRequest, - ListResponse, LoadRequest, LoadResponse, PullBytecodeRequest, PullBytecodeResponse, - TcAttachInfo, TracepointAttachInfo, UnloadRequest, UnloadResponse, UprobeAttachInfo, - XdpAttachInfo, - }, - TcProceedOn, XdpProceedOn, -}; -use log::warn; -use tokio::sync::{mpsc, mpsc::Sender, oneshot}; -use tonic::{Request, Response, Status}; - -use crate::command::{ - Command, GetArgs, KprobeProgram, LoadArgs, Program, ProgramData, PullBytecodeArgs, TcProgram, - TracepointProgram, UnloadArgs, UprobeProgram, XdpProgram, -}; - -#[derive(Debug)] -pub struct BpfmanLoader { - tx: Sender, -} - -impl BpfmanLoader { - pub(crate) fn new(tx: mpsc::Sender) -> BpfmanLoader { - BpfmanLoader { tx } - } -} - -#[tonic::async_trait] -impl Bpfman for BpfmanLoader { - async fn load(&self, request: Request) -> Result, Status> { - let request = request.into_inner(); - - let (resp_tx, resp_rx) = oneshot::channel(); - - let bytecode_source = match request - .bytecode - .ok_or(Status::aborted("missing bytecode info"))? - .location - .ok_or(Status::aborted("missing location"))? - { - Location::Image(i) => crate::command::Location::Image(i.into()), - Location::File(p) => crate::command::Location::File(p), - }; - - let data = ProgramData::new( - bytecode_source, - request.name, - request.metadata, - request.global_data, - request.map_owner_id, - ); - - let load_args = LoadArgs { - program: match request - .attach - .ok_or(Status::aborted("missing attach info"))? - .info - .ok_or(Status::aborted("missing info"))? - { - Info::XdpAttachInfo(XdpAttachInfo { - priority, - iface, - position: _, - proceed_on, - }) => Program::Xdp(XdpProgram::new( - data, - priority, - iface, - XdpProceedOn::from_int32s(proceed_on) - .map_err(|_| Status::aborted("failed to parse proceed_on"))?, - )), - Info::TcAttachInfo(TcAttachInfo { - priority, - iface, - position: _, - direction, - proceed_on, - }) => { - let direction = direction - .try_into() - .map_err(|_| Status::aborted("direction is not a string"))?; - Program::Tc(TcProgram::new( - data, - priority, - iface, - TcProceedOn::from_int32s(proceed_on) - .map_err(|_| Status::aborted("failed to parse proceed_on"))?, - direction, - )) - } - Info::TracepointAttachInfo(TracepointAttachInfo { tracepoint }) => { - Program::Tracepoint(TracepointProgram::new(data, tracepoint)) - } - Info::KprobeAttachInfo(KprobeAttachInfo { - fn_name, - offset, - retprobe, - namespace, - }) => Program::Kprobe(KprobeProgram::new( - data, fn_name, offset, retprobe, namespace, - )), - Info::UprobeAttachInfo(UprobeAttachInfo { - fn_name, - offset, - target, - retprobe, - pid, - namespace, - }) => Program::Uprobe(UprobeProgram::new( - data, fn_name, offset, target, retprobe, pid, namespace, - )), - }, - responder: resp_tx, - }; - - // Send the GET request - self.tx.send(Command::Load(load_args)).await.unwrap(); - - // Await the response - match resp_rx.await { - Ok(res) => match res { - Ok(program) => { - let reply_entry = LoadResponse { - info: match (&program).try_into() { - Ok(i) => Some(i), - Err(_) => None, - }, - kernel_info: match (&program).try_into() { - Ok(i) => Some(i), - Err(_) => None, - }, - }; - Ok(Response::new(reply_entry)) - } - Err(e) => { - warn!("BPFMAN load error: {:#?}", e); - Err(Status::aborted(format!("{e}"))) - } - }, - - Err(e) => { - warn!("RPC load error: {:#?}", e); - Err(Status::aborted(format!("{e}"))) - } - } - } - - async fn unload( - &self, - request: Request, - ) -> Result, Status> { - let reply = UnloadResponse {}; - let request = request.into_inner(); - let id = request.id; - - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::Unload(UnloadArgs { - id, - responder: resp_tx, - }); - - // Send the GET request - self.tx.send(cmd).await.unwrap(); - - // Await the response - match resp_rx.await { - Ok(res) => match res { - Ok(_) => Ok(Response::new(reply)), - Err(e) => { - warn!("BPFMAN unload error: {}", e); - Err(Status::aborted(format!("{e}"))) - } - }, - Err(e) => { - warn!("RPC unload error: {}", e); - Err(Status::aborted(format!("{e}"))) - } - } - } - - async fn get(&self, request: Request) -> Result, Status> { - let request = request.into_inner(); - let id = request.id; - - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::Get(GetArgs { - id, - responder: resp_tx, - }); - - // Send the GET request - self.tx.send(cmd).await.unwrap(); - - // Await the response - match resp_rx.await { - Ok(res) => match res { - Ok(program) => { - let reply_entry = GetResponse { - info: match (&program).try_into() { - Ok(i) => Some(i), - Err(_) => None, - }, - kernel_info: match (&program).try_into() { - Ok(i) => Some(i), - Err(_) => None, - }, - }; - Ok(Response::new(reply_entry)) - } - Err(e) => { - warn!("BPFMAN get error: {}", e); - Err(Status::aborted(format!("{e}"))) - } - }, - Err(e) => { - warn!("RPC get error: {}", e); - Err(Status::aborted(format!("{e}"))) - } - } - } - - async fn list(&self, request: Request) -> Result, Status> { - let mut reply = ListResponse { results: vec![] }; - - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::List { responder: resp_tx }; - - // Send the GET request - self.tx.send(cmd).await.unwrap(); - - // Await the response - match resp_rx.await { - Ok(res) => match res { - Ok(results) => { - for r in results { - // If filtering on Program Type, then make sure this program matches, else skip. - if let Some(p) = request.get_ref().program_type { - if p != r.kind() as u32 { - continue; - } - } - - match r.data() { - // If filtering on `bpfman Only`, this program is of type Unsupported so skip - Err(_) => { - if request.get_ref().bpfman_programs_only() - || !request.get_ref().match_metadata.is_empty() - { - continue; - } - } - // Bpfman Program - Ok(data) => { - // Filter on the input metadata field if provided - let mut meta_match = true; - for (key, value) in &request.get_ref().match_metadata { - if let Some(v) = data.metadata().get(key) { - if *value != *v { - meta_match = false; - break; - } - } else { - meta_match = false; - break; - } - } - - if !meta_match { - continue; - } - } - } - - // Populate the response with the Program Info and the Kernel Info. - let reply_entry = ListResult { - info: match (&r).try_into() { - Ok(i) => Some(i), - Err(_) => None, - }, - kernel_info: match (&r).try_into() { - Ok(i) => Some(i), - Err(_) => None, - }, - }; - reply.results.push(reply_entry) - } - Ok(Response::new(reply)) - } - Err(e) => { - warn!("BPFMAN list error: {}", e); - Err(Status::aborted(format!("{e}"))) - } - }, - Err(e) => { - warn!("RPC list error: {}", e); - Err(Status::aborted(format!("{e}"))) - } - } - } - - async fn pull_bytecode( - &self, - request: tonic::Request, - ) -> std::result::Result, tonic::Status> { - let request = request.into_inner(); - let image = match request.image { - Some(i) => i.into(), - None => return Err(Status::aborted("Empty pull_bytecode request received")), - }; - let (resp_tx, resp_rx) = oneshot::channel(); - let cmd = Command::PullBytecode(PullBytecodeArgs { - image, - responder: resp_tx, - }); - - self.tx.send(cmd).await.unwrap(); - - // Await the response - match resp_rx.await { - Ok(res) => match res { - Ok(_) => { - let reply = PullBytecodeResponse {}; - Ok(Response::new(reply)) - } - Err(e) => { - warn!("BPFMAN pull_bytecode error: {:#?}", e); - Err(Status::aborted(format!("{e}"))) - } - }, - - Err(e) => { - warn!("RPC pull_bytecode error: {:#?}", e); - Err(Status::aborted(format!("{e}"))) - } - } - } -} - -#[cfg(test)] -mod test { - use bpfman_api::v1::{ - bytecode_location::Location, AttachInfo, BytecodeLocation, LoadRequest, XdpAttachInfo, - }; - use tokio::sync::mpsc::Receiver; - - use super::*; - use crate::command::{KernelProgramInfo, Program}; - - #[tokio::test] - async fn test_load_with_valid_id() { - let (tx, rx) = mpsc::channel(32); - let loader = BpfmanLoader::new(tx.clone()); - - let attach_info = AttachInfo { - info: Some(Info::XdpAttachInfo(XdpAttachInfo { - iface: "eth0".to_string(), - priority: 50, - position: 0, - proceed_on: vec![2, 31], - })), - }; - let request = LoadRequest { - bytecode: Some(BytecodeLocation { - location: Some(Location::Image(bpfman_api::v1::BytecodeImage { - url: "quay.io/bpfman-bytecode/xdp:latest".to_string(), - ..Default::default() - })), - }), - attach: Some(attach_info), - ..Default::default() - }; - - tokio::spawn(async move { - mock_serve(rx).await; - }); - - let res = loader.load(Request::new(request)).await; - assert!(res.is_ok()); - } - - #[tokio::test] - async fn test_pull_bytecode() { - let (tx, rx) = mpsc::channel(32); - let loader = BpfmanLoader::new(tx.clone()); - - let request = PullBytecodeRequest { - image: Some(bpfman_api::v1::BytecodeImage { - url: String::from("quay.io/bpfman-bytecode/xdp_pass:latest"), - image_pull_policy: bpfman_api::ImagePullPolicy::Always.into(), - username: Some(String::from("someone")), - password: Some(String::from("secret")), - }), - }; - - tokio::spawn(async move { mock_serve(rx).await }); - - let res = loader.pull_bytecode(Request::new(request)).await; - assert!(res.is_ok()); - } - - async fn mock_serve(mut rx: Receiver) { - let kernel_info = KernelProgramInfo { - id: 0, - name: "".to_string(), - program_type: 0, - loaded_at: "".to_string(), - tag: "".to_string(), - gpl_compatible: false, - map_ids: vec![], - btf_id: 0, - bytes_xlated: 0, - jited: false, - bytes_jited: 0, - bytes_memlock: 0, - verified_insns: 0, - }; - let mut data = ProgramData::default(); - data.set_kernel_info(Some(kernel_info)); - - let program = Program::Xdp(XdpProgram { - data, - priority: 0, - if_index: Some(9999), - iface: String::new(), - proceed_on: XdpProceedOn::default(), - current_position: None, - attached: false, - }); - - while let Some(cmd) = rx.recv().await { - match cmd { - Command::Load(args) => args.responder.send(Ok(program.clone())).unwrap(), - Command::Unload(args) => args.responder.send(Ok(())).unwrap(), - Command::List { responder, .. } => responder.send(Ok(vec![])).unwrap(), - Command::Get(args) => args.responder.send(Ok(program.clone())).unwrap(), - Command::PullBytecode(args) => args.responder.send(Ok(())).unwrap(), - } - } - } -} diff --git a/bpfman/src/serve.rs b/bpfman/src/serve.rs deleted file mode 100644 index 128dd1653..000000000 --- a/bpfman/src/serve.rs +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of bpfman - -use std::{fs::remove_file, path::Path}; - -use bpfman_api::{ - config::{self, Config}, - util::directories::STDIR_BYTECODE_IMAGE_CONTENT_STORE, - v1::bpfman_server::BpfmanServer, -}; -use log::{debug, info}; -use tokio::{ - join, - net::UnixListener, - select, - signal::unix::{signal, SignalKind}, - sync::mpsc, - task::JoinHandle, -}; -use tokio_stream::wrappers::UnixListenerStream; -use tonic::transport::Server; - -use crate::{ - bpf::BpfManager, - oci_utils::ImageManager, - rpc::BpfmanLoader, - static_program::get_static_programs, - storage::StorageManager, - utils::{set_file_permissions, SOCK_MODE}, -}; - -pub async fn serve( - config: &Config, - static_program_path: &str, - csi_support: bool, -) -> anyhow::Result<()> { - let (tx, rx) = mpsc::channel(32); - - let loader = BpfmanLoader::new(tx.clone()); - let service = BpfmanServer::new(loader); - - let mut listeners: Vec<_> = Vec::new(); - - for endpoint in &config.grpc.endpoints { - match endpoint { - config::Endpoint::Unix { path, enabled } => { - if !enabled { - info!("Skipping disabled endpoint on {path}"); - continue; - } - - match serve_unix(path.clone(), service.clone()).await { - Ok(handle) => listeners.push(handle), - Err(e) => eprintln!("Error = {e:?}"), - } - } - } - } - - let allow_unsigned = config.signing.as_ref().map_or(true, |s| s.allow_unsigned); - let (itx, irx) = mpsc::channel(32); - - let mut image_manager = - ImageManager::new(STDIR_BYTECODE_IMAGE_CONTENT_STORE, allow_unsigned, irx).await?; - let image_manager_handle = tokio::spawn(async move { - image_manager.run().await; - }); - - let mut bpf_manager = BpfManager::new(config.clone(), rx, itx); - bpf_manager.rebuild_state().await?; - - let static_programs = get_static_programs(static_program_path).await?; - - // Load any static programs first - if !static_programs.is_empty() { - for prog in static_programs { - let ret_prog = bpf_manager.add_program(prog).await?; - // Get the Kernel Info. - let kernel_info = ret_prog - .kernel_info() - .expect("kernel info should be set for all loaded programs"); - info!("Loaded static program with program id {}", kernel_info.id) - } - }; - - if csi_support { - let storage_manager = StorageManager::new(tx); - let storage_manager_handle = tokio::spawn(async move { storage_manager.run().await }); - let (_, res_image, res_storage, _) = join!( - join_listeners(listeners), - image_manager_handle, - storage_manager_handle, - bpf_manager.process_commands() - ); - if let Some(e) = res_storage.err() { - return Err(e.into()); - } - if let Some(e) = res_image.err() { - return Err(e.into()); - } - } else { - let (_, res_image, _) = join!( - join_listeners(listeners), - image_manager_handle, - bpf_manager.process_commands() - ); - if let Some(e) = res_image.err() { - return Err(e.into()); - } - } - - Ok(()) -} - -pub(crate) async fn shutdown_handler() { - let mut sigint = signal(SignalKind::interrupt()).unwrap(); - let mut sigterm = signal(SignalKind::terminate()).unwrap(); - select! { - _ = sigint.recv() => {debug!("Received SIGINT")}, - _ = sigterm.recv() => {debug!("Received SIGTERM")}, - } -} - -async fn join_listeners(listeners: Vec>) { - for listener in listeners { - match listener.await { - Ok(()) => {} - Err(e) => eprintln!("Error = {e:?}"), - } - } -} - -async fn serve_unix( - path: String, - service: BpfmanServer, -) -> anyhow::Result> { - // Listen on Unix socket - if Path::new(&path).exists() { - // Attempt to remove the socket, since bind fails if it exists - remove_file(&path)?; - } - - let uds = UnixListener::bind(&path)?; - let uds_stream = UnixListenerStream::new(uds); - // Always set the file permissions of our listening socket. - set_file_permissions(&path.clone(), SOCK_MODE).await; - - let serve = Server::builder() - .add_service(service) - .serve_with_incoming_shutdown(uds_stream, shutdown_handler()); - - Ok(tokio::spawn(async move { - info!("Listening on {path}"); - if let Err(e) = serve.await { - eprintln!("Error = {e:?}"); - } - info!("Shutdown Unix Handler {}", path); - })) -} diff --git a/bpfman/src/static_program.rs b/bpfman/src/static_program.rs index 2fd306fe9..dba03c8da 100644 --- a/bpfman/src/static_program.rs +++ b/bpfman/src/static_program.rs @@ -1,262 +1,264 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of bpfman -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; -use anyhow::bail; -use bpfman_api::{ - util::directories::CFGDIR_STATIC_PROGRAMS, ProgramType, TcProceedOn, XdpProceedOn, -}; -use log::{info, warn}; -use serde::Deserialize; -use tokio::fs; +// TODO(astoycos) see issue #881 +// use std::{ +// collections::HashMap, +// path::{Path, PathBuf}, +// }; -use crate::{ - command::{ - Direction, - Location::{File, Image}, - Program, ProgramData, TcProgram, TracepointProgram, XdpProgram, - }, - oci_utils::image_manager::BytecodeImage, - utils::read_to_string, -}; +// use anyhow::bail; +// use bpfman_api::{ +// util::directories::CFGDIR_STATIC_PROGRAMS, ProgramType, TcProceedOn, XdpProceedOn, +// }; +// use log::{info, warn}; +// use serde::Deserialize; +// use tokio::fs; -#[derive(Debug, Clone, Deserialize)] -pub(crate) struct XdpAttachInfo { - pub(crate) priority: i32, - pub(crate) iface: String, - pub(crate) proceed_on: XdpProceedOn, -} +// use crate::{ +// command::{ +// Direction, +// Location::{File, Image}, +// Program, ProgramData, TcProgram, TracepointProgram, XdpProgram, +// }, +// oci_utils::image_manager::BytecodeImage, +// utils::read_to_string, +// }; -#[derive(Debug, Clone, Deserialize)] -pub(crate) struct TcAttachInfo { - pub(crate) priority: i32, - pub(crate) iface: String, - pub(crate) proceed_on: TcProceedOn, - pub(crate) direction: Direction, -} - -#[derive(Debug, Clone, Deserialize)] -pub(crate) struct TracepointAttachInfo { - pub(crate) tracepoint: String, -} +// #[derive(Debug, Clone, Deserialize)] +// pub(crate) struct XdpAttachInfo { +// pub(crate) priority: i32, +// pub(crate) iface: String, +// pub(crate) proceed_on: XdpProceedOn, +// } -// TODO not yet implemented // #[derive(Debug, Clone, Deserialize)] -// pub(crate) struct KprobeAttachInfo { -// pub(crate) fn_name: String, -// pub(crate) offset: u64, -// pub(crate) retprobe: bool, -// pub(crate) namespace: Option, +// pub(crate) struct TcAttachInfo { +// pub(crate) priority: i32, +// pub(crate) iface: String, +// pub(crate) proceed_on: TcProceedOn, +// pub(crate) direction: Direction, // } // #[derive(Debug, Clone, Deserialize)] -// pub(crate) struct UprobeAttachInfo { -// pub(crate) fn_name: Option, -// pub(crate) offset: u64, -// pub(crate) target: String, -// pub(crate) retprobe: bool, -// pub(crate) pid: Option, -// pub(crate) namespace: Option, +// pub(crate) struct TracepointAttachInfo { +// pub(crate) tracepoint: String, // } -#[derive(Debug, Deserialize, Clone)] -pub struct StaticProgramEntry { - bytecode_image: Option, - file_path: Option, - name: String, - global_data: HashMap>, - program_type: ProgramType, - xdp_attach: Option, - tc_attach: Option, - tracepoint_attach: Option, -} +// // TODO not yet implemented +// // #[derive(Debug, Clone, Deserialize)] +// // pub(crate) struct KprobeAttachInfo { +// // pub(crate) fn_name: String, +// // pub(crate) offset: u64, +// // pub(crate) retprobe: bool, +// // pub(crate) container_pid: Option, +// // } -impl StaticProgramEntry { - pub(crate) fn get_bytecode_image(self) -> Option { - self.bytecode_image - } -} +// // #[derive(Debug, Clone, Deserialize)] +// // pub(crate) struct UprobeAttachInfo { +// // pub(crate) fn_name: Option, +// // pub(crate) offset: u64, +// // pub(crate) target: String, +// // pub(crate) retprobe: bool, +// // pub(crate) pid: Option, +// // pub(crate) container_pid: Option, +// // } -#[derive(Debug, Deserialize)] -pub struct NetworkAttach { - pub interface: String, - pub priority: i32, - pub proceed_on: Vec, -} +// #[derive(Debug, Deserialize, Clone)] +// pub struct StaticProgramEntry { +// bytecode_image: Option, +// file_path: Option, +// name: String, +// global_data: HashMap>, +// program_type: ProgramType, +// xdp_attach: Option, +// tc_attach: Option, +// tracepoint_attach: Option, +// } -#[derive(Debug, Deserialize, Clone)] -struct StaticProgramManager { - #[serde(skip)] - path: PathBuf, - programs: Vec, -} +// impl StaticProgramEntry { +// pub(crate) fn get_bytecode_image(self) -> Option { +// self.bytecode_image +// } +// } -impl StaticProgramManager { - async fn programs_from_directory(mut self) -> Result<(), anyhow::Error> { - if let Ok(mut entries) = fs::read_dir(self.path).await { - while let Some(file) = entries.next_entry().await? { - let path = &file.path(); - // ignore directories - if path.is_dir() { - continue; - } +// #[derive(Debug, Deserialize)] +// pub struct NetworkAttach { +// pub interface: String, +// pub priority: i32, +// pub proceed_on: Vec, +// } - if let Ok(contents) = read_to_string(path).await { - let program = toml::from_str(&contents)?; +// #[derive(Debug, Deserialize, Clone)] +// struct StaticProgramManager { +// #[serde(skip)] +// path: PathBuf, +// programs: Vec, +// } - self.programs.push(program); - } else { - warn!("Failed to parse program static file {:?}.", path.to_str()); - continue; - } - } - } - Ok(()) - } -} +// impl StaticProgramManager { +// async fn programs_from_directory(mut self) -> Result<(), anyhow::Error> { +// if let Ok(mut entries) = fs::read_dir(self.path).await { +// while let Some(file) = entries.next_entry().await? { +// let path = &file.path(); +// // ignore directories +// if path.is_dir() { +// continue; +// } -pub(crate) async fn get_static_programs>( - path: P, -) -> Result, anyhow::Error> { - let static_program_manager = StaticProgramManager { - path: path.as_ref().to_path_buf(), - programs: Vec::new(), - }; +// if let Ok(contents) = read_to_string(path).await { +// let program = toml::from_str(&contents)?; - static_program_manager - .clone() - .programs_from_directory() - .await?; +// self.programs.push(program); +// } else { +// warn!("Failed to parse program static file {:?}.", path.to_str()); +// continue; +// } +// } +// } +// Ok(()) +// } +// } + +// pub(crate) async fn get_static_programs>( +// path: P, +// ) -> Result, anyhow::Error> { +// let static_program_manager = StaticProgramManager { +// path: path.as_ref().to_path_buf(), +// programs: Vec::new(), +// }; + +// static_program_manager +// .clone() +// .programs_from_directory() +// .await?; - let mut programs: Vec = Vec::new(); +// let mut programs: Vec = Vec::new(); - // Load any static programs first - if !static_program_manager.programs.is_empty() { - info!("Loading static programs from {CFGDIR_STATIC_PROGRAMS}"); - for program in static_program_manager.programs { - let location = match program.file_path { - Some(p) => File(p), - None => Image( - program - .clone() - .get_bytecode_image() - .expect("static program did not provide bytecode"), - ), - }; +// // Load any static programs first +// if !static_program_manager.programs.is_empty() { +// info!("Loading static programs from {CFGDIR_STATIC_PROGRAMS}"); +// for program in static_program_manager.programs { +// let location = match program.file_path { +// Some(p) => File(p), +// None => Image( +// program +// .clone() +// .get_bytecode_image() +// .expect("static program did not provide bytecode"), +// ), +// }; - let data = ProgramData::new( - location, - program.name, - HashMap::new(), - program.global_data, - None, - ); - let prog = match program.program_type { - ProgramType::Xdp => { - if let Some(m) = program.xdp_attach { - Program::Xdp(XdpProgram::new(data, m.priority, m.iface, m.proceed_on)) - } else { - bail!("invalid info for xdp program") - } - } - ProgramType::Tc => { - if let Some(m) = program.tc_attach { - Program::Tc(TcProgram::new( - data, - m.priority, - m.iface, - m.proceed_on, - m.direction, - )) - } else { - bail!("invalid attach type for tc program") - } - } - ProgramType::Tracepoint => { - if let Some(m) = program.tracepoint_attach { - Program::Tracepoint(TracepointProgram::new(data, m.tracepoint)) - } else { - bail!("invalid attach type for tc program") - } - } - m => bail!("program type not yet supported to load statically: {:?}", m), - }; +// let data = ProgramData::new( +// location, +// program.name, +// HashMap::new(), +// program.global_data, +// None, +// ); +// let prog = match program.program_type { +// ProgramType::Xdp => { +// if let Some(m) = program.xdp_attach { +// Program::Xdp(XdpProgram::new(data, m.priority, m.iface, m.proceed_on)) +// } else { +// bail!("invalid info for xdp program") +// } +// } +// ProgramType::Tc => { +// if let Some(m) = program.tc_attach { +// Program::Tc(TcProgram::new( +// data, +// m.priority, +// m.iface, +// m.proceed_on, +// m.direction, +// )) +// } else { +// bail!("invalid attach type for tc program") +// } +// } +// ProgramType::Tracepoint => { +// if let Some(m) = program.tracepoint_attach { +// Program::Tracepoint(TracepointProgram::new(data, m.tracepoint)) +// } else { +// bail!("invalid attach type for tc program") +// } +// } +// m => bail!("program type not yet supported to load statically: {:?}", m), +// }; - programs.push(prog) - } - }; +// programs.push(prog) +// } +// }; - Ok(programs) -} +// Ok(programs) +// } + +// #[cfg(test)] +// mod test { +// use super::*; -#[cfg(test)] -mod test { - use super::*; +// #[tokio::test] +// async fn test_parse_program_from_invalid_path() { +// let static_program_manager = StaticProgramManager { +// path: "/tmp/file.toml".into(), +// programs: Vec::new(), +// }; - #[tokio::test] - async fn test_parse_program_from_invalid_path() { - let static_program_manager = StaticProgramManager { - path: "/tmp/file.toml".into(), - programs: Vec::new(), - }; +// static_program_manager +// .clone() +// .programs_from_directory() +// .await +// .unwrap(); +// assert!(static_program_manager.programs.is_empty()) +// } - static_program_manager - .clone() - .programs_from_directory() - .await - .unwrap(); - assert!(static_program_manager.programs.is_empty()) - } +// #[test] +// fn test_parse_single_file() { +// let input: &str = r#" +// [[programs]] +// name = "firewall" +// file_path = "/opt/bin/myapp/lib/myebpf.o" +// global_data = { } +// program_type ="Xdp" +// xdp_attach = { iface = "eth0", priority = 50, proceed_on = [], position=0 } - #[test] - fn test_parse_single_file() { - let input: &str = r#" - [[programs]] - name = "firewall" - file_path = "/opt/bin/myapp/lib/myebpf.o" - global_data = { } - program_type ="Xdp" - xdp_attach = { iface = "eth0", priority = 50, proceed_on = [], position=0 } +// [[programs]] +// name = "pass" +// bytecode_image = { image_url = "quay.io/bpfman-bytecode/xdp_pass:latest", image_pull_policy="Always" } +// global_data = { } +// program_type ="Xdp" +// xdp_attach = { iface = "eth0", priority = 55, proceed_on = [], position=0 } - [[programs]] - name = "pass" - bytecode_image = { image_url = "quay.io/bpfman-bytecode/xdp_pass:latest", image_pull_policy="Always" } - global_data = { } - program_type ="Xdp" - xdp_attach = { iface = "eth0", priority = 55, proceed_on = [], position=0 } +// [[programs]] +// name = "counter" +// bytecode_image = { image_url = "quay.io/bpfman-bytecode/xdp_pass:latest", image_pull_policy="Always" } +// global_data = { } +// program_type ="Tc" +// tc_attach = { iface = "eth0", priority = 55, proceed_on = [], position=0, direction="Ingress" } - [[programs]] - name = "counter" - bytecode_image = { image_url = "quay.io/bpfman-bytecode/xdp_pass:latest", image_pull_policy="Always" } - global_data = { } - program_type ="Tc" - tc_attach = { iface = "eth0", priority = 55, proceed_on = [], position=0, direction="Ingress" } - - [[programs]] - name = "tracepoint" - bytecode_image = { image_url = "quay.io/bpfman-bytecode/tracepoint:latest", image_pull_policy="Always" } - global_data = { } - program_type ="Tracepoint" - tracepoint_attach = { tracepoint = "syscalls/sys_enter_openat" } - "#; +// [[programs]] +// name = "tracepoint" +// bytecode_image = { image_url = "quay.io/bpfman-bytecode/tracepoint:latest", image_pull_policy="Always" } +// global_data = { } +// program_type ="Tracepoint" +// tracepoint_attach = { tracepoint = "syscalls/sys_enter_openat" } +// "#; - let mut programs: StaticProgramManager = - toml::from_str(input).expect("error parsing toml input"); - match programs.programs.pop() { - Some(i) => { - if let Some(m) = i.xdp_attach { - assert_eq!(m.iface, "eth0"); - assert_eq!(m.priority, 55); - } else if let Some(m) = i.tracepoint_attach { - assert_eq!(m.tracepoint, "syscalls/sys_enter_openat") - } else { - panic!("incorrect attach type") - } - } - None => panic!("expected programs to be present"), - } - } -} +// let mut programs: StaticProgramManager = +// toml::from_str(input).expect("error parsing toml input"); +// match programs.programs.pop() { +// Some(i) => { +// if let Some(m) = i.xdp_attach { +// assert_eq!(m.iface, "eth0"); +// assert_eq!(m.priority, 55); +// } else if let Some(m) = i.tracepoint_attach { +// assert_eq!(m.tracepoint, "syscalls/sys_enter_openat") +// } else { +// panic!("incorrect attach type") +// } +// } +// None => panic!("expected programs to be present"), +// } +// } +// } diff --git a/bpfman/src/types.rs b/bpfman/src/types.rs new file mode 100644 index 000000000..001f2da6b --- /dev/null +++ b/bpfman/src/types.rs @@ -0,0 +1,2164 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of bpfman + +//! Commands between the RPC thread and the BPF thread +use std::{ + collections::HashMap, + fmt, fs, + path::{Path, PathBuf}, + time::SystemTime, +}; + +use aya::programs::ProgramInfo as AyaProgInfo; +use chrono::{prelude::DateTime, Local}; +use clap::ValueEnum; +use log::{info, warn}; +use rand::Rng; +use serde::{Deserialize, Serialize}; +use sled::Db; + +use crate::{ + directories::RTDIR_FS, + errors::{BpfmanError, ParseError}, + multiprog::{DispatcherId, DispatcherInfo}, + oci_utils::image_manager::ImageManager, + utils::{ + bytes_to_bool, bytes_to_i32, bytes_to_string, bytes_to_u32, bytes_to_u64, bytes_to_usize, + sled_get, sled_get_option, sled_insert, + }, +}; + +/// These constants define the key of SLED DB +pub(crate) const PROGRAM_PREFIX: &str = "program_"; +pub(crate) const PROGRAM_PRE_LOAD_PREFIX: &str = "pre_load_program_"; +const KIND: &str = "kind"; +const NAME: &str = "name"; +const ID: &str = "id"; +const LOCATION_FILENAME: &str = "location_filename"; +const LOCATION_IMAGE_URL: &str = "location_image_url"; +const LOCATION_IMAGE_PULL_POLICY: &str = "location_image_pull_policy"; +const LOCATION_USERNAME: &str = "location_username"; +const LOCATION_PASSWORD: &str = "location_password"; +const MAP_OWNER_ID: &str = "map_owner_id"; +const MAP_PIN_PATH: &str = "map_pin_path"; +const PREFIX_GLOBAL_DATA: &str = "global_data_"; +const PREFIX_METADATA: &str = "metadata_"; +const PREFIX_MAPS_USED_BY: &str = "maps_used_by_"; +const PROGRAM_BYTES: &str = "program_bytes"; + +const KERNEL_NAME: &str = "kernel_name"; +const KERNEL_PROGRAM_TYPE: &str = "kernel_program_type"; +const KERNEL_LOADED_AT: &str = "kernel_loaded_at"; +const KERNEL_TAG: &str = "kernel_tag"; +const KERNEL_GPL_COMPATIBLE: &str = "kernel_gpl_compatible"; +const KERNEL_BTF_ID: &str = "kernel_btf_id"; +const KERNEL_BYTES_XLATED: &str = "kernel_bytes_xlated"; +const KERNEL_JITED: &str = "kernel_jited"; +const KERNEL_BYTES_JITED: &str = "kernel_bytes_jited"; +const KERNEL_BYTES_MEMLOCK: &str = "kernel_bytes_memlock"; +const KERNEL_VERIFIED_INSNS: &str = "kernel_verified_insns"; +const PREFIX_KERNEL_MAP_IDS: &str = "kernel_map_ids_"; + +const XDP_PRIORITY: &str = "xdp_priority"; +const XDP_IFACE: &str = "xdp_iface"; +const XDP_CURRENT_POSITION: &str = "xdp_current_position"; +const XDP_IF_INDEX: &str = "xdp_if_index"; +const XDP_ATTACHED: &str = "xdp_attached"; +const PREFIX_XDP_PROCEED_ON: &str = "xdp_proceed_on_"; + +const TC_PRIORITY: &str = "tc_priority"; +const TC_IFACE: &str = "tc_iface"; +const TC_CURRENT_POSITION: &str = "tc_current_position"; +const TC_IF_INDEX: &str = "tc_if_index"; +const TC_ATTACHED: &str = "tc_attached"; +const TC_DIRECTION: &str = "tc_direction"; +const PREFIX_TC_PROCEED_ON: &str = "tc_proceed_on_"; + +const TRACEPOINT_NAME: &str = "tracepoint_name"; + +const KPROBE_FN_NAME: &str = "kprobe_fn_name"; +const KPROBE_OFFSET: &str = "kprobe_offset"; +const KPROBE_RETPROBE: &str = "kprobe_retprobe"; +const KPROBE_CONTAINER_PID: &str = "kprobe_container_pid"; + +const UPROBE_FN_NAME: &str = "uprobe_fn_name"; +const UPROBE_OFFSET: &str = "uprobe_offset"; +const UPROBE_RETPROBE: &str = "uprobe_retprobe"; +const UPROBE_CONTAINER_PID: &str = "uprobe_container_pid"; +const UPROBE_PID: &str = "uprobe_pid"; +const UPROBE_TARGET: &str = "uprobe_target"; + +const FENTRY_FN_NAME: &str = "fentry_fn_name"; +const FEXIT_FN_NAME: &str = "fexit_fn_name"; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct BytecodeImage { + pub image_url: String, + pub image_pull_policy: ImagePullPolicy, + pub username: Option, + pub password: Option, +} + +impl BytecodeImage { + pub fn new( + image_url: String, + image_pull_policy: i32, + username: Option, + password: Option, + ) -> Self { + Self { + image_url, + image_pull_policy: image_pull_policy + .try_into() + .expect("Unable to parse ImagePullPolicy"), + username, + password, + } + } + + pub fn get_url(&self) -> &str { + &self.image_url + } + + pub fn get_pull_policy(&self) -> &ImagePullPolicy { + &self.image_pull_policy + } +} +#[derive(Debug, Clone, Default)] +pub struct ListFilter { + pub(crate) program_type: Option, + pub(crate) metadata_selector: HashMap, + pub(crate) bpfman_programs_only: bool, +} + +impl ListFilter { + pub fn new( + program_type: Option, + metadata_selector: HashMap, + bpfman_programs_only: bool, + ) -> Self { + Self { + program_type, + metadata_selector, + bpfman_programs_only, + } + } + + pub(crate) fn matches(&self, program: &Program) -> bool { + if let Program::Unsupported(_) = program { + if self.bpfman_programs_only { + return false; + } + + if let Some(prog_type) = self.program_type { + match program.get_data().get_kernel_program_type() { + Ok(kernel_prog_type) => { + if kernel_prog_type != prog_type { + return false; + } + } + Err(e) => { + warn!("Failed to get kernel program type during list match: {}", e); + return false; + } + } + } + + // If a selector was provided, skip over non-bpfman loaded programs. + if !self.metadata_selector.is_empty() { + return false; + } + } else { + // Program type filtering has to be done differently for bpfman owned + // programs since XDP and TC programs have a type EXT when loaded by + // bpfman. + let prog_type_internal: u32 = program.kind().into(); + if let Some(prog_type) = self.program_type { + if prog_type_internal != prog_type { + return false; + } + } + // Filter on the input metadata field if provided + for (key, value) in &self.metadata_selector { + match program.get_data().get_metadata() { + Ok(metadata) => { + if let Some(v) = metadata.get(key) { + if *value != *v { + return false; + } + } else { + return false; + } + } + Err(e) => { + warn!("Failed to get metadata during list match: {}", e); + return false; + } + } + } + } + true + } +} + +#[derive(Debug, Clone)] +pub enum Program { + Xdp(XdpProgram), + Tc(TcProgram), + Tracepoint(TracepointProgram), + Kprobe(KprobeProgram), + Uprobe(UprobeProgram), + Fentry(FentryProgram), + Fexit(FexitProgram), + Unsupported(ProgramData), +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum Location { + Image(BytecodeImage), + File(String), +} + +impl Location { + async fn get_program_bytes( + &self, + root_db: &Db, + image_manager: &mut ImageManager, + ) -> Result<(Vec, String), BpfmanError> { + match self { + Location::File(l) => Ok((crate::utils::read(l)?, "".to_owned())), + Location::Image(l) => { + let (path, bpf_function_name) = image_manager + .get_image( + root_db, + &l.image_url, + l.image_pull_policy.clone(), + l.username.clone(), + l.password.clone(), + ) + .await?; + let bytecode = image_manager.get_bytecode_from_image_store(root_db, path)?; + + Ok((bytecode, bpf_function_name)) + } + } + } +} + +#[derive(Debug, Serialize, Hash, Deserialize, Eq, PartialEq, Copy, Clone)] +pub enum Direction { + Ingress = 1, + Egress = 2, +} + +impl TryFrom for Direction { + type Error = ParseError; + + fn try_from(v: u32) -> Result { + match v { + 1 => Ok(Self::Ingress), + 2 => Ok(Self::Egress), + m => Err(ParseError::InvalidDirection { + direction: m.to_string(), + }), + } + } +} + +impl TryFrom for Direction { + type Error = ParseError; + + fn try_from(v: String) -> Result { + match v.as_str() { + "ingress" => Ok(Self::Ingress), + "egress" => Ok(Self::Egress), + m => Err(ParseError::InvalidDirection { + direction: m.to_string(), + }), + } + } +} + +impl std::fmt::Display for Direction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Direction::Ingress => f.write_str("ingress"), + Direction::Egress => f.write_str("egress"), + } + } +} + +/// ProgramData stores information about bpf programs that are loaded and managed +/// by bpfman. +#[derive(Debug, Clone)] +pub struct ProgramData { + // Prior to load this will be a temporary Tree with a random ID, following + // load it will be replaced with the main program database tree. + db_tree: sled::Tree, +} + +impl ProgramData { + pub fn new( + location: Location, + name: String, + metadata: HashMap, + global_data: HashMap>, + map_owner_id: Option, + ) -> Result { + let db = sled::Config::default() + .temporary(true) + .open() + .expect("unable to open temporary database"); + + let mut rng = rand::thread_rng(); + let id_rand = rng.gen::(); + + let db_tree = db + .open_tree(PROGRAM_PRE_LOAD_PREFIX.to_string() + &id_rand.to_string()) + .expect("Unable to open program database tree"); + + let mut pd = Self { db_tree }; + + pd.set_id(id_rand)?; + pd.set_location(location)?; + pd.set_name(&name)?; + pd.set_metadata(metadata)?; + pd.set_global_data(global_data)?; + if let Some(id) = map_owner_id { + pd.set_map_owner_id(id)?; + }; + + Ok(pd) + } + + pub(crate) fn new_empty(tree: sled::Tree) -> Self { + Self { db_tree: tree } + } + pub(crate) fn load(&mut self, root_db: &Db) -> Result<(), BpfmanError> { + let db_tree = root_db + .open_tree(self.db_tree.name()) + .expect("Unable to open program database tree"); + + // Copy over all key's and values to persistent tree + for r in self.db_tree.into_iter() { + let (k, v) = r.expect("unable to iterate db_tree"); + db_tree.insert(k, v).map_err(|e| { + BpfmanError::DatabaseError( + "unable to insert entry during copy".to_string(), + e.to_string(), + ) + })?; + } + + self.db_tree = db_tree; + + Ok(()) + } + + pub(crate) fn swap_tree(&mut self, root_db: &Db, new_id: u32) -> Result<(), BpfmanError> { + let new_tree = root_db + .open_tree(PROGRAM_PREFIX.to_string() + &new_id.to_string()) + .expect("Unable to open program database tree"); + + // Copy over all key's and values to new tree + for r in self.db_tree.into_iter() { + let (k, v) = r.expect("unable to iterate db_tree"); + new_tree.insert(k, v).map_err(|e| { + BpfmanError::DatabaseError( + "unable to insert entry during copy".to_string(), + e.to_string(), + ) + })?; + } + + root_db + .drop_tree(self.db_tree.name()) + .expect("unable to delete temporary program tree"); + + self.db_tree = new_tree; + self.set_id(new_id)?; + + Ok(()) + } + + /* + * Methods for setting and getting program data for programs managed by + * bpfman. + */ + + // A programData's kind could be different from the kernel_program_type value + // since the TC and XDP programs loaded by bpfman will have a ProgramType::Ext + // rather than ProgramType::Xdp or ProgramType::Tc. + // Kind should only be set on programs loaded by bpfman. + fn set_kind(&mut self, kind: ProgramType) -> Result<(), BpfmanError> { + sled_insert( + &self.db_tree, + KIND, + &(Into::::into(kind)).to_ne_bytes(), + ) + } + + pub fn get_kind(&self) -> Result, BpfmanError> { + sled_get_option(&self.db_tree, KIND).map(|v| v.map(|v| bytes_to_u32(v).try_into().unwrap())) + } + + pub(crate) fn set_name(&mut self, name: &str) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, NAME, name.as_bytes()) + } + + pub fn get_name(&self) -> Result { + sled_get(&self.db_tree, NAME).map(|v| bytes_to_string(&v)) + } + + pub(crate) fn set_id(&mut self, id: u32) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, ID, &id.to_ne_bytes()) + } + + pub fn get_id(&self) -> Result { + sled_get(&self.db_tree, ID).map(bytes_to_u32) + } + + pub(crate) fn set_location(&mut self, loc: Location) -> Result<(), BpfmanError> { + match loc { + Location::File(l) => sled_insert(&self.db_tree, LOCATION_FILENAME, l.as_bytes()), + Location::Image(l) => { + sled_insert(&self.db_tree, LOCATION_IMAGE_URL, l.image_url.as_bytes())?; + sled_insert( + &self.db_tree, + LOCATION_IMAGE_PULL_POLICY, + l.image_pull_policy.to_string().as_bytes(), + )?; + if let Some(u) = l.username { + sled_insert(&self.db_tree, LOCATION_USERNAME, u.as_bytes())?; + }; + + if let Some(p) = l.password { + sled_insert(&self.db_tree, LOCATION_PASSWORD, p.as_bytes())?; + }; + Ok(()) + } + } + .map_err(|e| { + BpfmanError::DatabaseError( + format!( + "Unable to insert location database entries into tree {:?}", + self.db_tree.name() + ), + e.to_string(), + ) + }) + } + + pub fn get_location(&self) -> Result { + if let Ok(l) = sled_get(&self.db_tree, LOCATION_FILENAME) { + Ok(Location::File(bytes_to_string(&l).to_string())) + } else { + Ok(Location::Image(BytecodeImage { + image_url: bytes_to_string(&sled_get(&self.db_tree, LOCATION_IMAGE_URL)?) + .to_string(), + image_pull_policy: bytes_to_string(&sled_get( + &self.db_tree, + LOCATION_IMAGE_PULL_POLICY, + )?) + .as_str() + .try_into() + .unwrap(), + username: sled_get_option(&self.db_tree, LOCATION_USERNAME)? + .map(|v| bytes_to_string(&v)), + password: sled_get_option(&self.db_tree, LOCATION_PASSWORD)? + .map(|v| bytes_to_string(&v)), + })) + } + } + + pub(crate) fn set_global_data( + &mut self, + data: HashMap>, + ) -> Result<(), BpfmanError> { + data.iter().try_for_each(|(k, v)| { + sled_insert( + &self.db_tree, + format!("{PREFIX_GLOBAL_DATA}{k}").as_str(), + v, + ) + }) + } + + pub fn get_global_data(&self) -> Result>, BpfmanError> { + self.db_tree + .scan_prefix(PREFIX_GLOBAL_DATA) + .map(|n| { + n.map(|(k, v)| { + ( + bytes_to_string(&k) + .strip_prefix(PREFIX_GLOBAL_DATA) + .unwrap() + .to_string(), + v.to_vec(), + ) + }) + }) + .map(|n| { + n.map_err(|e| { + BpfmanError::DatabaseError( + "Failed to get global data".to_string(), + e.to_string(), + ) + }) + }) + .collect() + } + + pub(crate) fn set_metadata( + &mut self, + data: HashMap, + ) -> Result<(), BpfmanError> { + data.iter().try_for_each(|(k, v)| { + sled_insert( + &self.db_tree, + format!("{PREFIX_METADATA}{k}").as_str(), + v.as_bytes(), + ) + }) + } + + pub fn get_metadata(&self) -> Result, BpfmanError> { + self.db_tree + .scan_prefix(PREFIX_METADATA) + .map(|n| { + n.map(|(k, v)| { + ( + bytes_to_string(&k) + .strip_prefix(PREFIX_METADATA) + .unwrap() + .to_string(), + bytes_to_string(&v).to_string(), + ) + }) + }) + .map(|n| { + n.map_err(|e| { + BpfmanError::DatabaseError("Failed to get metadata".to_string(), e.to_string()) + }) + }) + .collect() + } + + pub(crate) fn set_map_owner_id(&mut self, id: u32) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, MAP_OWNER_ID, &id.to_ne_bytes()) + } + + pub fn get_map_owner_id(&self) -> Result, BpfmanError> { + sled_get_option(&self.db_tree, MAP_OWNER_ID).map(|v| v.map(bytes_to_u32)) + } + + pub(crate) fn set_map_pin_path(&mut self, path: &Path) -> Result<(), BpfmanError> { + sled_insert( + &self.db_tree, + MAP_PIN_PATH, + path.to_str().unwrap().as_bytes(), + ) + } + + pub fn get_map_pin_path(&self) -> Result, BpfmanError> { + sled_get_option(&self.db_tree, MAP_PIN_PATH) + .map(|v| v.map(|f| PathBuf::from(bytes_to_string(&f)))) + } + + // set_maps_used_by differs from other setters in that it's explicitly idempotent. + pub(crate) fn set_maps_used_by(&mut self, ids: Vec) -> Result<(), BpfmanError> { + self.clear_maps_used_by(); + + ids.iter().enumerate().try_for_each(|(i, v)| { + sled_insert( + &self.db_tree, + format!("{PREFIX_MAPS_USED_BY}{i}").as_str(), + &v.to_ne_bytes(), + ) + }) + } + + pub fn get_maps_used_by(&self) -> Result, BpfmanError> { + self.db_tree + .scan_prefix(PREFIX_MAPS_USED_BY) + .map(|n| n.map(|(_, v)| bytes_to_u32(v.to_vec()))) + .map(|n| { + n.map_err(|e| { + BpfmanError::DatabaseError( + "Failed to get maps used by".to_string(), + e.to_string(), + ) + }) + }) + .collect() + } + + pub(crate) fn clear_maps_used_by(&self) { + self.db_tree.scan_prefix(PREFIX_MAPS_USED_BY).for_each(|n| { + self.db_tree + .remove(n.unwrap().0) + .expect("unable to clear maps used by"); + }); + } + + pub(crate) fn get_program_bytes(&self) -> Result, BpfmanError> { + sled_get(&self.db_tree, PROGRAM_BYTES) + } + + pub(crate) async fn set_program_bytes( + &mut self, + root_db: &Db, + image_manager: &mut ImageManager, + ) -> Result<(), BpfmanError> { + let loc = self.get_location()?; + match loc.get_program_bytes(root_db, image_manager).await { + Err(e) => Err(e), + Ok((v, s)) => { + match loc { + Location::Image(l) => { + info!( + "Loading program bytecode from container image: {}", + l.get_url() + ); + // If program name isn't provided and we're loading from a container + // image use the program name provided in the image metadata, otherwise + // always use the provided program name. + let provided_name = self.get_name()?.clone(); + + if provided_name.is_empty() { + self.set_name(&s)?; + } else if s != provided_name { + return Err(BpfmanError::BytecodeMetaDataMismatch { + image_prog_name: s, + provided_prog_name: provided_name.to_string(), + }); + } + } + Location::File(l) => { + info!("Loading program bytecode from file: {}", l); + } + } + sled_insert(&self.db_tree, PROGRAM_BYTES, &v)?; + Ok(()) + } + } + } + + /* + * End bpfman program info getters/setters. + */ + + /* + * Methods for setting and getting kernel information. + */ + + pub fn get_kernel_name(&self) -> Result { + sled_get(&self.db_tree, KERNEL_NAME).map(|n| bytes_to_string(&n)) + } + + pub(crate) fn set_kernel_name(&mut self, name: &str) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, KERNEL_NAME, name.as_bytes()) + } + + pub fn get_kernel_program_type(&self) -> Result { + sled_get(&self.db_tree, KERNEL_PROGRAM_TYPE).map(bytes_to_u32) + } + + pub(crate) fn set_kernel_program_type(&mut self, program_type: u32) -> Result<(), BpfmanError> { + sled_insert( + &self.db_tree, + KERNEL_PROGRAM_TYPE, + &program_type.to_ne_bytes(), + ) + } + + pub fn get_kernel_loaded_at(&self) -> Result { + sled_get(&self.db_tree, KERNEL_LOADED_AT).map(|n| bytes_to_string(&n)) + } + + pub(crate) fn set_kernel_loaded_at( + &mut self, + loaded_at: SystemTime, + ) -> Result<(), BpfmanError> { + sled_insert( + &self.db_tree, + KERNEL_LOADED_AT, + DateTime::::from(loaded_at) + .format("%Y-%m-%dT%H:%M:%S%z") + .to_string() + .as_bytes(), + ) + } + + pub fn get_kernel_tag(&self) -> Result { + sled_get(&self.db_tree, KERNEL_TAG).map(|n| bytes_to_string(&n)) + } + + pub(crate) fn set_kernel_tag(&mut self, tag: u64) -> Result<(), BpfmanError> { + sled_insert( + &self.db_tree, + KERNEL_TAG, + format!("{:x}", tag).as_str().as_bytes(), + ) + } + + pub(crate) fn set_kernel_gpl_compatible( + &mut self, + gpl_compatible: bool, + ) -> Result<(), BpfmanError> { + sled_insert( + &self.db_tree, + KERNEL_GPL_COMPATIBLE, + &(gpl_compatible as i8 % 2).to_ne_bytes(), + ) + } + + pub fn get_kernel_gpl_compatible(&self) -> Result { + sled_get(&self.db_tree, KERNEL_GPL_COMPATIBLE).map(bytes_to_bool) + } + + pub fn get_kernel_map_ids(&self) -> Result, BpfmanError> { + self.db_tree + .scan_prefix(PREFIX_KERNEL_MAP_IDS.as_bytes()) + .map(|n| n.map(|(_, v)| bytes_to_u32(v.to_vec()))) + .map(|n| { + n.map_err(|e| { + BpfmanError::DatabaseError("Failed to get map ids".to_string(), e.to_string()) + }) + }) + .collect() + } + + pub(crate) fn set_kernel_map_ids(&mut self, map_ids: Vec) -> Result<(), BpfmanError> { + let map_ids = map_ids.iter().map(|i| i.to_ne_bytes()).collect::>(); + + map_ids.iter().enumerate().try_for_each(|(i, v)| { + sled_insert( + &self.db_tree, + format!("{PREFIX_KERNEL_MAP_IDS}{i}").as_str(), + v, + ) + }) + } + + pub fn get_kernel_btf_id(&self) -> Result { + sled_get(&self.db_tree, KERNEL_BTF_ID).map(bytes_to_u32) + } + + pub(crate) fn set_kernel_btf_id(&mut self, btf_id: u32) -> Result<(), BpfmanError> { + sled_insert(&self.db_tree, KERNEL_BTF_ID, &btf_id.to_ne_bytes()) + } + + pub fn get_kernel_bytes_xlated(&self) -> Result { + sled_get(&self.db_tree, KERNEL_BYTES_XLATED).map(bytes_to_u32) + } + + pub(crate) fn set_kernel_bytes_xlated(&mut self, bytes_xlated: u32) -> Result<(), BpfmanError> { + sled_insert( + &self.db_tree, + KERNEL_BYTES_XLATED, + &bytes_xlated.to_ne_bytes(), + ) + } + + pub fn get_kernel_jited(&self) -> Result { + sled_get(&self.db_tree, KERNEL_JITED).map(bytes_to_bool) + } + + pub(crate) fn set_kernel_jited(&mut self, jited: bool) -> Result<(), BpfmanError> { + sled_insert( + &self.db_tree, + KERNEL_JITED, + &(jited as i8 % 2).to_ne_bytes(), + ) + } + + pub fn get_kernel_bytes_jited(&self) -> Result { + sled_get(&self.db_tree, KERNEL_BYTES_JITED).map(bytes_to_u32) + } + + pub(crate) fn set_kernel_bytes_jited(&mut self, bytes_jited: u32) -> Result<(), BpfmanError> { + sled_insert( + &self.db_tree, + KERNEL_BYTES_JITED, + &bytes_jited.to_ne_bytes(), + ) + } + + pub fn get_kernel_bytes_memlock(&self) -> Result { + sled_get(&self.db_tree, KERNEL_BYTES_MEMLOCK).map(bytes_to_u32) + } + + pub(crate) fn set_kernel_bytes_memlock( + &mut self, + bytes_memlock: u32, + ) -> Result<(), BpfmanError> { + sled_insert( + &self.db_tree, + KERNEL_BYTES_MEMLOCK, + &bytes_memlock.to_ne_bytes(), + ) + } + + pub fn get_kernel_verified_insns(&self) -> Result { + sled_get(&self.db_tree, KERNEL_VERIFIED_INSNS).map(bytes_to_u32) + } + + pub(crate) fn set_kernel_verified_insns( + &mut self, + verified_insns: u32, + ) -> Result<(), BpfmanError> { + sled_insert( + &self.db_tree, + KERNEL_VERIFIED_INSNS, + &verified_insns.to_ne_bytes(), + ) + } + + pub(crate) fn set_kernel_info(&mut self, prog: &AyaProgInfo) -> Result<(), BpfmanError> { + self.set_id(prog.id())?; + self.set_kernel_name( + prog.name_as_str() + .expect("Program name is not valid unicode"), + )?; + self.set_kernel_program_type(prog.program_type())?; + self.set_kernel_loaded_at(prog.loaded_at())?; + self.set_kernel_tag(prog.tag())?; + self.set_kernel_gpl_compatible(prog.gpl_compatible())?; + self.set_kernel_btf_id(prog.btf_id().map_or(0, |n| n.into()))?; + self.set_kernel_bytes_xlated(prog.size_translated())?; + self.set_kernel_jited(prog.size_jitted() != 0)?; + self.set_kernel_bytes_jited(prog.size_jitted())?; + self.set_kernel_verified_insns(prog.verified_instruction_count())?; + // Ignore errors here since it's possible the program was deleted mid + // list, causing aya apis which make system calls using the file descriptor + // to fail. + if let Ok(ids) = prog.map_ids() { + self.set_kernel_map_ids(ids)?; + } + if let Ok(bytes_memlock) = prog.memory_locked() { + self.set_kernel_bytes_memlock(bytes_memlock)?; + } + + Ok(()) + } + + /* + * End kernel info getters/setters. + */ +} + +#[derive(Debug, Clone)] +pub struct XdpProgram { + data: ProgramData, +} + +impl XdpProgram { + pub fn new( + data: ProgramData, + priority: i32, + iface: String, + proceed_on: XdpProceedOn, + ) -> Result { + let mut xdp_prog = Self { data }; + + xdp_prog.set_priority(priority)?; + xdp_prog.set_iface(iface)?; + xdp_prog.set_proceed_on(proceed_on)?; + xdp_prog.get_data_mut().set_kind(ProgramType::Xdp)?; + + Ok(xdp_prog) + } + + pub(crate) fn set_priority(&mut self, priority: i32) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, XDP_PRIORITY, &priority.to_ne_bytes()) + } + + pub fn get_priority(&self) -> Result { + sled_get(&self.data.db_tree, XDP_PRIORITY).map(bytes_to_i32) + } + + pub(crate) fn set_iface(&mut self, iface: String) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, XDP_IFACE, iface.as_bytes()) + } + + pub fn get_iface(&self) -> Result { + sled_get(&self.data.db_tree, XDP_IFACE).map(|v| bytes_to_string(&v)) + } + + pub(crate) fn set_proceed_on(&mut self, proceed_on: XdpProceedOn) -> Result<(), BpfmanError> { + proceed_on + .as_action_vec() + .iter() + .enumerate() + .try_for_each(|(i, v)| { + sled_insert( + &self.data.db_tree, + format!("{PREFIX_XDP_PROCEED_ON}{i}").as_str(), + &v.to_ne_bytes(), + ) + }) + } + + pub fn get_proceed_on(&self) -> Result { + self.data + .db_tree + .scan_prefix(PREFIX_XDP_PROCEED_ON) + .map(|n| { + n.map(|(_, v)| XdpProceedOnEntry::try_from(bytes_to_i32(v.to_vec()))) + .unwrap() + }) + .map(|n| { + n.map_err(|e| { + BpfmanError::DatabaseError( + "Failed to get proceed on".to_string(), + e.to_string(), + ) + }) + }) + .collect() + } + + pub(crate) fn set_current_position(&mut self, pos: usize) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, XDP_CURRENT_POSITION, &pos.to_ne_bytes()) + } + + pub fn get_current_position(&self) -> Result, BpfmanError> { + Ok(sled_get_option(&self.data.db_tree, XDP_CURRENT_POSITION)?.map(bytes_to_usize)) + } + + pub(crate) fn set_if_index(&mut self, if_index: u32) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, XDP_IF_INDEX, &if_index.to_ne_bytes()) + } + + pub fn get_if_index(&self) -> Result, BpfmanError> { + Ok(sled_get_option(&self.data.db_tree, XDP_IF_INDEX)?.map(bytes_to_u32)) + } + + pub(crate) fn set_attached(&mut self, attached: bool) -> Result<(), BpfmanError> { + sled_insert( + &self.data.db_tree, + XDP_ATTACHED, + &(attached as i8).to_ne_bytes(), + ) + } + + pub fn get_attached(&self) -> Result { + Ok(sled_get_option(&self.data.db_tree, XDP_ATTACHED)? + .map(bytes_to_bool) + .unwrap_or(false)) + } + + pub(crate) fn get_data(&self) -> &ProgramData { + &self.data + } + + pub(crate) fn get_data_mut(&mut self) -> &mut ProgramData { + &mut self.data + } +} + +#[derive(Debug, Clone)] +pub struct TcProgram { + pub(crate) data: ProgramData, +} + +impl TcProgram { + pub fn new( + data: ProgramData, + priority: i32, + iface: String, + proceed_on: TcProceedOn, + direction: Direction, + ) -> Result { + let mut tc_prog = Self { data }; + + tc_prog.set_priority(priority)?; + tc_prog.set_iface(iface)?; + tc_prog.set_proceed_on(proceed_on)?; + tc_prog.set_direction(direction)?; + tc_prog.get_data_mut().set_kind(ProgramType::Tc)?; + + Ok(tc_prog) + } + + pub(crate) fn set_priority(&mut self, priority: i32) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, TC_PRIORITY, &priority.to_ne_bytes()) + } + + pub fn get_priority(&self) -> Result { + sled_get(&self.data.db_tree, TC_PRIORITY).map(bytes_to_i32) + } + + pub(crate) fn set_iface(&mut self, iface: String) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, TC_IFACE, iface.as_bytes()) + } + + pub fn get_iface(&self) -> Result { + sled_get(&self.data.db_tree, TC_IFACE).map(|v| bytes_to_string(&v)) + } + + pub(crate) fn set_proceed_on(&mut self, proceed_on: TcProceedOn) -> Result<(), BpfmanError> { + proceed_on + .as_action_vec() + .iter() + .enumerate() + .try_for_each(|(i, v)| { + sled_insert( + &self.data.db_tree, + format!("{PREFIX_TC_PROCEED_ON}{i}").as_str(), + &v.to_ne_bytes(), + ) + }) + } + + pub fn get_proceed_on(&self) -> Result { + self.data + .db_tree + .scan_prefix(PREFIX_TC_PROCEED_ON) + .map(|n| n.map(|(_, v)| TcProceedOnEntry::try_from(bytes_to_i32(v.to_vec())).unwrap())) + .map(|n| { + n.map_err(|e| { + BpfmanError::DatabaseError( + "Failed to get proceed on".to_string(), + e.to_string(), + ) + }) + }) + .collect() + } + + pub(crate) fn set_current_position(&mut self, pos: usize) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, TC_CURRENT_POSITION, &pos.to_ne_bytes()) + } + + pub fn get_current_position(&self) -> Result, BpfmanError> { + Ok(sled_get_option(&self.data.db_tree, TC_CURRENT_POSITION)?.map(bytes_to_usize)) + } + + pub(crate) fn set_if_index(&mut self, if_index: u32) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, TC_IF_INDEX, &if_index.to_ne_bytes()) + } + + pub fn get_if_index(&self) -> Result, BpfmanError> { + Ok(sled_get_option(&self.data.db_tree, TC_IF_INDEX)?.map(bytes_to_u32)) + } + + pub(crate) fn set_attached(&mut self, attached: bool) -> Result<(), BpfmanError> { + sled_insert( + &self.data.db_tree, + TC_ATTACHED, + &(attached as i8).to_ne_bytes(), + ) + } + + pub fn get_attached(&self) -> Result { + Ok(sled_get_option(&self.data.db_tree, TC_ATTACHED)? + .map(bytes_to_bool) + .unwrap_or(false)) + } + + pub(crate) fn set_direction(&mut self, direction: Direction) -> Result<(), BpfmanError> { + sled_insert( + &self.data.db_tree, + TC_DIRECTION, + direction.to_string().as_bytes(), + ) + } + + pub fn get_direction(&self) -> Result { + sled_get(&self.data.db_tree, TC_DIRECTION) + .map(|v| bytes_to_string(&v).to_string().try_into().unwrap()) + } + + pub(crate) fn get_data(&self) -> &ProgramData { + &self.data + } + + pub(crate) fn get_data_mut(&mut self) -> &mut ProgramData { + &mut self.data + } +} + +#[derive(Debug, Clone)] +pub struct TracepointProgram { + pub(crate) data: ProgramData, +} + +impl TracepointProgram { + pub fn new(data: ProgramData, tracepoint: String) -> Result { + let mut tp_prog = Self { data }; + tp_prog.set_tracepoint(tracepoint)?; + tp_prog.get_data_mut().set_kind(ProgramType::Tracepoint)?; + + Ok(tp_prog) + } + + pub(crate) fn set_tracepoint(&mut self, tracepoint: String) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, TRACEPOINT_NAME, tracepoint.as_bytes()) + } + + pub fn get_tracepoint(&self) -> Result { + sled_get(&self.data.db_tree, TRACEPOINT_NAME).map(|v| bytes_to_string(&v)) + } + + pub(crate) fn get_data(&self) -> &ProgramData { + &self.data + } + + pub(crate) fn get_data_mut(&mut self) -> &mut ProgramData { + &mut self.data + } +} + +#[derive(Debug, Clone)] +pub struct KprobeProgram { + pub(crate) data: ProgramData, +} + +impl KprobeProgram { + pub fn new( + data: ProgramData, + fn_name: String, + offset: u64, + retprobe: bool, + container_pid: Option, + ) -> Result { + let mut kprobe_prog = Self { data }; + kprobe_prog.set_fn_name(fn_name)?; + kprobe_prog.set_offset(offset)?; + kprobe_prog.set_retprobe(retprobe)?; + kprobe_prog.get_data_mut().set_kind(ProgramType::Probe)?; + if container_pid.is_some() { + kprobe_prog.set_container_pid(container_pid.unwrap())?; + } + Ok(kprobe_prog) + } + + pub(crate) fn set_fn_name(&mut self, fn_name: String) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, KPROBE_FN_NAME, fn_name.as_bytes()) + } + + pub fn get_fn_name(&self) -> Result { + sled_get(&self.data.db_tree, KPROBE_FN_NAME).map(|v| bytes_to_string(&v)) + } + + pub(crate) fn set_offset(&mut self, offset: u64) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, KPROBE_OFFSET, &offset.to_ne_bytes()) + } + + pub fn get_offset(&self) -> Result { + sled_get(&self.data.db_tree, KPROBE_OFFSET).map(bytes_to_u64) + } + + pub(crate) fn set_retprobe(&mut self, retprobe: bool) -> Result<(), BpfmanError> { + sled_insert( + &self.data.db_tree, + KPROBE_RETPROBE, + &(retprobe as i8 % 2).to_ne_bytes(), + ) + } + + pub fn get_retprobe(&self) -> Result { + Ok(sled_get_option(&self.data.db_tree, KPROBE_RETPROBE)? + .map(bytes_to_bool) + .unwrap_or(false)) + } + + pub(crate) fn set_container_pid(&mut self, container_pid: i32) -> Result<(), BpfmanError> { + sled_insert( + &self.data.db_tree, + KPROBE_CONTAINER_PID, + &container_pid.to_ne_bytes(), + ) + } + + pub fn get_container_pid(&self) -> Result, BpfmanError> { + Ok(sled_get_option(&self.data.db_tree, KPROBE_CONTAINER_PID)?.map(bytes_to_i32)) + } + + pub(crate) fn get_data(&self) -> &ProgramData { + &self.data + } + + pub(crate) fn get_data_mut(&mut self) -> &mut ProgramData { + &mut self.data + } +} + +#[derive(Debug, Clone)] +pub struct UprobeProgram { + pub(crate) data: ProgramData, +} + +impl UprobeProgram { + pub fn new( + data: ProgramData, + fn_name: Option, + offset: u64, + target: String, + retprobe: bool, + pid: Option, + container_pid: Option, + ) -> Result { + let mut uprobe_prog = Self { data }; + + if fn_name.is_some() { + uprobe_prog.set_fn_name(fn_name.unwrap())?; + } + + uprobe_prog.set_offset(offset)?; + uprobe_prog.set_retprobe(retprobe)?; + if let Some(p) = container_pid { + uprobe_prog.set_container_pid(p)?; + } + if let Some(p) = pid { + uprobe_prog.set_pid(p)?; + } + uprobe_prog.set_target(target)?; + uprobe_prog.get_data_mut().set_kind(ProgramType::Probe)?; + Ok(uprobe_prog) + } + + pub(crate) fn set_fn_name(&mut self, fn_name: String) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, UPROBE_FN_NAME, fn_name.as_bytes()) + } + + pub fn get_fn_name(&self) -> Result, BpfmanError> { + Ok(sled_get_option(&self.data.db_tree, UPROBE_FN_NAME)?.map(|v| bytes_to_string(&v))) + } + + pub(crate) fn set_offset(&mut self, offset: u64) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, UPROBE_OFFSET, &offset.to_ne_bytes()) + } + + pub fn get_offset(&self) -> Result { + sled_get(&self.data.db_tree, UPROBE_OFFSET).map(bytes_to_u64) + } + + pub(crate) fn set_retprobe(&mut self, retprobe: bool) -> Result<(), BpfmanError> { + sled_insert( + &self.data.db_tree, + UPROBE_RETPROBE, + &(retprobe as i8 % 2).to_ne_bytes(), + ) + } + + pub fn get_retprobe(&self) -> Result { + Ok(sled_get_option(&self.data.db_tree, UPROBE_RETPROBE)? + .map(bytes_to_bool) + .unwrap_or(false)) + } + + pub(crate) fn set_container_pid(&mut self, container_pid: i32) -> Result<(), BpfmanError> { + sled_insert( + &self.data.db_tree, + UPROBE_CONTAINER_PID, + &container_pid.to_ne_bytes(), + ) + } + + pub fn get_container_pid(&self) -> Result, BpfmanError> { + Ok(sled_get_option(&self.data.db_tree, UPROBE_CONTAINER_PID)?.map(bytes_to_i32)) + } + + pub(crate) fn set_pid(&mut self, pid: i32) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, UPROBE_PID, &pid.to_ne_bytes()) + } + + pub fn get_pid(&self) -> Result, BpfmanError> { + Ok(sled_get_option(&self.data.db_tree, UPROBE_PID)?.map(bytes_to_i32)) + } + + pub(crate) fn set_target(&mut self, target: String) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, UPROBE_TARGET, target.as_bytes()) + } + + pub fn get_target(&self) -> Result { + sled_get(&self.data.db_tree, UPROBE_TARGET).map(|v| bytes_to_string(&v)) + } + + pub(crate) fn get_data(&self) -> &ProgramData { + &self.data + } + + pub(crate) fn get_data_mut(&mut self) -> &mut ProgramData { + &mut self.data + } +} + +#[derive(Debug, Clone)] +pub struct FentryProgram { + pub(crate) data: ProgramData, +} + +impl FentryProgram { + pub fn new(data: ProgramData, fn_name: String) -> Result { + let mut fentry_prog = Self { data }; + fentry_prog.set_fn_name(fn_name)?; + fentry_prog.get_data_mut().set_kind(ProgramType::Tracing)?; + + Ok(fentry_prog) + } + + pub(crate) fn set_fn_name(&mut self, fn_name: String) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, FENTRY_FN_NAME, fn_name.as_bytes()) + } + + pub fn get_fn_name(&self) -> Result { + sled_get(&self.data.db_tree, FENTRY_FN_NAME).map(|v| bytes_to_string(&v)) + } + + pub(crate) fn get_data(&self) -> &ProgramData { + &self.data + } + + pub(crate) fn get_data_mut(&mut self) -> &mut ProgramData { + &mut self.data + } +} + +#[derive(Debug, Clone)] +pub struct FexitProgram { + pub(crate) data: ProgramData, +} + +impl FexitProgram { + pub fn new(data: ProgramData, fn_name: String) -> Result { + let mut fexit_prog = Self { data }; + fexit_prog.set_fn_name(fn_name)?; + fexit_prog.get_data_mut().set_kind(ProgramType::Tracing)?; + + Ok(fexit_prog) + } + + pub(crate) fn set_fn_name(&mut self, fn_name: String) -> Result<(), BpfmanError> { + sled_insert(&self.data.db_tree, FEXIT_FN_NAME, fn_name.as_bytes()) + } + + pub fn get_fn_name(&self) -> Result { + sled_get(&self.data.db_tree, FEXIT_FN_NAME).map(|v| bytes_to_string(&v)) + } + + pub(crate) fn get_data(&self) -> &ProgramData { + &self.data + } + + pub(crate) fn get_data_mut(&mut self) -> &mut ProgramData { + &mut self.data + } +} + +impl Program { + pub fn kind(&self) -> ProgramType { + match self { + Program::Xdp(_) => ProgramType::Xdp, + Program::Tc(_) => ProgramType::Tc, + Program::Tracepoint(_) => ProgramType::Tracepoint, + Program::Kprobe(_) => ProgramType::Probe, + Program::Uprobe(_) => ProgramType::Probe, + Program::Fentry(_) => ProgramType::Tracing, + Program::Fexit(_) => ProgramType::Tracing, + Program::Unsupported(i) => i.get_kernel_program_type().unwrap().try_into().unwrap(), + } + } + + pub(crate) fn dispatcher_id(&self) -> Result, BpfmanError> { + Ok(match self { + Program::Xdp(p) => Some(DispatcherId::Xdp(DispatcherInfo( + p.get_if_index()? + .expect("if_index should be known at this point"), + None, + ))), + Program::Tc(p) => Some(DispatcherId::Tc(DispatcherInfo( + p.get_if_index()? + .expect("if_index should be known at this point"), + Some(p.get_direction()?), + ))), + _ => None, + }) + } + + pub(crate) fn get_data_mut(&mut self) -> &mut ProgramData { + match self { + Program::Xdp(p) => &mut p.data, + Program::Tracepoint(p) => &mut p.data, + Program::Tc(p) => &mut p.data, + Program::Kprobe(p) => &mut p.data, + Program::Uprobe(p) => &mut p.data, + Program::Fentry(p) => &mut p.data, + Program::Fexit(p) => &mut p.data, + Program::Unsupported(p) => p, + } + } + + pub(crate) fn attached(&self) -> bool { + match self { + Program::Xdp(p) => p.get_attached().unwrap(), + Program::Tc(p) => p.get_attached().unwrap(), + _ => false, + } + } + + pub(crate) fn set_attached(&mut self) { + match self { + Program::Xdp(p) => p.set_attached(true).unwrap(), + Program::Tc(p) => p.set_attached(true).unwrap(), + _ => (), + }; + } + + pub(crate) fn set_position(&mut self, pos: usize) -> Result<(), BpfmanError> { + match self { + Program::Xdp(p) => p.set_current_position(pos), + Program::Tc(p) => p.set_current_position(pos), + _ => Err(BpfmanError::Error( + "cannot set position on programs other than TC or XDP".to_string(), + )), + } + } + + pub(crate) fn delete(&self, root_db: &Db) -> Result<(), anyhow::Error> { + let id = self.get_data().get_id()?; + root_db.drop_tree(self.get_data().db_tree.name())?; + + let path = format!("{RTDIR_FS}/prog_{id}"); + if PathBuf::from(&path).exists() { + fs::remove_file(path)?; + } + let path = format!("{RTDIR_FS}/prog_{id}_link"); + if PathBuf::from(&path).exists() { + fs::remove_file(path)?; + } + Ok(()) + } + + pub(crate) fn if_index(&self) -> Result, BpfmanError> { + match self { + Program::Xdp(p) => p.get_if_index(), + Program::Tc(p) => p.get_if_index(), + _ => Err(BpfmanError::Error( + "cannot get if_index on programs other than TC or XDP".to_string(), + )), + } + } + + pub(crate) fn set_if_index(&mut self, if_index: u32) -> Result<(), BpfmanError> { + match self { + Program::Xdp(p) => p.set_if_index(if_index), + Program::Tc(p) => p.set_if_index(if_index), + _ => Err(BpfmanError::Error( + "cannot set if_index on programs other than TC or XDP".to_string(), + )), + } + } + + pub(crate) fn if_name(&self) -> Result { + match self { + Program::Xdp(p) => p.get_iface(), + Program::Tc(p) => p.get_iface(), + _ => Err(BpfmanError::Error( + "cannot get interface on programs other than TC or XDP".to_string(), + )), + } + } + + pub(crate) fn priority(&self) -> Result { + match self { + Program::Xdp(p) => p.get_priority(), + Program::Tc(p) => p.get_priority(), + _ => Err(BpfmanError::Error( + "cannot get priority on programs other than TC or XDP".to_string(), + )), + } + } + + pub(crate) fn direction(&self) -> Result, BpfmanError> { + match self { + Program::Tc(p) => Ok(Some(p.get_direction()?)), + _ => Ok(None), + } + } + + pub fn get_data(&self) -> &ProgramData { + match self { + Program::Xdp(p) => p.get_data(), + Program::Tracepoint(p) => p.get_data(), + Program::Tc(p) => p.get_data(), + Program::Kprobe(p) => p.get_data(), + Program::Uprobe(p) => p.get_data(), + Program::Fentry(p) => p.get_data(), + Program::Fexit(p) => p.get_data(), + Program::Unsupported(p) => p, + } + } + + pub(crate) fn new_from_db(id: u32, tree: sled::Tree) -> Result { + let data = ProgramData::new_empty(tree); + + if data.get_id()? != id { + return Err(BpfmanError::Error( + "Program id does not match database id program isn't fully loaded".to_string(), + )); + } + match data.get_kind()? { + Some(p) => match p { + ProgramType::Xdp => Ok(Program::Xdp(XdpProgram { data })), + ProgramType::Tc => Ok(Program::Tc(TcProgram { data })), + ProgramType::Tracepoint => Ok(Program::Tracepoint(TracepointProgram { data })), + // kernel does not distinguish between kprobe and uprobe program types + ProgramType::Probe => { + if data.db_tree.get(UPROBE_OFFSET).unwrap().is_some() { + Ok(Program::Uprobe(UprobeProgram { data })) + } else { + Ok(Program::Kprobe(KprobeProgram { data })) + } + } + // kernel does not distinguish between fentry and fexit program types + ProgramType::Tracing => { + if data.db_tree.get(FENTRY_FN_NAME).unwrap().is_some() { + Ok(Program::Fentry(FentryProgram { data })) + } else { + Ok(Program::Fexit(FexitProgram { data })) + } + } + _ => Err(BpfmanError::Error("Unsupported program type".to_string())), + }, + None => Err(BpfmanError::Error("Unsupported program type".to_string())), + } + } +} + +#[derive(ValueEnum, Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +pub enum ProgramType { + Unspec, + SocketFilter, + Probe, // kprobe, kretprobe, uprobe, uretprobe + Tc, + SchedAct, + Tracepoint, + Xdp, + PerfEvent, + CgroupSkb, + CgroupSock, + LwtIn, + LwtOut, + LwtXmit, + SockOps, + SkSkb, + CgroupDevice, + SkMsg, + RawTracepoint, + CgroupSockAddr, + LwtSeg6Local, + LircMode2, + SkReuseport, + FlowDissector, + CgroupSysctl, + RawTracepointWritable, + CgroupSockopt, + Tracing, // fentry, fexit + StructOps, + Ext, + Lsm, + SkLookup, + Syscall, +} + +impl TryFrom for ProgramType { + type Error = ParseError; + + fn try_from(value: String) -> Result { + Ok(match value.as_str() { + "unspec" => ProgramType::Unspec, + "socket_filter" => ProgramType::SocketFilter, + "probe" => ProgramType::Probe, + "tc" => ProgramType::Tc, + "sched_act" => ProgramType::SchedAct, + "tracepoint" => ProgramType::Tracepoint, + "xdp" => ProgramType::Xdp, + "perf_event" => ProgramType::PerfEvent, + "cgroup_skb" => ProgramType::CgroupSkb, + "cgroup_sock" => ProgramType::CgroupSock, + "lwt_in" => ProgramType::LwtIn, + "lwt_out" => ProgramType::LwtOut, + "lwt_xmit" => ProgramType::LwtXmit, + "sock_ops" => ProgramType::SockOps, + "sk_skb" => ProgramType::SkSkb, + "cgroup_device" => ProgramType::CgroupDevice, + "sk_msg" => ProgramType::SkMsg, + "raw_tracepoint" => ProgramType::RawTracepoint, + "cgroup_sock_addr" => ProgramType::CgroupSockAddr, + "lwt_seg6local" => ProgramType::LwtSeg6Local, + "lirc_mode2" => ProgramType::LircMode2, + "sk_reuseport" => ProgramType::SkReuseport, + "flow_dissector" => ProgramType::FlowDissector, + "cgroup_sysctl" => ProgramType::CgroupSysctl, + "raw_tracepoint_writable" => ProgramType::RawTracepointWritable, + "cgroup_sockopt" => ProgramType::CgroupSockopt, + "tracing" => ProgramType::Tracing, + "struct_ops" => ProgramType::StructOps, + "ext" => ProgramType::Ext, + "lsm" => ProgramType::Lsm, + "sk_lookup" => ProgramType::SkLookup, + "syscall" => ProgramType::Syscall, + other => { + return Err(ParseError::InvalidProgramType { + program: other.to_string(), + }) + } + }) + } +} + +impl TryFrom for ProgramType { + type Error = ParseError; + + fn try_from(value: u32) -> Result { + Ok(match value { + 0 => ProgramType::Unspec, + 1 => ProgramType::SocketFilter, + 2 => ProgramType::Probe, + 3 => ProgramType::Tc, + 4 => ProgramType::SchedAct, + 5 => ProgramType::Tracepoint, + 6 => ProgramType::Xdp, + 7 => ProgramType::PerfEvent, + 8 => ProgramType::CgroupSkb, + 9 => ProgramType::CgroupSock, + 10 => ProgramType::LwtIn, + 11 => ProgramType::LwtOut, + 12 => ProgramType::LwtXmit, + 13 => ProgramType::SockOps, + 14 => ProgramType::SkSkb, + 15 => ProgramType::CgroupDevice, + 16 => ProgramType::SkMsg, + 17 => ProgramType::RawTracepoint, + 18 => ProgramType::CgroupSockAddr, + 19 => ProgramType::LwtSeg6Local, + 20 => ProgramType::LircMode2, + 21 => ProgramType::SkReuseport, + 22 => ProgramType::FlowDissector, + 23 => ProgramType::CgroupSysctl, + 24 => ProgramType::RawTracepointWritable, + 25 => ProgramType::CgroupSockopt, + 26 => ProgramType::Tracing, + 27 => ProgramType::StructOps, + 28 => ProgramType::Ext, + 29 => ProgramType::Lsm, + 30 => ProgramType::SkLookup, + 31 => ProgramType::Syscall, + other => { + return Err(ParseError::InvalidProgramType { + program: other.to_string(), + }) + } + }) + } +} + +impl From for u32 { + fn from(val: ProgramType) -> Self { + match val { + ProgramType::Unspec => 0, + ProgramType::SocketFilter => 1, + ProgramType::Probe => 2, + ProgramType::Tc => 3, + ProgramType::SchedAct => 4, + ProgramType::Tracepoint => 5, + ProgramType::Xdp => 6, + ProgramType::PerfEvent => 7, + ProgramType::CgroupSkb => 8, + ProgramType::CgroupSock => 9, + ProgramType::LwtIn => 10, + ProgramType::LwtOut => 11, + ProgramType::LwtXmit => 12, + ProgramType::SockOps => 13, + ProgramType::SkSkb => 14, + ProgramType::CgroupDevice => 15, + ProgramType::SkMsg => 16, + ProgramType::RawTracepoint => 17, + ProgramType::CgroupSockAddr => 18, + ProgramType::LwtSeg6Local => 19, + ProgramType::LircMode2 => 20, + ProgramType::SkReuseport => 21, + ProgramType::FlowDissector => 22, + ProgramType::CgroupSysctl => 23, + ProgramType::RawTracepointWritable => 24, + ProgramType::CgroupSockopt => 25, + ProgramType::Tracing => 26, + ProgramType::StructOps => 27, + ProgramType::Ext => 28, + ProgramType::Lsm => 29, + ProgramType::SkLookup => 30, + ProgramType::Syscall => 31, + } + } +} + +impl std::fmt::Display for ProgramType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let v = match self { + ProgramType::Unspec => "unspec", + ProgramType::SocketFilter => "socket_filter", + ProgramType::Probe => "probe", + ProgramType::Tc => "tc", + ProgramType::SchedAct => "sched_act", + ProgramType::Tracepoint => "tracepoint", + ProgramType::Xdp => "xdp", + ProgramType::PerfEvent => "perf_event", + ProgramType::CgroupSkb => "cgroup_skb", + ProgramType::CgroupSock => "cgroup_sock", + ProgramType::LwtIn => "lwt_in", + ProgramType::LwtOut => "lwt_out", + ProgramType::LwtXmit => "lwt_xmit", + ProgramType::SockOps => "sock_ops", + ProgramType::SkSkb => "sk_skb", + ProgramType::CgroupDevice => "cgroup_device", + ProgramType::SkMsg => "sk_msg", + ProgramType::RawTracepoint => "raw_tracepoint", + ProgramType::CgroupSockAddr => "cgroup_sock_addr", + ProgramType::LwtSeg6Local => "lwt_seg6local", + ProgramType::LircMode2 => "lirc_mode2", + ProgramType::SkReuseport => "sk_reuseport", + ProgramType::FlowDissector => "flow_dissector", + ProgramType::CgroupSysctl => "cgroup_sysctl", + ProgramType::RawTracepointWritable => "raw_tracepoint_writable", + ProgramType::CgroupSockopt => "cgroup_sockopt", + ProgramType::Tracing => "tracing", + ProgramType::StructOps => "struct_ops", + ProgramType::Ext => "ext", + ProgramType::Lsm => "lsm", + ProgramType::SkLookup => "sk_lookup", + ProgramType::Syscall => "syscall", + }; + write!(f, "{v}") + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +pub enum ProbeType { + Kprobe, + Kretprobe, + Uprobe, + Uretprobe, +} + +impl TryFrom for ProbeType { + type Error = ParseError; + + fn try_from(value: i32) -> Result { + Ok(match value { + 0 => ProbeType::Kprobe, + 1 => ProbeType::Kretprobe, + 2 => ProbeType::Uprobe, + 3 => ProbeType::Uretprobe, + other => { + return Err(ParseError::InvalidProbeType { + probe: other.to_string(), + }) + } + }) + } +} + +impl From for ProbeType { + fn from(value: aya::programs::ProbeKind) -> Self { + match value { + aya::programs::ProbeKind::KProbe => ProbeType::Kprobe, + aya::programs::ProbeKind::KRetProbe => ProbeType::Kretprobe, + aya::programs::ProbeKind::UProbe => ProbeType::Uprobe, + aya::programs::ProbeKind::URetProbe => ProbeType::Uretprobe, + } + } +} + +impl std::fmt::Display for ProbeType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let v = match self { + ProbeType::Kprobe => "kprobe", + ProbeType::Kretprobe => "kretprobe", + ProbeType::Uprobe => "uprobe", + ProbeType::Uretprobe => "uretprobe", + }; + write!(f, "{v}") + } +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +pub enum XdpProceedOnEntry { + Aborted, + Drop, + Pass, + Tx, + Redirect, + DispatcherReturn = 31, +} + +impl FromIterator for XdpProceedOn { + fn from_iter>(iter: I) -> Self { + let mut c = Vec::new(); + + let mut iter = iter.into_iter().peekable(); + + // make sure to default if proceed on is empty + if iter.peek().is_none() { + return XdpProceedOn::default(); + }; + + for i in iter { + c.push(i); + } + + XdpProceedOn(c) + } +} + +impl TryFrom for XdpProceedOnEntry { + type Error = ParseError; + fn try_from(value: String) -> Result { + Ok(match value.as_str() { + "aborted" => XdpProceedOnEntry::Aborted, + "drop" => XdpProceedOnEntry::Drop, + "pass" => XdpProceedOnEntry::Pass, + "tx" => XdpProceedOnEntry::Tx, + "redirect" => XdpProceedOnEntry::Redirect, + "dispatcher_return" => XdpProceedOnEntry::DispatcherReturn, + proceedon => { + return Err(ParseError::InvalidProceedOn { + proceedon: proceedon.to_string(), + }) + } + }) + } +} + +impl TryFrom for XdpProceedOnEntry { + type Error = ParseError; + fn try_from(value: i32) -> Result { + Ok(match value { + 0 => XdpProceedOnEntry::Aborted, + 1 => XdpProceedOnEntry::Drop, + 2 => XdpProceedOnEntry::Pass, + 3 => XdpProceedOnEntry::Tx, + 4 => XdpProceedOnEntry::Redirect, + 31 => XdpProceedOnEntry::DispatcherReturn, + proceedon => { + return Err(ParseError::InvalidProceedOn { + proceedon: proceedon.to_string(), + }) + } + }) + } +} + +impl std::fmt::Display for XdpProceedOnEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let v = match self { + XdpProceedOnEntry::Aborted => "aborted", + XdpProceedOnEntry::Drop => "drop", + XdpProceedOnEntry::Pass => "pass", + XdpProceedOnEntry::Tx => "tx", + XdpProceedOnEntry::Redirect => "redirect", + XdpProceedOnEntry::DispatcherReturn => "dispatcher_return", + }; + write!(f, "{v}") + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct XdpProceedOn(Vec); +impl Default for XdpProceedOn { + fn default() -> Self { + XdpProceedOn(vec![ + XdpProceedOnEntry::Pass, + XdpProceedOnEntry::DispatcherReturn, + ]) + } +} + +impl XdpProceedOn { + pub fn from_strings>(values: T) -> Result { + let entries = values.as_ref(); + let mut res = vec![]; + for e in entries { + res.push(e.to_owned().try_into()?) + } + Ok(XdpProceedOn(res)) + } + + pub fn from_int32s>(values: T) -> Result { + let entries = values.as_ref(); + if entries.is_empty() { + return Ok(XdpProceedOn::default()); + } + let mut res = vec![]; + for e in entries { + res.push((*e).try_into()?) + } + Ok(XdpProceedOn(res)) + } + + pub fn mask(&self) -> u32 { + let mut proceed_on_mask: u32 = 0; + for action in self.0.clone().into_iter() { + proceed_on_mask |= 1 << action as u32; + } + proceed_on_mask + } + + pub fn as_action_vec(&self) -> Vec { + let mut res = vec![]; + for entry in &self.0 { + res.push((*entry) as i32) + } + res + } +} + +impl std::fmt::Display for XdpProceedOn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let res: Vec = self.0.iter().map(|x| x.to_string()).collect(); + write!(f, "{}", res.join(", ")) + } +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +pub enum TcProceedOnEntry { + Unspec = -1, + Ok = 0, + Reclassify, + Shot, + Pipe, + Stolen, + Queued, + Repeat, + Redirect, + Trap, + DispatcherReturn = 30, +} + +impl TryFrom for TcProceedOnEntry { + type Error = ParseError; + fn try_from(value: String) -> Result { + Ok(match value.as_str() { + "unspec" => TcProceedOnEntry::Unspec, + "ok" => TcProceedOnEntry::Ok, + "reclassify" => TcProceedOnEntry::Reclassify, + "shot" => TcProceedOnEntry::Shot, + "pipe" => TcProceedOnEntry::Pipe, + "stolen" => TcProceedOnEntry::Stolen, + "queued" => TcProceedOnEntry::Queued, + "repeat" => TcProceedOnEntry::Repeat, + "redirect" => TcProceedOnEntry::Redirect, + "trap" => TcProceedOnEntry::Trap, + "dispatcher_return" => TcProceedOnEntry::DispatcherReturn, + proceedon => { + return Err(ParseError::InvalidProceedOn { + proceedon: proceedon.to_string(), + }) + } + }) + } +} + +impl TryFrom for TcProceedOnEntry { + type Error = ParseError; + fn try_from(value: i32) -> Result { + Ok(match value { + -1 => TcProceedOnEntry::Unspec, + 0 => TcProceedOnEntry::Ok, + 1 => TcProceedOnEntry::Reclassify, + 2 => TcProceedOnEntry::Shot, + 3 => TcProceedOnEntry::Pipe, + 4 => TcProceedOnEntry::Stolen, + 5 => TcProceedOnEntry::Queued, + 6 => TcProceedOnEntry::Repeat, + 7 => TcProceedOnEntry::Redirect, + 8 => TcProceedOnEntry::Trap, + 30 => TcProceedOnEntry::DispatcherReturn, + proceedon => { + return Err(ParseError::InvalidProceedOn { + proceedon: proceedon.to_string(), + }) + } + }) + } +} + +impl std::fmt::Display for TcProceedOnEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let v = match self { + TcProceedOnEntry::Unspec => "unspec", + TcProceedOnEntry::Ok => "ok", + TcProceedOnEntry::Reclassify => "reclassify", + TcProceedOnEntry::Shot => "shot", + TcProceedOnEntry::Pipe => "pipe", + TcProceedOnEntry::Stolen => "stolen", + TcProceedOnEntry::Queued => "queued", + TcProceedOnEntry::Repeat => "repeat", + TcProceedOnEntry::Redirect => "redirect", + TcProceedOnEntry::Trap => "trap", + TcProceedOnEntry::DispatcherReturn => "dispatcher_return", + }; + write!(f, "{v}") + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TcProceedOn(pub(crate) Vec); +impl Default for TcProceedOn { + fn default() -> Self { + TcProceedOn(vec![ + TcProceedOnEntry::Pipe, + TcProceedOnEntry::DispatcherReturn, + ]) + } +} + +impl FromIterator for TcProceedOn { + fn from_iter>(iter: I) -> Self { + let mut c = Vec::new(); + let mut iter = iter.into_iter().peekable(); + + // make sure to default if proceed on is empty + if iter.peek().is_none() { + return TcProceedOn::default(); + }; + + for i in iter { + c.push(i); + } + + TcProceedOn(c) + } +} + +impl TcProceedOn { + pub fn from_strings>(values: T) -> Result { + let entries = values.as_ref(); + let mut res = vec![]; + for e in entries { + res.push(e.to_owned().try_into()?) + } + Ok(TcProceedOn(res)) + } + + pub fn from_int32s>(values: T) -> Result { + let entries = values.as_ref(); + if entries.is_empty() { + return Ok(TcProceedOn::default()); + } + let mut res = vec![]; + for e in entries { + res.push((*e).try_into()?) + } + Ok(TcProceedOn(res)) + } + + // Valid TC return values range from -1 to 8. Since -1 is not a valid shift value, + // 1 is added to the value to determine the bit to set in the bitmask and, + // correspondingly, The TC dispatcher adds 1 to the return value from the BPF program + // before it compares it to the configured bit mask. + pub fn mask(&self) -> u32 { + let mut proceed_on_mask: u32 = 0; + for action in self.0.clone().into_iter() { + proceed_on_mask |= 1 << ((action as i32) + 1); + } + proceed_on_mask + } + + pub fn as_action_vec(&self) -> Vec { + let mut res = vec![]; + for entry in &self.0 { + res.push((*entry) as i32) + } + res + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl std::fmt::Display for TcProceedOn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let res: Vec = self.0.iter().map(|x| x.to_string()).collect(); + write!(f, "{}", res.join(", ")) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum ImagePullPolicy { + Always, + IfNotPresent, + Never, +} + +impl std::fmt::Display for ImagePullPolicy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let v = match self { + ImagePullPolicy::Always => "Always", + ImagePullPolicy::IfNotPresent => "IfNotPresent", + ImagePullPolicy::Never => "Never", + }; + write!(f, "{v}") + } +} + +impl TryFrom for ImagePullPolicy { + type Error = ParseError; + fn try_from(value: i32) -> Result { + Ok(match value { + 0 => ImagePullPolicy::Always, + 1 => ImagePullPolicy::IfNotPresent, + 2 => ImagePullPolicy::Never, + policy => { + return Err(ParseError::InvalidBytecodeImagePullPolicy { + pull_policy: policy.to_string(), + }) + } + }) + } +} + +impl TryFrom<&str> for ImagePullPolicy { + type Error = ParseError; + fn try_from(value: &str) -> Result { + Ok(match value { + "Always" => ImagePullPolicy::Always, + "IfNotPresent" => ImagePullPolicy::IfNotPresent, + "Never" => ImagePullPolicy::Never, + policy => { + return Err(ParseError::InvalidBytecodeImagePullPolicy { + pull_policy: policy.to_string(), + }) + } + }) + } +} + +impl From for i32 { + fn from(value: ImagePullPolicy) -> Self { + match value { + ImagePullPolicy::Always => 0, + ImagePullPolicy::IfNotPresent => 1, + ImagePullPolicy::Never => 2, + } + } +} + +impl std::fmt::Display for Location { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + // Cast imagePullPolicy into it's concrete type so we can easily print. + Location::Image(i) => write!( + f, + "image: {{ url: {}, pullpolicy: {} }}", + i.image_url, + TryInto::::try_into(i.image_pull_policy.clone()).unwrap() + ), + Location::File(p) => write!(f, "file: {{ path: {p} }}"), + } + } +} diff --git a/bpfman/src/utils.rs b/bpfman/src/utils.rs index 73cf3327b..e92bc08d0 100644 --- a/bpfman/src/utils.rs +++ b/bpfman/src/utils.rs @@ -1,52 +1,45 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright Authors of bpfman -use std::{os::unix::fs::PermissionsExt, path::Path, str}; +use std::{ + env, + fs::{create_dir_all, set_permissions, File, OpenOptions}, + io::{BufRead, BufReader, Read}, + os::unix::fs::{OpenOptionsExt, PermissionsExt}, + path::Path, + str::FromStr, +}; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use log::{debug, info, warn}; use nix::{ + libc::RLIM_INFINITY, mount::{mount, MsFlags}, net::if_::if_nametoindex, + sys::resource::{setrlimit, Resource}, }; -use tokio::{fs, io::AsyncReadExt}; +use sled::Tree; +use systemd_journal_logger::{connected_to_journal, JournalLog}; -use crate::errors::BpfmanError; +use crate::{config::Config, directories::*, errors::BpfmanError, BPFMAN_ENV_LOG_LEVEL}; // The bpfman socket should always allow the same users and members of the same group // to Read/Write to it. -pub(crate) const SOCK_MODE: u32 = 0o0660; +pub const SOCK_MODE: u32 = 0o0660; // Like tokio::fs::read, but with O_NOCTTY set -pub(crate) async fn read>(path: P) -> Result, BpfmanError> { +pub(crate) fn read>(path: P) -> Result, BpfmanError> { let mut data = vec![]; - tokio::fs::OpenOptions::new() + OpenOptions::new() .custom_flags(nix::libc::O_NOCTTY) .read(true) .open(path) - .await .map_err(|e| BpfmanError::Error(format!("can't open file: {e}")))? .read_to_end(&mut data) - .await .map_err(|e| BpfmanError::Error(format!("can't read file: {e}")))?; Ok(data) } -// Like tokio::fs::read_to_string, but with O_NOCTTY set -pub(crate) async fn read_to_string>(path: P) -> Result { - let mut buffer = String::new(); - tokio::fs::OpenOptions::new() - .custom_flags(nix::libc::O_NOCTTY) - .read(true) - .open(path) - .await - .map_err(|e| BpfmanError::Error(format!("can't open file: {e}")))? - .read_to_string(&mut buffer) - .await - .map_err(|e| BpfmanError::Error(format!("can't read file: {e}")))?; - Ok(buffer) -} - pub(crate) fn get_ifindex(iface: &str) -> Result { match if_nametoindex(iface) { Ok(index) => { @@ -60,23 +53,26 @@ pub(crate) fn get_ifindex(iface: &str) -> Result { } } -pub(crate) async fn set_file_permissions(path: &str, mode: u32) { +pub fn set_file_permissions(path: &Path, mode: u32) { // Set the permissions on the file based on input - if (tokio::fs::set_permissions(path, std::fs::Permissions::from_mode(mode)).await).is_err() { - warn!("Unable to set permissions on file {}. Continuing", path); + if (set_permissions(path, std::fs::Permissions::from_mode(mode))).is_err() { + debug!( + "Unable to set permissions on file {}. Continuing", + path.to_path_buf().display() + ); } } -pub(crate) async fn set_dir_permissions(directory: &str, mode: u32) { +pub fn set_dir_permissions(directory: &str, mode: u32) { // Iterate through the files in the provided directory - let mut entries = fs::read_dir(directory).await.unwrap(); - while let Some(file) = entries.next_entry().await.unwrap() { + let entries = std::fs::read_dir(directory).unwrap(); + for file in entries.flatten() { // Set the permissions on the file based on input - set_file_permissions(&file.path().into_os_string().into_string().unwrap(), mode).await; + set_file_permissions(&file.path(), mode); } } -pub(crate) fn create_bpffs(directory: &str) -> anyhow::Result<()> { +pub fn create_bpffs(directory: &str) -> anyhow::Result<()> { debug!("Creating bpffs at {directory}"); let flags = MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_NOEXEC | MsFlags::MS_RELATIME; mount::(None, directory, Some("bpf"), flags, None) @@ -86,3 +82,228 @@ pub(crate) fn create_bpffs(directory: &str) -> anyhow::Result<()> { pub(crate) fn should_map_be_pinned(name: &str) -> bool { !(name.contains(".rodata") || name.contains(".bss") || name.contains(".data")) } + +pub(crate) fn bytes_to_u32(bytes: Vec) -> u32 { + u32::from_ne_bytes( + bytes + .try_into() + .expect("unable to marshall &[u8] to &[u8; 4]"), + ) +} + +pub(crate) fn bytes_to_u16(bytes: Vec) -> u16 { + u16::from_ne_bytes( + bytes + .try_into() + .expect("unable to marshall &[u8] to &[u8; 4]"), + ) +} + +pub(crate) fn bytes_to_i32(bytes: Vec) -> i32 { + i32::from_ne_bytes( + bytes + .try_into() + .expect("unable to marshall &[u8] to &[u8; 4]"), + ) +} + +pub(crate) fn bytes_to_string(bytes: &[u8]) -> String { + String::from_utf8(bytes.to_vec()).expect("failed to convert &[u8] to string") +} + +pub(crate) fn bytes_to_bool(bytes: Vec) -> bool { + i8::from_ne_bytes( + bytes + .try_into() + .expect("unable to marshall &[u8] to &[i8; 1]"), + ) != 0 +} + +pub(crate) fn bytes_to_usize(bytes: Vec) -> usize { + usize::from_ne_bytes( + bytes + .try_into() + .expect("unable to marshall &[u8] to &[u8; 8]"), + ) +} + +pub(crate) fn bytes_to_u64(bytes: Vec) -> u64 { + u64::from_ne_bytes( + bytes + .try_into() + .expect("unable to marshall &[u8] to &[u8; 8]"), + ) +} + +// Sled in memory database helper functions which help with error handling and +// data marshalling. + +pub(crate) fn sled_get(db_tree: &Tree, key: &str) -> Result, BpfmanError> { + db_tree + .get(key) + .map_err(|e| { + BpfmanError::DatabaseError( + format!( + "Unable to get database entry {key} from tree {}", + bytes_to_string(&db_tree.name()) + ), + e.to_string(), + ) + })? + .map(|v| v.to_vec()) + .ok_or(BpfmanError::DatabaseError( + format!( + "Database entry {key} does not exist in tree {:?}", + bytes_to_string(&db_tree.name()) + ), + "".to_string(), + )) +} + +pub(crate) fn sled_get_option(db_tree: &Tree, key: &str) -> Result>, BpfmanError> { + db_tree + .get(key) + .map_err(|e| { + BpfmanError::DatabaseError( + format!( + "Unable to get database entry {key} from tree {}", + bytes_to_string(&db_tree.name()), + ), + e.to_string(), + ) + }) + .map(|v| v.map(|v| v.to_vec())) +} + +pub(crate) fn sled_insert(db_tree: &Tree, key: &str, value: &[u8]) -> Result<(), BpfmanError> { + db_tree.insert(key, value).map(|_| ()).map_err(|e| { + BpfmanError::DatabaseError( + format!( + "Unable to insert database entry {key} into tree {:?}", + db_tree.name() + ), + e.to_string(), + ) + }) +} + +// Helper function to get the error message from stderr +pub(crate) fn get_error_msg_from_stderr(stderr: &[u8]) -> String { + // Convert to lines + let stderr_lines = String::from_utf8_lossy(stderr) + .split('\n') + .map(|s| s.to_string()) + .collect::>(); + + // Remove empty lines + let stderr_lines = stderr_lines + .iter() + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + .collect::>(); + + // return the last line if it exists, otherwise return "No message" + stderr_lines + .last() + .unwrap_or(&"No message".to_string()) + .to_string() +} + +pub(crate) fn open_config_file() -> Config { + if let Ok(c) = std::fs::read_to_string(CFGPATH_BPFMAN_CONFIG) { + c.parse().unwrap_or_else(|_| { + warn!("Unable to parse config file, using defaults"); + Config::default() + }) + } else { + warn!("Unable to read config file, using defaults"); + Config::default() + } +} + +fn has_cap(cset: caps::CapSet, cap: caps::Capability) { + info!("Has {}: {}", cap, caps::has_cap(None, cset, cap).unwrap()); +} + +fn is_bpffs_mounted() -> Result { + let file = File::open("/proc/mounts").context("Failed to open /proc/mounts")?; + let reader = BufReader::new(file); + for l in reader.lines() { + match l { + Ok(line) => { + let parts: Vec<&str> = line.split(' ').collect(); + if parts.len() != 6 { + bail!("expected 6 parts in proc mount") + } + if parts[0] == "none" && parts[1].contains("bpfman") && parts[2] == "bpf" { + return Ok(true); + } + } + Err(e) => bail!("problem reading lines {}", e), + } + } + Ok(false) +} + +pub(crate) fn initialize_bpfman() -> anyhow::Result<()> { + if connected_to_journal() { + // If bpfman is running as a service, log to journald. + JournalLog::new()? + .with_extra_fields(vec![("VERSION", env!("CARGO_PKG_VERSION"))]) + .install() + .unwrap(); + manage_journal_log_level(); + debug!("Log using journald"); + } else { + // Ignore error if already initialized. + let _ = env_logger::try_init(); + debug!("Log using env_logger"); + } + + has_cap(caps::CapSet::Effective, caps::Capability::CAP_BPF); + has_cap(caps::CapSet::Effective, caps::Capability::CAP_SYS_ADMIN); + + setrlimit(Resource::RLIMIT_MEMLOCK, RLIM_INFINITY, RLIM_INFINITY).unwrap(); + + // Create directories associated with bpfman + create_dir_all(RTDIR).context("unable to create runtime directory")?; + create_dir_all(RTDIR_FS).context("unable to create mountpoint")?; + create_dir_all(RTDIR_TC_INGRESS_DISPATCHER).context("unable to create dispatcher directory")?; + create_dir_all(RTDIR_TC_EGRESS_DISPATCHER).context("unable to create dispatcher directory")?; + create_dir_all(RTDIR_XDP_DISPATCHER).context("unable to create dispatcher directory")?; + create_dir_all(RTDIR_PROGRAMS).context("unable to create programs directory")?; + + if !is_bpffs_mounted()? { + create_bpffs(RTDIR_FS)?; + } + create_dir_all(RTDIR_FS_XDP).context("unable to create xdp dispatcher directory")?; + create_dir_all(RTDIR_FS_TC_INGRESS) + .context("unable to create tc ingress dispatcher directory")?; + create_dir_all(RTDIR_FS_TC_EGRESS) + .context("unable to create tc egress dispatcher directory")?; + create_dir_all(RTDIR_FS_MAPS).context("unable to create maps directory")?; + create_dir_all(RTDIR_TUF).context("unable to create TUF directory")?; + + create_dir_all(STDIR).context("unable to create state directory")?; + + create_dir_all(CFGDIR_STATIC_PROGRAMS).context("unable to create static programs directory")?; + + set_dir_permissions(CFGDIR, CFGDIR_MODE); + set_dir_permissions(RTDIR, RTDIR_MODE); + set_dir_permissions(STDIR, STDIR_MODE); + + Ok(()) +} + +fn manage_journal_log_level() { + // env_logger uses the environment variable RUST_LOG to set the log + // level. Parse RUST_LOG to set the log level for journald. + log::set_max_level(log::LevelFilter::Error); + if env::var(BPFMAN_ENV_LOG_LEVEL).is_ok() { + let rust_log = log::LevelFilter::from_str(&env::var(BPFMAN_ENV_LOG_LEVEL).unwrap()); + match rust_log { + Ok(value) => log::set_max_level(value), + Err(e) => log::error!("Invalid Log Level: {}", e), + } + } +} diff --git a/changelogs/CHANGELOG-v0.3.1.md b/changelogs/CHANGELOG-v0.3.1.md index 8ccb003aa..b227d5bf5 100644 --- a/changelogs/CHANGELOG-v0.3.1.md +++ b/changelogs/CHANGELOG-v0.3.1.md @@ -48,28 +48,29 @@ Lastly, the bpfman user and user group was removed which will only effect users that run bpfman via a systemd service and try to use `bpfctl` without root privileges. This helped reduce internal complexity and allows us to focus instead on finetuning the permissions of the bpfman process itself, see the [linux -capabilities guide](https://bpfman.io/developer-guide/linux-capabilities/) for more information. +capabilities guide](https://bpfman.io/main/developer-guide/linux-capabilities/) for more information. ## What's Changed (excluding dependency bumps) -* release: automate release yamls by @astoycos in https://github.com/bpfman/bpfman/pull/775 -* bpf: returns an error when adding a tc program to existence clsact qdisc by @navarrothiago in https://github.com/bpfman/bpfman/pull/761 -* workspace-ified the netlink dependencies by @anfredette in https://github.com/bpfman/bpfman/pull/783 -* Don't try and pin .data maps by @astoycos in https://github.com/bpfman/bpfman/pull/794 -* .github: Add actions to dependabot by @dave-tucker in https://github.com/bpfman/bpfman/pull/803 -* Fix Procceedon bug (Issue #791) by @anfredette in https://github.com/bpfman/bpfman/pull/792 -* Add script to delete bpfman qdiscs on all interfaces by @anfredette in https://github.com/bpfman/bpfman/pull/780 -* Fix BPF Licensing by @dave-tucker in https://github.com/bpfman/bpfman/pull/796 -* Fix example bytecode image builds add test coverage by @astoycos in https://github.com/bpfman/bpfman/pull/810 -* Relicense userspace to Apache 2.0 only by @dave-tucker in https://github.com/bpfman/bpfman/pull/795 -* bpfman: Use tc dispatcher from container image by @dave-tucker in https://github.com/bpfman/bpfman/pull/817 -* bpfman: Unify the "run as root" and "run as bpfman user" codepaths by @Billy99 in https://github.com/bpfman/bpfman/pull/777 -* bpfman, bpfctl, operator: Remove support for TCP/TLS by @dave-tucker in https://github.com/bpfman/bpfman/pull/819 -* bpfman-operator: Make the CSI deployment default for bpfman-operator by @Billy99 in https://github.com/bpfman/bpfman/pull/811 -* ci: Add YAML formatter by @dave-tucker in https://github.com/bpfman/bpfman/pull/802 -* Fix some panics + add testing and fix for map sharing by @astoycos in https://github.com/bpfman/bpfman/pull/820 -* bpfman: mount default bpffs on kind by @astoycos in https://github.com/bpfman/bpfman/pull/823 -* bpfman: Remove unused file by @dave-tucker in https://github.com/bpfman/bpfman/pull/824 -* Document valid kernel versions by @Billy99 in https://github.com/bpfman/bpfman/pull/827 -* Update documentation on new YAML Linter by @Billy99 in https://github.com/bpfman/bpfman/pull/830 -**Full Changelog**: https://github.com/bpfman/bpfman/compare/v0.3.0...v0.3.1 +* release: automate release yamls by @astoycos in +* bpf: returns an error when adding a tc program to existence clsact qdisc by @navarrothiago in +* workspace-ified the netlink dependencies by @anfredette in +* Don't try and pin .data maps by @astoycos in +* .github: Add actions to dependabot by @dave-tucker in +* Fix Procceedon bug (Issue #791) by @anfredette in +* Add script to delete bpfman qdiscs on all interfaces by @anfredette in +* Fix BPF Licensing by @dave-tucker in +* Fix example bytecode image builds add test coverage by @astoycos in +* Relicense userspace to Apache 2.0 only by @dave-tucker in +* bpfman: Use tc dispatcher from container image by @dave-tucker in +* bpfman: Unify the "run as root" and "run as bpfman user" codepaths by @Billy99 in +* bpfman, bpfctl, operator: Remove support for TCP/TLS by @dave-tucker in +* bpfman-operator: Make the CSI deployment default for bpfman-operator by @Billy99 in +* ci: Add YAML formatter by @dave-tucker in +* Fix some panics + add testing and fix for map sharing by @astoycos in +* bpfman: mount default bpffs on kind by @astoycos in +* bpfman: Remove unused file by @dave-tucker in +* Document valid kernel versions by @Billy99 in +* Update documentation on new YAML Linter by @Billy99 in + +**Full Changelog**: diff --git a/changelogs/CHANGELOG-v0.4.0.md b/changelogs/CHANGELOG-v0.4.0.md new file mode 100644 index 000000000..e03829b77 --- /dev/null +++ b/changelogs/CHANGELOG-v0.4.0.md @@ -0,0 +1,138 @@ + +The v0.4.0 release is a minor release and the first following the project +[rename from bpfd to bpfman](https://bpfman.io/main/blog/2023/11/23/bpfd-becomes-bpfman/). +From a design perspective, this release fully transitions bpfman to be a +library rather than a daemon. From a user's perspective the main +difference is that the `bpfctl` CLI can now simply be called directly with the +`bpfman` binary, otherwise all of the major commands remain the same. + +On top of transitioning to a library the community has also implemented some +exciting new features: + +- Support for Uprobe Programs in both core and kubernetes +- The ability to attach Uprobes inside containers (locally and in Kubernetes), see [the blog](https://bpfman.io/main/blog/2024/02/26/technical-challenges-for-attaching-ebpf-programs-in-containers/) for more. +- Support for Kprobe Programs in both core and kubernetes +- Support for Fentry Programs in core +- Support for Fexit Programs in core + +Additionally this release provides some new binary crates. The `bpfman-rpc` binary +allows other languages to call into the bpfman library using the existing +grpc api defined in the `bpfman-api` crate bindings. The `bpf-metrics-exporter`, +and the `bpf-log-exporter`binaries allow users to gather useful information regarding +the bpf subsystem. + +> [!WARNING] +FEATURE DEPRECATION: The ability to view all programs on a given node via the +`BpfProgram` CRD has been deprecated. Instead the community now provides +the `bpf-metrics-exporter` crate. + +> [!WARNING] +The CSI feature still requires a privileged application on distributions which +enable SELinux by default (i.e Red Hat Openshift). Therefore we've shipped a set of +deployment configs specifically for openshift in this release, see the additional +`go--counter-install-ocp.yaml` artifacts included in the release +payload. Stay tuned to [#829](https://github.com/bpfman/bpfman/issues/596) for +updates. + +## What's Changed (excluding dependency bumps) + +* Correct cargo path by @danielmellado in https://github.com/bpfman/bpfman/pull/835 +* Rename all of the things! by @dave-tucker in https://github.com/bpfman/bpfman/pull/834 +* Merge bpfctl into bpfman by @dave-tucker in https://github.com/bpfman/bpfman/pull/826 +* .github: Reinstate the image-build workflow by @dave-tucker in https://github.com/bpfman/bpfman/pull/839 +* bpf-metrics-exporter: Add metrics exporter by @dave-tucker in https://github.com/bpfman/bpfman/pull/821 +* docs/design: Add daemonless design doc by @dave-tucker in https://github.com/bpfman/bpfman/pull/778 +* blog: Add blog about logo design by @dave-tucker in https://github.com/bpfman/bpfman/pull/840 +* README: Update Title by @dave-tucker in https://github.com/bpfman/bpfman/pull/841 +* .github: Bring back the verify check by @dave-tucker in https://github.com/bpfman/bpfman/pull/836 +* blog: some small fixups by @astoycos in https://github.com/bpfman/bpfman/pull/847 +* bpfman: Fix panic if XDP prog already loaded by @Billy99 in https://github.com/bpfman/bpfman/pull/849 +* scripts: Update scripts to still cleanup bpfd by @Billy99 in https://github.com/bpfman/bpfman/pull/850 +* Update Project Logo by @dave-tucker in https://github.com/bpfman/bpfman/pull/851 +* docs: Simplify api-docs generation by @dave-tucker in https://github.com/bpfman/bpfman/pull/852 +* Running `cargo xtask build-proto` resulted in some changes by @anfredette in https://github.com/bpfman/bpfman/pull/846 +* docs: Update README by @dave-tucker in https://github.com/bpfman/bpfman/pull/854 +* Metrics deployment updates by @astoycos in https://github.com/bpfman/bpfman/pull/853 +* Support attaching uprobes to targets inside containers by @anfredette in https://github.com/bpfman/bpfman/pull/784 +* packaging: Add RPMs by @dave-tucker in https://github.com/bpfman/bpfman/pull/848 +* Packaging and Release Fixes by @dave-tucker in https://github.com/bpfman/bpfman/pull/868 +* bpfman: in memory db setup + oci_utils conversion by @astoycos in https://github.com/bpfman/bpfman/pull/861 +* Packaging tweaks by @dave-tucker in https://github.com/bpfman/bpfman/pull/869 +* bpfman: cli: Refactor command handling by @dave-tucker in https://github.com/bpfman/bpfman/pull/870 +* bpfman-actions: update -artifact by @astoycos in https://github.com/bpfman/bpfman/pull/884 +* bpfman actions: remove put-issue-in-project by @astoycos in https://github.com/bpfman/bpfman/pull/883 +* bpfman: Remove grpc section from config by @Billy99 in https://github.com/bpfman/bpfman/pull/885 +* xtask: add man and completion script generation by @weiyuhang2011 in https://github.com/bpfman/bpfman/pull/873 +* bpfman: add support for socket activation by @Billy99 in https://github.com/bpfman/bpfman/pull/872 +* Sled integration for the core program type by @astoycos in https://github.com/bpfman/bpfman/pull/874 +* docs: Community Meeting Minutes - Jan 4, 2024 by @Billy99 in https://github.com/bpfman/bpfman/pull/902 +* Tidy up async code and shutdown handling by @dave-tucker in https://github.com/bpfman/bpfman/pull/903 +* Sled fixes, convert xdpdispatcher to use sled, fix loaded_programs race by @astoycos in https://github.com/bpfman/bpfman/pull/901 +* fixup xdp_pass_private test by @astoycos in https://github.com/bpfman/bpfman/pull/916 +* Sled tc dispatcher by @astoycos in https://github.com/bpfman/bpfman/pull/910 +* article: sled-db conversion by @astoycos in https://github.com/bpfman/bpfman/pull/911 +* add daemonless doc to website fixup sled article by @astoycos in https://github.com/bpfman/bpfman/pull/934 +* packaging: Move RPM to use socket activation by @Billy99 in https://github.com/bpfman/bpfman/pull/922 +* deps: Bump netlink dependencies by @dave-tucker in https://github.com/bpfman/bpfman/pull/953 +* cli: fix nits in manpage and tab-completion by @Billy99 in https://github.com/bpfman/bpfman/pull/935 +* refactor: make all sled-db keys into const by @shawnh2 in https://github.com/bpfman/bpfman/pull/923 +* Kubernetes Support for attaching uprobes in containers by @anfredette in https://github.com/bpfman/bpfman/pull/875 +* docs: Community Meeting Minutes - Jan 11 and 19, 2024 by @Billy99 in https://github.com/bpfman/bpfman/pull/954 +* bpfman: move bpfman.sock to standalone directory by @Billy99 in https://github.com/bpfman/bpfman/pull/962 +* docs: Minor nits by @Billy99 in https://github.com/bpfman/bpfman/pull/963 +* bpfman: Make the listening socket configurable by @astoycos in https://github.com/bpfman/bpfman/pull/964 +* DROP: Temporarily drop rust-cache action by @astoycos in https://github.com/bpfman/bpfman/pull/974 +* build: set golang to 1.21 for github builds by @Billy99 in https://github.com/bpfman/bpfman/pull/975 +* sled fixups and prefixes for final conversion bits by @astoycos in https://github.com/bpfman/bpfman/pull/956 +* operator: make health and metrics ports configurable by @Billy99 in https://github.com/bpfman/bpfman/pull/965 +* build: make bundle failing on upstream main by @Billy99 in https://github.com/bpfman/bpfman/pull/976 +* build: revert back to nightly by @Billy99 in https://github.com/bpfman/bpfman/pull/979 +* ci: Don't fail the build on codecov by @dave-tucker in https://github.com/bpfman/bpfman/pull/981 +* ci: Group dependabot updates by @dave-tucker in https://github.com/bpfman/bpfman/pull/982 +* Enable loggercheck lint by @dave-tucker in https://github.com/bpfman/bpfman/pull/983 +* chore: Fix yamllint/vscode-yaml formatting discrepencies by @dave-tucker in https://github.com/bpfman/bpfman/pull/984 +* speed up bpfman image build by @astoycos in https://github.com/bpfman/bpfman/pull/986 +* remove to-string clippy failures by @astoycos in https://github.com/bpfman/bpfman/pull/989 +* fixups for pr #875 by @anfredette in https://github.com/bpfman/bpfman/pull/978 +* fix startup race by @astoycos in https://github.com/bpfman/bpfman/pull/990 +* Make bpfman get work without gRPC by @dave-tucker in https://github.com/bpfman/bpfman/pull/994 +* Fix clippy errors flagged by new version of clippy by @anfredette in https://github.com/bpfman/bpfman/pull/998 +* bpfman: Cache TUF Metadata by @dave-tucker in https://github.com/bpfman/bpfman/pull/1000 +* Convert the rest of the cli to not use GRPC by @astoycos in https://github.com/bpfman/bpfman/pull/995 +* Fix broken links in docs by @anfredette in https://github.com/bpfman/bpfman/pull/996 +* chore(bpfman): Update aya dependency by @dave-tucker in https://github.com/bpfman/bpfman/pull/1006 +* bpfman-agent: list filters not working by @Billy99 in https://github.com/bpfman/bpfman/pull/1004 +* bpfman-operator: Add status to kubectl get commands by @Billy99 in https://github.com/bpfman/bpfman/pull/1007 +* docs: Update Kubernetes integration test instructions by @anfredette in https://github.com/bpfman/bpfman/pull/1005 +* update dependencies by @astoycos in https://github.com/bpfman/bpfman/pull/1008 +* Image manager refactor by @astoycos in https://github.com/bpfman/bpfman/pull/1011 +* blog: uprobe in container work by @anfredette in https://github.com/bpfman/bpfman/pull/1001 +* chore(bpfman): Use native-tls by @dave-tucker in https://github.com/bpfman/bpfman/pull/1023 +* blog: bpfman integration with AF_XDP by @maryamtahhan in https://github.com/bpfman/bpfman/pull/1010 +* Fixup bpfd references in AF_XDP blog by @maryamtahhan in https://github.com/bpfman/bpfman/pull/1027 +* bpfman: Add support for fentry/fexit program types by @Billy99 in https://github.com/bpfman/bpfman/pull/1024 +* docs: Update authors by @Billy99 in https://github.com/bpfman/bpfman/pull/1029 +* feat(bpf-log-exporter): Initial Commit by @dave-tucker in https://github.com/bpfman/bpfman/pull/1028 +* Turn bpfman into a library crate by @astoycos in https://github.com/bpfman/bpfman/pull/1014 +* Fixup K8s operator generated code by @astoycos in https://github.com/bpfman/bpfman/pull/1033 +* Add kprobe and uprobe example programs by @anfredette in https://github.com/bpfman/bpfman/pull/1017 +* build docs on pull requests by @astoycos in https://github.com/bpfman/bpfman/pull/1035 +* add public-api checks by @astoycos in https://github.com/bpfman/bpfman/pull/1037 +* DROP pull in cosign fixes by @astoycos in https://github.com/bpfman/bpfman/pull/1042 +* Add xtask rules to run lint and ut by @msherif1234 in https://github.com/bpfman/bpfman/pull/1039 +* bpfman-operator: Add K8s support for fentry and fexit by @Billy99 in https://github.com/bpfman/bpfman/pull/1047 +* Add kprobe and uprobe k8s integration tests by @anfredette in https://github.com/bpfman/bpfman/pull/1041 +* bump sigstore-rs to 0.9.0 by @astoycos in https://github.com/bpfman/bpfman/pull/1051 +* small doc fixups by @astoycos in https://github.com/bpfman/bpfman/pull/1052 +* remove a bunch of un-needed apis by @astoycos in https://github.com/bpfman/bpfman/pull/1049 +* fix public-api for latest nightly by @astoycos in https://github.com/bpfman/bpfman/pull/1057 +* docs: Scrub documentation for Release 0.4.0 by @Billy99 in https://github.com/bpfman/bpfman/pull/1053 +* stop checking bpfman-api and bpfman-csi public_api by @astoycos in https://github.com/bpfman/bpfman/pull/1061 + +## New Contributors +* @danielmellado made their first contribution in https://github.com/bpfman/bpfman/pull/835 +* @weiyuhang2011 made their first contribution in https://github.com/bpfman/bpfman/pull/873 +* @shawnh2 made their first contribution in https://github.com/bpfman/bpfman/pull/923 +* @msherif1234 made their first contribution in https://github.com/bpfman/bpfman/pull/1039 + +**Full Changelog**: https://github.com/bpfman/bpfman/compare/v0.3.1...v0.4.0 \ No newline at end of file diff --git a/clients/gobpfman/v1/bpfman.pb.go b/clients/gobpfman/v1/bpfman.pb.go index 744623e09..520ae2081 100644 --- a/clients/gobpfman/v1/bpfman.pb.go +++ b/clients/gobpfman/v1/bpfman.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 -// protoc v3.19.4 +// protoc v3.19.6 // source: bpfman.proto package v1 @@ -100,6 +100,7 @@ type BytecodeLocation struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Location: + // // *BytecodeLocation_Image // *BytecodeLocation_File Location isBytecodeLocation_Location `protobuf_oneof:"location"` @@ -624,10 +625,10 @@ type KprobeAttachInfo struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - FnName string `protobuf:"bytes,1,opt,name=fn_name,json=fnName,proto3" json:"fn_name,omitempty"` - Offset uint64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` - Retprobe bool `protobuf:"varint,3,opt,name=retprobe,proto3" json:"retprobe,omitempty"` - Namespace *string `protobuf:"bytes,4,opt,name=namespace,proto3,oneof" json:"namespace,omitempty"` + FnName string `protobuf:"bytes,1,opt,name=fn_name,json=fnName,proto3" json:"fn_name,omitempty"` + Offset uint64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + Retprobe bool `protobuf:"varint,3,opt,name=retprobe,proto3" json:"retprobe,omitempty"` + ContainerPid *int32 `protobuf:"varint,4,opt,name=container_pid,json=containerPid,proto3,oneof" json:"container_pid,omitempty"` } func (x *KprobeAttachInfo) Reset() { @@ -683,11 +684,11 @@ func (x *KprobeAttachInfo) GetRetprobe() bool { return false } -func (x *KprobeAttachInfo) GetNamespace() string { - if x != nil && x.Namespace != nil { - return *x.Namespace +func (x *KprobeAttachInfo) GetContainerPid() int32 { + if x != nil && x.ContainerPid != nil { + return *x.ContainerPid } - return "" + return 0 } type UprobeAttachInfo struct { @@ -695,12 +696,12 @@ type UprobeAttachInfo struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - FnName *string `protobuf:"bytes,1,opt,name=fn_name,json=fnName,proto3,oneof" json:"fn_name,omitempty"` - Offset uint64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` - Target string `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"` - Retprobe bool `protobuf:"varint,4,opt,name=retprobe,proto3" json:"retprobe,omitempty"` - Pid *int32 `protobuf:"varint,5,opt,name=pid,proto3,oneof" json:"pid,omitempty"` - Namespace *string `protobuf:"bytes,6,opt,name=namespace,proto3,oneof" json:"namespace,omitempty"` + FnName *string `protobuf:"bytes,1,opt,name=fn_name,json=fnName,proto3,oneof" json:"fn_name,omitempty"` + Offset uint64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + Target string `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"` + Retprobe bool `protobuf:"varint,4,opt,name=retprobe,proto3" json:"retprobe,omitempty"` + Pid *int32 `protobuf:"varint,5,opt,name=pid,proto3,oneof" json:"pid,omitempty"` + ContainerPid *int32 `protobuf:"varint,6,opt,name=container_pid,json=containerPid,proto3,oneof" json:"container_pid,omitempty"` } func (x *UprobeAttachInfo) Reset() { @@ -770,9 +771,103 @@ func (x *UprobeAttachInfo) GetPid() int32 { return 0 } -func (x *UprobeAttachInfo) GetNamespace() string { - if x != nil && x.Namespace != nil { - return *x.Namespace +func (x *UprobeAttachInfo) GetContainerPid() int32 { + if x != nil && x.ContainerPid != nil { + return *x.ContainerPid + } + return 0 +} + +type FentryAttachInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FnName string `protobuf:"bytes,1,opt,name=fn_name,json=fnName,proto3" json:"fn_name,omitempty"` +} + +func (x *FentryAttachInfo) Reset() { + *x = FentryAttachInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_bpfman_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FentryAttachInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FentryAttachInfo) ProtoMessage() {} + +func (x *FentryAttachInfo) ProtoReflect() protoreflect.Message { + mi := &file_bpfman_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FentryAttachInfo.ProtoReflect.Descriptor instead. +func (*FentryAttachInfo) Descriptor() ([]byte, []int) { + return file_bpfman_proto_rawDescGZIP(), []int{9} +} + +func (x *FentryAttachInfo) GetFnName() string { + if x != nil { + return x.FnName + } + return "" +} + +type FexitAttachInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FnName string `protobuf:"bytes,1,opt,name=fn_name,json=fnName,proto3" json:"fn_name,omitempty"` +} + +func (x *FexitAttachInfo) Reset() { + *x = FexitAttachInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_bpfman_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FexitAttachInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FexitAttachInfo) ProtoMessage() {} + +func (x *FexitAttachInfo) ProtoReflect() protoreflect.Message { + mi := &file_bpfman_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FexitAttachInfo.ProtoReflect.Descriptor instead. +func (*FexitAttachInfo) Descriptor() ([]byte, []int) { + return file_bpfman_proto_rawDescGZIP(), []int{10} +} + +func (x *FexitAttachInfo) GetFnName() string { + if x != nil { + return x.FnName } return "" } @@ -783,18 +878,21 @@ type AttachInfo struct { unknownFields protoimpl.UnknownFields // Types that are assignable to Info: + // // *AttachInfo_XdpAttachInfo // *AttachInfo_TcAttachInfo // *AttachInfo_TracepointAttachInfo // *AttachInfo_KprobeAttachInfo // *AttachInfo_UprobeAttachInfo + // *AttachInfo_FentryAttachInfo + // *AttachInfo_FexitAttachInfo Info isAttachInfo_Info `protobuf_oneof:"info"` } func (x *AttachInfo) Reset() { *x = AttachInfo{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[9] + mi := &file_bpfman_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -807,7 +905,7 @@ func (x *AttachInfo) String() string { func (*AttachInfo) ProtoMessage() {} func (x *AttachInfo) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[9] + mi := &file_bpfman_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -820,7 +918,7 @@ func (x *AttachInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use AttachInfo.ProtoReflect.Descriptor instead. func (*AttachInfo) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{9} + return file_bpfman_proto_rawDescGZIP(), []int{11} } func (m *AttachInfo) GetInfo() isAttachInfo_Info { @@ -865,6 +963,20 @@ func (x *AttachInfo) GetUprobeAttachInfo() *UprobeAttachInfo { return nil } +func (x *AttachInfo) GetFentryAttachInfo() *FentryAttachInfo { + if x, ok := x.GetInfo().(*AttachInfo_FentryAttachInfo); ok { + return x.FentryAttachInfo + } + return nil +} + +func (x *AttachInfo) GetFexitAttachInfo() *FexitAttachInfo { + if x, ok := x.GetInfo().(*AttachInfo_FexitAttachInfo); ok { + return x.FexitAttachInfo + } + return nil +} + type isAttachInfo_Info interface { isAttachInfo_Info() } @@ -889,6 +1001,14 @@ type AttachInfo_UprobeAttachInfo struct { UprobeAttachInfo *UprobeAttachInfo `protobuf:"bytes,6,opt,name=uprobe_attach_info,json=uprobeAttachInfo,proto3,oneof"` } +type AttachInfo_FentryAttachInfo struct { + FentryAttachInfo *FentryAttachInfo `protobuf:"bytes,7,opt,name=fentry_attach_info,json=fentryAttachInfo,proto3,oneof"` +} + +type AttachInfo_FexitAttachInfo struct { + FexitAttachInfo *FexitAttachInfo `protobuf:"bytes,8,opt,name=fexit_attach_info,json=fexitAttachInfo,proto3,oneof"` +} + func (*AttachInfo_XdpAttachInfo) isAttachInfo_Info() {} func (*AttachInfo_TcAttachInfo) isAttachInfo_Info() {} @@ -899,6 +1019,10 @@ func (*AttachInfo_KprobeAttachInfo) isAttachInfo_Info() {} func (*AttachInfo_UprobeAttachInfo) isAttachInfo_Info() {} +func (*AttachInfo_FentryAttachInfo) isAttachInfo_Info() {} + +func (*AttachInfo_FexitAttachInfo) isAttachInfo_Info() {} + type LoadRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -917,7 +1041,7 @@ type LoadRequest struct { func (x *LoadRequest) Reset() { *x = LoadRequest{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[10] + mi := &file_bpfman_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -930,7 +1054,7 @@ func (x *LoadRequest) String() string { func (*LoadRequest) ProtoMessage() {} func (x *LoadRequest) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[10] + mi := &file_bpfman_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -943,7 +1067,7 @@ func (x *LoadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LoadRequest.ProtoReflect.Descriptor instead. func (*LoadRequest) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{10} + return file_bpfman_proto_rawDescGZIP(), []int{12} } func (x *LoadRequest) GetBytecode() *BytecodeLocation { @@ -1014,7 +1138,7 @@ type LoadResponse struct { func (x *LoadResponse) Reset() { *x = LoadResponse{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[11] + mi := &file_bpfman_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1027,7 +1151,7 @@ func (x *LoadResponse) String() string { func (*LoadResponse) ProtoMessage() {} func (x *LoadResponse) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[11] + mi := &file_bpfman_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1040,7 +1164,7 @@ func (x *LoadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LoadResponse.ProtoReflect.Descriptor instead. func (*LoadResponse) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{11} + return file_bpfman_proto_rawDescGZIP(), []int{13} } func (x *LoadResponse) GetInfo() *ProgramInfo { @@ -1068,7 +1192,7 @@ type UnloadRequest struct { func (x *UnloadRequest) Reset() { *x = UnloadRequest{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[12] + mi := &file_bpfman_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1081,7 +1205,7 @@ func (x *UnloadRequest) String() string { func (*UnloadRequest) ProtoMessage() {} func (x *UnloadRequest) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[12] + mi := &file_bpfman_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1094,7 +1218,7 @@ func (x *UnloadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnloadRequest.ProtoReflect.Descriptor instead. func (*UnloadRequest) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{12} + return file_bpfman_proto_rawDescGZIP(), []int{14} } func (x *UnloadRequest) GetId() uint32 { @@ -1113,7 +1237,7 @@ type UnloadResponse struct { func (x *UnloadResponse) Reset() { *x = UnloadResponse{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[13] + mi := &file_bpfman_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1126,7 +1250,7 @@ func (x *UnloadResponse) String() string { func (*UnloadResponse) ProtoMessage() {} func (x *UnloadResponse) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[13] + mi := &file_bpfman_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1139,7 +1263,7 @@ func (x *UnloadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnloadResponse.ProtoReflect.Descriptor instead. func (*UnloadResponse) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{13} + return file_bpfman_proto_rawDescGZIP(), []int{15} } type ListRequest struct { @@ -1147,15 +1271,15 @@ type ListRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ProgramType *uint32 `protobuf:"varint,1,opt,name=program_type,json=programType,proto3,oneof" json:"program_type,omitempty"` + ProgramType *uint32 `protobuf:"varint,1,opt,name=program_type,json=programType,proto3,oneof" json:"program_type,omitempty"` BpfmanProgramsOnly *bool `protobuf:"varint,2,opt,name=bpfman_programs_only,json=bpfmanProgramsOnly,proto3,oneof" json:"bpfman_programs_only,omitempty"` - MatchMetadata map[string]string `protobuf:"bytes,3,rep,name=match_metadata,json=matchMetadata,proto3" json:"match_metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + MatchMetadata map[string]string `protobuf:"bytes,3,rep,name=match_metadata,json=matchMetadata,proto3" json:"match_metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } func (x *ListRequest) Reset() { *x = ListRequest{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[14] + mi := &file_bpfman_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1168,7 +1292,7 @@ func (x *ListRequest) String() string { func (*ListRequest) ProtoMessage() {} func (x *ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[14] + mi := &file_bpfman_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1181,7 +1305,7 @@ func (x *ListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. func (*ListRequest) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{14} + return file_bpfman_proto_rawDescGZIP(), []int{16} } func (x *ListRequest) GetProgramType() uint32 { @@ -1216,7 +1340,7 @@ type ListResponse struct { func (x *ListResponse) Reset() { *x = ListResponse{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[15] + mi := &file_bpfman_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1229,7 +1353,7 @@ func (x *ListResponse) String() string { func (*ListResponse) ProtoMessage() {} func (x *ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[15] + mi := &file_bpfman_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1242,7 +1366,7 @@ func (x *ListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. func (*ListResponse) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{15} + return file_bpfman_proto_rawDescGZIP(), []int{17} } func (x *ListResponse) GetResults() []*ListResponse_ListResult { @@ -1263,7 +1387,7 @@ type PullBytecodeRequest struct { func (x *PullBytecodeRequest) Reset() { *x = PullBytecodeRequest{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[16] + mi := &file_bpfman_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1276,7 +1400,7 @@ func (x *PullBytecodeRequest) String() string { func (*PullBytecodeRequest) ProtoMessage() {} func (x *PullBytecodeRequest) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[16] + mi := &file_bpfman_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1289,7 +1413,7 @@ func (x *PullBytecodeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PullBytecodeRequest.ProtoReflect.Descriptor instead. func (*PullBytecodeRequest) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{16} + return file_bpfman_proto_rawDescGZIP(), []int{18} } func (x *PullBytecodeRequest) GetImage() *BytecodeImage { @@ -1308,7 +1432,7 @@ type PullBytecodeResponse struct { func (x *PullBytecodeResponse) Reset() { *x = PullBytecodeResponse{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[17] + mi := &file_bpfman_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1321,7 +1445,7 @@ func (x *PullBytecodeResponse) String() string { func (*PullBytecodeResponse) ProtoMessage() {} func (x *PullBytecodeResponse) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[17] + mi := &file_bpfman_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1334,7 +1458,7 @@ func (x *PullBytecodeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PullBytecodeResponse.ProtoReflect.Descriptor instead. func (*PullBytecodeResponse) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{17} + return file_bpfman_proto_rawDescGZIP(), []int{19} } type GetRequest struct { @@ -1348,7 +1472,7 @@ type GetRequest struct { func (x *GetRequest) Reset() { *x = GetRequest{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[18] + mi := &file_bpfman_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1361,7 +1485,7 @@ func (x *GetRequest) String() string { func (*GetRequest) ProtoMessage() {} func (x *GetRequest) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[18] + mi := &file_bpfman_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1374,7 +1498,7 @@ func (x *GetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRequest.ProtoReflect.Descriptor instead. func (*GetRequest) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{18} + return file_bpfman_proto_rawDescGZIP(), []int{20} } func (x *GetRequest) GetId() uint32 { @@ -1396,7 +1520,7 @@ type GetResponse struct { func (x *GetResponse) Reset() { *x = GetResponse{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[19] + mi := &file_bpfman_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1409,7 +1533,7 @@ func (x *GetResponse) String() string { func (*GetResponse) ProtoMessage() {} func (x *GetResponse) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[19] + mi := &file_bpfman_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1422,7 +1546,7 @@ func (x *GetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetResponse.ProtoReflect.Descriptor instead. func (*GetResponse) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{19} + return file_bpfman_proto_rawDescGZIP(), []int{21} } func (x *GetResponse) GetInfo() *ProgramInfo { @@ -1451,7 +1575,7 @@ type ListResponse_ListResult struct { func (x *ListResponse_ListResult) Reset() { *x = ListResponse_ListResult{} if protoimpl.UnsafeEnabled { - mi := &file_bpfman_proto_msgTypes[25] + mi := &file_bpfman_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1464,7 +1588,7 @@ func (x *ListResponse_ListResult) String() string { func (*ListResponse_ListResult) ProtoMessage() {} func (x *ListResponse_ListResult) ProtoReflect() protoreflect.Message { - mi := &file_bpfman_proto_msgTypes[25] + mi := &file_bpfman_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1477,7 +1601,7 @@ func (x *ListResponse_ListResult) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResponse_ListResult.ProtoReflect.Descriptor instead. func (*ListResponse_ListResult) Descriptor() ([]byte, []int) { - return file_bpfman_proto_rawDescGZIP(), []int{15, 0} + return file_bpfman_proto_rawDescGZIP(), []int{17, 0} } func (x *ListResponse_ListResult) GetInfo() *ProgramInfo { @@ -1497,202 +1621,221 @@ func (x *ListResponse_ListResult) GetKernelInfo() *KernelProgramInfo { var File_bpfman_proto protoreflect.FileDescriptor var file_bpfman_proto_rawDesc = []byte{ - 0x0a, 0x0a, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x62, 0x70, - 0x66, 0x64, 0x2e, 0x76, 0x31, 0x22, 0xa9, 0x01, 0x0a, 0x0d, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, - 0x64, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x2a, 0x0a, 0x11, 0x69, 0x6d, 0x61, - 0x67, 0x65, 0x5f, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x50, 0x75, 0x6c, 0x6c, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1f, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, - 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, - 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x22, 0x64, 0x0a, 0x10, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x4c, 0x6f, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x42, - 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x05, - 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x6c, - 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x86, 0x03, 0x0a, 0x11, 0x4b, 0x65, 0x72, 0x6e, - 0x65, 0x6c, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x41, - 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x70, 0x6c, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, - 0x74, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x67, 0x70, 0x6c, - 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x61, - 0x70, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x06, 0x6d, 0x61, 0x70, - 0x49, 0x64, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x74, 0x66, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x05, 0x62, 0x74, 0x66, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x79, - 0x74, 0x65, 0x73, 0x5f, 0x78, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x58, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x6a, 0x69, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6a, 0x69, - 0x74, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6a, 0x69, 0x74, - 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4a, - 0x69, 0x74, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6d, 0x65, - 0x6d, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x62, 0x79, 0x74, - 0x65, 0x73, 0x4d, 0x65, 0x6d, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x25, 0x0a, 0x0e, 0x76, 0x65, 0x72, - 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x73, 0x6e, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x0d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x49, 0x6e, 0x73, 0x6e, 0x73, - 0x22, 0x82, 0x04, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x62, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, - 0x2e, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x08, 0x62, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x61, - 0x74, 0x74, 0x61, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x70, - 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x06, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x12, 0x45, 0x0a, 0x0b, 0x67, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, - 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, - 0x6e, 0x66, 0x6f, 0x2e, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x0a, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x12, - 0x25, 0x0a, 0x0c, 0x6d, 0x61, 0x70, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x0a, 0x6d, 0x61, 0x70, 0x4f, 0x77, 0x6e, 0x65, - 0x72, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x61, 0x70, 0x5f, 0x70, 0x69, - 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, - 0x70, 0x50, 0x69, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1e, 0x0a, 0x0b, 0x6d, 0x61, 0x70, 0x5f, - 0x75, 0x73, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6d, - 0x61, 0x70, 0x55, 0x73, 0x65, 0x64, 0x42, 0x79, 0x12, 0x3e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x70, 0x66, - 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, - 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3d, 0x0a, 0x0f, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x6d, 0x61, 0x70, 0x5f, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x22, 0x7c, 0x0a, 0x0d, 0x58, 0x44, 0x50, 0x41, 0x74, 0x74, 0x61, - 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x66, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x69, 0x66, 0x61, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x65, 0x64, 0x5f, - 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x65, - 0x64, 0x4f, 0x6e, 0x22, 0x99, 0x01, 0x0a, 0x0c, 0x54, 0x43, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x0a, 0x0c, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, + 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x22, 0xa9, 0x01, 0x0a, 0x0d, 0x42, 0x79, + 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x2a, 0x0a, + 0x11, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x50, + 0x75, 0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1f, 0x0a, 0x08, 0x75, 0x73, 0x65, + 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x75, + 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x70, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, + 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x66, 0x0a, 0x10, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, + 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x05, 0x69, 0x6d, 0x61, + 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x49, 0x6d, 0x61, + 0x67, 0x65, 0x48, 0x00, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x04, 0x66, + 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x66, 0x69, 0x6c, + 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x86, 0x03, + 0x0a, 0x11, 0x4b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, + 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, + 0x61, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, + 0x6f, 0x61, 0x64, 0x65, 0x64, 0x41, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x70, 0x6c, + 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0d, 0x67, 0x70, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, + 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x61, 0x70, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x0d, 0x52, 0x06, 0x6d, 0x61, 0x70, 0x49, 0x64, 0x73, 0x12, 0x15, 0x0a, 0x06, 0x62, 0x74, 0x66, + 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x62, 0x74, 0x66, 0x49, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x78, 0x6c, 0x61, 0x74, 0x65, 0x64, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x58, 0x6c, 0x61, + 0x74, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x69, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x05, 0x6a, 0x69, 0x74, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x5f, 0x6a, 0x69, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x4a, 0x69, 0x74, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x79, + 0x74, 0x65, 0x73, 0x5f, 0x6d, 0x65, 0x6d, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0c, 0x62, 0x79, 0x74, 0x65, 0x73, 0x4d, 0x65, 0x6d, 0x6c, 0x6f, 0x63, 0x6b, 0x12, + 0x25, 0x0a, 0x0e, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x73, 0x6e, + 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, + 0x64, 0x49, 0x6e, 0x73, 0x6e, 0x73, 0x22, 0x8a, 0x04, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x67, 0x72, + 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x62, 0x79, + 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, + 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, + 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x62, 0x79, 0x74, 0x65, 0x63, + 0x6f, 0x64, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x61, 0x74, 0x74, 0x61, + 0x63, 0x68, 0x12, 0x47, 0x0a, 0x0b, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x2e, + 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x0a, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x12, 0x25, 0x0a, 0x0c, 0x6d, + 0x61, 0x70, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0d, 0x48, 0x00, 0x52, 0x0a, 0x6d, 0x61, 0x70, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x88, + 0x01, 0x01, 0x12, 0x20, 0x0a, 0x0c, 0x6d, 0x61, 0x70, 0x5f, 0x70, 0x69, 0x6e, 0x5f, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x61, 0x70, 0x50, 0x69, 0x6e, + 0x50, 0x61, 0x74, 0x68, 0x12, 0x1e, 0x0a, 0x0b, 0x6d, 0x61, 0x70, 0x5f, 0x75, 0x73, 0x65, 0x64, + 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x61, 0x70, 0x55, 0x73, + 0x65, 0x64, 0x42, 0x79, 0x12, 0x40, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3d, 0x0a, 0x0f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, + 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x6d, 0x61, 0x70, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x22, 0x7c, 0x0a, 0x0d, 0x58, 0x44, 0x50, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x66, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x66, 0x61, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x18, 0x05, - 0x20, 0x03, 0x28, 0x05, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x65, 0x64, 0x4f, 0x6e, 0x22, - 0x36, 0x0a, 0x14, 0x54, 0x72, 0x61, 0x63, 0x65, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x74, 0x74, - 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, - 0x63, 0x65, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x90, 0x01, 0x0a, 0x10, 0x4b, 0x70, 0x72, 0x6f, - 0x62, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, - 0x66, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, - 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x72, 0x65, 0x74, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x72, 0x65, 0x74, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x12, 0x21, 0x0a, 0x09, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0xd8, 0x01, 0x0a, 0x10, 0x55, - 0x70, 0x72, 0x6f, 0x62, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x1c, 0x0a, 0x07, 0x66, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x48, 0x00, 0x52, 0x06, 0x66, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x72, 0x65, 0x74, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x72, 0x65, 0x74, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x12, 0x15, 0x0a, 0x03, 0x70, 0x69, 0x64, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x03, 0x70, 0x69, 0x64, 0x88, 0x01, 0x01, - 0x12, 0x21, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x65, 0x64, 0x5f, 0x6f, 0x6e, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x65, 0x64, 0x4f, + 0x6e, 0x22, 0x99, 0x01, 0x0a, 0x0c, 0x54, 0x43, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x69, 0x66, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, + 0x66, 0x61, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x05, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x65, 0x64, 0x4f, 0x6e, 0x22, 0x36, 0x0a, + 0x14, 0x54, 0x72, 0x61, 0x63, 0x65, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x61, 0x63, 0x65, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x9b, 0x01, 0x0a, 0x10, 0x4b, 0x70, 0x72, 0x6f, 0x62, 0x65, + 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6e, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6e, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, + 0x65, 0x74, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, + 0x65, 0x74, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x12, 0x28, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, + 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x69, 0x64, 0x88, 0x01, + 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, + 0x70, 0x69, 0x64, 0x22, 0xe3, 0x01, 0x0a, 0x10, 0x55, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x41, 0x74, + 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1c, 0x0a, 0x07, 0x66, 0x6e, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x66, 0x6e, 0x4e, + 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x16, + 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x74, 0x70, 0x72, 0x6f, + 0x62, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x74, 0x70, 0x72, 0x6f, + 0x62, 0x65, 0x12, 0x15, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x48, + 0x01, 0x52, 0x03, 0x70, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, + 0x48, 0x02, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x69, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x66, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, - 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x69, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x82, 0x03, 0x0a, 0x0a, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x40, 0x0a, 0x0f, 0x78, 0x64, 0x70, 0x5f, 0x61, 0x74, 0x74, 0x61, - 0x63, 0x68, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, - 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x58, 0x44, 0x50, 0x41, 0x74, 0x74, 0x61, 0x63, - 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x0d, 0x78, 0x64, 0x70, 0x41, 0x74, 0x74, 0x61, - 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0e, 0x74, 0x63, 0x5f, 0x61, 0x74, 0x74, - 0x61, 0x63, 0x68, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x43, 0x41, 0x74, 0x74, 0x61, 0x63, - 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x63, 0x41, 0x74, 0x74, 0x61, 0x63, - 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x55, 0x0a, 0x16, 0x74, 0x72, 0x61, 0x63, 0x65, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, - 0x54, 0x72, 0x61, 0x63, 0x65, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, - 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x14, 0x74, 0x72, 0x61, 0x63, 0x65, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x49, 0x0a, 0x12, - 0x6b, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x5f, 0x69, 0x6e, - 0x66, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, - 0x76, 0x31, 0x2e, 0x4b, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, - 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x10, 0x6b, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x41, 0x74, 0x74, - 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x49, 0x0a, 0x12, 0x75, 0x70, 0x72, 0x6f, 0x62, - 0x65, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, - 0x72, 0x6f, 0x62, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, - 0x52, 0x10, 0x75, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, - 0x66, 0x6f, 0x42, 0x06, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x85, 0x04, 0x0a, 0x0b, 0x4c, - 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x08, 0x62, 0x79, - 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x62, - 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x4c, + 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x69, 0x64, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x69, 0x64, 0x22, 0x2b, 0x0a, 0x10, 0x46, 0x65, 0x6e, + 0x74, 0x72, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, + 0x07, 0x66, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x66, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x0f, 0x46, 0x65, 0x78, 0x69, 0x74, 0x41, + 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6e, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6e, 0x4e, 0x61, + 0x6d, 0x65, 0x22, 0xa3, 0x04, 0x0a, 0x0a, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x42, 0x0a, 0x0f, 0x78, 0x64, 0x70, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x5f, + 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x70, 0x66, + 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x58, 0x44, 0x50, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x0d, 0x78, 0x64, 0x70, 0x41, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x3f, 0x0a, 0x0e, 0x74, 0x63, 0x5f, 0x61, 0x74, 0x74, 0x61, + 0x63, 0x68, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x43, 0x41, 0x74, 0x74, 0x61, + 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x63, 0x41, 0x74, 0x74, 0x61, + 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x57, 0x0a, 0x16, 0x74, 0x72, 0x61, 0x63, 0x65, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x5f, 0x69, 0x6e, 0x66, 0x6f, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x74, 0x74, + 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x14, 0x74, 0x72, 0x61, 0x63, 0x65, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, + 0x4b, 0x0a, 0x12, 0x6b, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x70, + 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x41, 0x74, + 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x10, 0x6b, 0x70, 0x72, 0x6f, + 0x62, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4b, 0x0a, 0x12, + 0x75, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x5f, 0x69, 0x6e, + 0x66, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x10, 0x75, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x41, + 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4b, 0x0a, 0x12, 0x66, 0x65, 0x6e, + 0x74, 0x72, 0x79, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x46, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, + 0x66, 0x6f, 0x48, 0x00, 0x52, 0x10, 0x66, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x41, 0x74, 0x74, 0x61, + 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x48, 0x0a, 0x11, 0x66, 0x65, 0x78, 0x69, 0x74, 0x5f, + 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x65, + 0x78, 0x69, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, + 0x0f, 0x66, 0x65, 0x78, 0x69, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, + 0x42, 0x06, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x8d, 0x04, 0x0a, 0x0b, 0x4c, 0x6f, 0x61, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x08, 0x62, 0x79, 0x74, 0x65, + 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x62, 0x70, 0x66, + 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x62, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x72, 0x6f, - 0x67, 0x72, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x61, 0x74, 0x74, 0x61, - 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x61, - 0x74, 0x74, 0x61, 0x63, 0x68, 0x12, 0x3e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x45, 0x0a, 0x0b, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x62, 0x70, 0x66, - 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0a, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x04, - 0x75, 0x75, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x75, 0x75, - 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0c, 0x6d, 0x61, 0x70, 0x5f, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, 0x0a, 0x6d, - 0x61, 0x70, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x88, 0x01, 0x01, 0x1a, 0x3b, 0x0a, 0x0d, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3d, 0x0a, 0x0f, 0x47, 0x6c, 0x6f, - 0x62, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x75, 0x75, 0x69, - 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x6d, 0x61, 0x70, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x22, 0x75, 0x0a, 0x0c, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x67, 0x72, - 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x3b, 0x0a, 0x0b, - 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x72, 0x6e, - 0x65, 0x6c, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x6b, - 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x1f, 0x0a, 0x0d, 0x55, 0x6e, 0x6c, - 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x22, 0x10, 0x0a, 0x0e, 0x55, 0x6e, - 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa2, 0x02, 0x0a, - 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0c, - 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0d, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x54, 0x79, 0x70, - 0x65, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x12, 0x62, 0x70, 0x66, 0x64, 0x5f, 0x70, 0x72, 0x6f, - 0x67, 0x72, 0x61, 0x6d, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x48, 0x01, 0x52, 0x10, 0x62, 0x70, 0x66, 0x64, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x73, - 0x4f, 0x6e, 0x6c, 0x79, 0x88, 0x01, 0x01, 0x12, 0x4e, 0x0a, 0x0e, 0x6d, 0x61, 0x74, 0x63, 0x68, - 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x27, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x67, 0x72, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x74, 0x74, 0x61, + 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x06, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x12, 0x40, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x62, 0x70, 0x66, 0x6d, + 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x47, 0x0a, 0x0b, 0x67, 0x6c, 0x6f, + 0x62, 0x61, 0x6c, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, + 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x44, 0x61, 0x74, + 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x44, 0x61, + 0x74, 0x61, 0x12, 0x17, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0c, 0x6d, + 0x61, 0x70, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0d, 0x48, 0x01, 0x52, 0x0a, 0x6d, 0x61, 0x70, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x88, + 0x01, 0x01, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, + 0x3d, 0x0a, 0x0f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x07, + 0x0a, 0x05, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x6d, 0x61, 0x70, 0x5f, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x22, 0x79, 0x0a, 0x0c, 0x4c, 0x6f, 0x61, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, + 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0b, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x69, + 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x70, 0x66, 0x6d, + 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x50, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, + 0x6e, 0x66, 0x6f, 0x22, 0x1f, 0x0a, 0x0d, 0x55, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x02, 0x69, 0x64, 0x22, 0x10, 0x0a, 0x0e, 0x55, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xaa, 0x02, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, + 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x0b, + 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x35, + 0x0a, 0x14, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, + 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x12, + 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x73, 0x4f, 0x6e, + 0x6c, 0x79, 0x88, 0x01, 0x01, 0x12, 0x50, 0x0a, 0x0e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, + 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x40, 0x0a, 0x12, 0x4d, 0x61, 0x74, 0x63, 0x68, @@ -1700,60 +1843,62 @@ var file_bpfman_proto_rawDesc = []byte{ 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x70, 0x72, - 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x62, - 0x70, 0x66, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, - 0x79, 0x22, 0xce, 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x1a, 0x81, - 0x01, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2d, 0x0a, - 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x70, - 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, - 0x6f, 0x48, 0x00, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x88, 0x01, 0x01, 0x12, 0x3b, 0x0a, 0x0b, - 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x72, 0x6e, - 0x65, 0x6c, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x6b, - 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x69, 0x6e, - 0x66, 0x6f, 0x22, 0x43, 0x0a, 0x13, 0x50, 0x75, 0x6c, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, - 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x6d, 0x61, - 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, - 0x76, 0x31, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, - 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x75, 0x6c, 0x6c, 0x42, - 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x1c, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x22, 0x82, 0x01, - 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, - 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x70, - 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, - 0x6f, 0x48, 0x00, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x88, 0x01, 0x01, 0x12, 0x3b, 0x0a, 0x0b, - 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x72, 0x6e, - 0x65, 0x6c, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x6b, - 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x69, 0x6e, - 0x66, 0x6f, 0x32, 0xaa, 0x02, 0x0a, 0x04, 0x42, 0x70, 0x66, 0x64, 0x12, 0x33, 0x0a, 0x04, 0x4c, - 0x6f, 0x61, 0x64, 0x12, 0x14, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, - 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x62, 0x70, 0x66, 0x64, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x39, 0x0a, 0x06, 0x55, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x16, 0x2e, 0x62, 0x70, 0x66, - 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x6c, - 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x4c, - 0x69, 0x73, 0x74, 0x12, 0x14, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x62, 0x70, 0x66, 0x64, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4b, 0x0a, 0x0c, 0x50, 0x75, 0x6c, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, - 0x12, 0x1c, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x42, - 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, - 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x42, 0x79, 0x74, - 0x65, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, - 0x03, 0x47, 0x65, 0x74, 0x12, 0x13, 0x2e, 0x62, 0x70, 0x66, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x62, 0x70, 0x66, 0x64, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, - 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x70, - 0x66, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x67, - 0x6f, 0x62, 0x70, 0x66, 0x64, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x42, 0x17, 0x0a, 0x15, 0x5f, 0x62, + 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x73, 0x5f, 0x6f, + 0x6e, 0x6c, 0x79, 0x22, 0xd4, 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x1a, 0x85, 0x01, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x16, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x67, + 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x88, + 0x01, 0x01, 0x12, 0x3d, 0x0a, 0x0b, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x6e, 0x66, + 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, + 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, + 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, + 0x6f, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x45, 0x0a, 0x13, 0x50, 0x75, + 0x6c, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x79, 0x74, + 0x65, 0x63, 0x6f, 0x64, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x75, 0x6c, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x0a, 0x0a, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x22, 0x86, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, + 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, + 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x88, 0x01, 0x01, 0x12, 0x3d, 0x0a, 0x0b, 0x6b, 0x65, 0x72, 0x6e, + 0x65, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x72, 0x6e, 0x65, 0x6c, + 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x6b, 0x65, 0x72, + 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x69, 0x6e, 0x66, 0x6f, + 0x32, 0xc0, 0x02, 0x0a, 0x06, 0x42, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x12, 0x37, 0x0a, 0x04, 0x4c, + 0x6f, 0x61, 0x64, 0x12, 0x16, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x62, 0x70, + 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x06, 0x55, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x18, + 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x61, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x62, 0x70, + 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0c, + 0x50, 0x75, 0x6c, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x1e, 0x2e, 0x62, + 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x42, 0x79, 0x74, + 0x65, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x62, + 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x6c, 0x6c, 0x42, 0x79, 0x74, + 0x65, 0x63, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, + 0x03, 0x47, 0x65, 0x74, 0x12, 0x15, 0x2e, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x62, 0x70, + 0x66, 0x6d, 0x61, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, + 0x2f, 0x67, 0x6f, 0x62, 0x70, 0x66, 0x6d, 0x61, 0x6e, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1768,7 +1913,7 @@ func file_bpfman_proto_rawDescGZIP() []byte { return file_bpfman_proto_rawDescData } -var file_bpfman_proto_msgTypes = make([]protoimpl.MessageInfo, 26) +var file_bpfman_proto_msgTypes = make([]protoimpl.MessageInfo, 28) var file_bpfman_proto_goTypes = []interface{}{ (*BytecodeImage)(nil), // 0: bpfman.v1.BytecodeImage (*BytecodeLocation)(nil), // 1: bpfman.v1.BytecodeLocation @@ -1779,63 +1924,67 @@ var file_bpfman_proto_goTypes = []interface{}{ (*TracepointAttachInfo)(nil), // 6: bpfman.v1.TracepointAttachInfo (*KprobeAttachInfo)(nil), // 7: bpfman.v1.KprobeAttachInfo (*UprobeAttachInfo)(nil), // 8: bpfman.v1.UprobeAttachInfo - (*AttachInfo)(nil), // 9: bpfman.v1.AttachInfo - (*LoadRequest)(nil), // 10: bpfman.v1.LoadRequest - (*LoadResponse)(nil), // 11: bpfman.v1.LoadResponse - (*UnloadRequest)(nil), // 12: bpfman.v1.UnloadRequest - (*UnloadResponse)(nil), // 13: bpfman.v1.UnloadResponse - (*ListRequest)(nil), // 14: bpfman.v1.ListRequest - (*ListResponse)(nil), // 15: bpfman.v1.ListResponse - (*PullBytecodeRequest)(nil), // 16: bpfman.v1.PullBytecodeRequest - (*PullBytecodeResponse)(nil), // 17: bpfman.v1.PullBytecodeResponse - (*GetRequest)(nil), // 18: bpfman.v1.GetRequest - (*GetResponse)(nil), // 19: bpfman.v1.GetResponse - nil, // 20: bpfman.v1.ProgramInfo.GlobalDataEntry - nil, // 21: bpfman.v1.ProgramInfo.MetadataEntry - nil, // 22: bpfman.v1.LoadRequest.MetadataEntry - nil, // 23: bpfman.v1.LoadRequest.GlobalDataEntry - nil, // 24: bpfman.v1.ListRequest.MatchMetadataEntry - (*ListResponse_ListResult)(nil), // 25: bpfman.v1.ListResponse.ListResult + (*FentryAttachInfo)(nil), // 9: bpfman.v1.FentryAttachInfo + (*FexitAttachInfo)(nil), // 10: bpfman.v1.FexitAttachInfo + (*AttachInfo)(nil), // 11: bpfman.v1.AttachInfo + (*LoadRequest)(nil), // 12: bpfman.v1.LoadRequest + (*LoadResponse)(nil), // 13: bpfman.v1.LoadResponse + (*UnloadRequest)(nil), // 14: bpfman.v1.UnloadRequest + (*UnloadResponse)(nil), // 15: bpfman.v1.UnloadResponse + (*ListRequest)(nil), // 16: bpfman.v1.ListRequest + (*ListResponse)(nil), // 17: bpfman.v1.ListResponse + (*PullBytecodeRequest)(nil), // 18: bpfman.v1.PullBytecodeRequest + (*PullBytecodeResponse)(nil), // 19: bpfman.v1.PullBytecodeResponse + (*GetRequest)(nil), // 20: bpfman.v1.GetRequest + (*GetResponse)(nil), // 21: bpfman.v1.GetResponse + nil, // 22: bpfman.v1.ProgramInfo.GlobalDataEntry + nil, // 23: bpfman.v1.ProgramInfo.MetadataEntry + nil, // 24: bpfman.v1.LoadRequest.MetadataEntry + nil, // 25: bpfman.v1.LoadRequest.GlobalDataEntry + nil, // 26: bpfman.v1.ListRequest.MatchMetadataEntry + (*ListResponse_ListResult)(nil), // 27: bpfman.v1.ListResponse.ListResult } var file_bpfman_proto_depIdxs = []int32{ 0, // 0: bpfman.v1.BytecodeLocation.image:type_name -> bpfman.v1.BytecodeImage 1, // 1: bpfman.v1.ProgramInfo.bytecode:type_name -> bpfman.v1.BytecodeLocation - 9, // 2: bpfman.v1.ProgramInfo.attach:type_name -> bpfman.v1.AttachInfo - 20, // 3: bpfman.v1.ProgramInfo.global_data:type_name -> bpfman.v1.ProgramInfo.GlobalDataEntry - 21, // 4: bpfman.v1.ProgramInfo.metadata:type_name -> bpfman.v1.ProgramInfo.MetadataEntry + 11, // 2: bpfman.v1.ProgramInfo.attach:type_name -> bpfman.v1.AttachInfo + 22, // 3: bpfman.v1.ProgramInfo.global_data:type_name -> bpfman.v1.ProgramInfo.GlobalDataEntry + 23, // 4: bpfman.v1.ProgramInfo.metadata:type_name -> bpfman.v1.ProgramInfo.MetadataEntry 4, // 5: bpfman.v1.AttachInfo.xdp_attach_info:type_name -> bpfman.v1.XDPAttachInfo 5, // 6: bpfman.v1.AttachInfo.tc_attach_info:type_name -> bpfman.v1.TCAttachInfo 6, // 7: bpfman.v1.AttachInfo.tracepoint_attach_info:type_name -> bpfman.v1.TracepointAttachInfo 7, // 8: bpfman.v1.AttachInfo.kprobe_attach_info:type_name -> bpfman.v1.KprobeAttachInfo 8, // 9: bpfman.v1.AttachInfo.uprobe_attach_info:type_name -> bpfman.v1.UprobeAttachInfo - 1, // 10: bpfman.v1.LoadRequest.bytecode:type_name -> bpfman.v1.BytecodeLocation - 9, // 11: bpfman.v1.LoadRequest.attach:type_name -> bpfman.v1.AttachInfo - 22, // 12: bpfman.v1.LoadRequest.metadata:type_name -> bpfman.v1.LoadRequest.MetadataEntry - 23, // 13: bpfman.v1.LoadRequest.global_data:type_name -> bpfman.v1.LoadRequest.GlobalDataEntry - 3, // 14: bpfman.v1.LoadResponse.info:type_name -> bpfman.v1.ProgramInfo - 2, // 15: bpfman.v1.LoadResponse.kernel_info:type_name -> bpfman.v1.KernelProgramInfo - 24, // 16: bpfman.v1.ListRequest.match_metadata:type_name -> bpfman.v1.ListRequest.MatchMetadataEntry - 25, // 17: bpfman.v1.ListResponse.results:type_name -> bpfman.v1.ListResponse.ListResult - 0, // 18: bpfman.v1.PullBytecodeRequest.image:type_name -> bpfman.v1.BytecodeImage - 3, // 19: bpfman.v1.GetResponse.info:type_name -> bpfman.v1.ProgramInfo - 2, // 20: bpfman.v1.GetResponse.kernel_info:type_name -> bpfman.v1.KernelProgramInfo - 3, // 21: bpfman.v1.ListResponse.ListResult.info:type_name -> bpfman.v1.ProgramInfo - 2, // 22: bpfman.v1.ListResponse.ListResult.kernel_info:type_name -> bpfman.v1.KernelProgramInfo - 10, // 23: bpfman.v1.Bpfman.Load:input_type -> bpfman.v1.LoadRequest - 12, // 24: bpfman.v1.Bpfman.Unload:input_type -> bpfman.v1.UnloadRequest - 14, // 25: bpfman.v1.Bpfman.List:input_type -> bpfman.v1.ListRequest - 16, // 26: bpfman.v1.Bpfman.PullBytecode:input_type -> bpfman.v1.PullBytecodeRequest - 18, // 27: bpfman.v1.Bpfman.Get:input_type -> bpfman.v1.GetRequest - 11, // 28: bpfman.v1.Bpfman.Load:output_type -> bpfman.v1.LoadResponse - 13, // 29: bpfman.v1.Bpfman.Unload:output_type -> bpfman.v1.UnloadResponse - 15, // 30: bpfman.v1.Bpfman.List:output_type -> bpfman.v1.ListResponse - 17, // 31: bpfman.v1.Bpfman.PullBytecode:output_type -> bpfman.v1.PullBytecodeResponse - 19, // 32: bpfman.v1.Bpfman.Get:output_type -> bpfman.v1.GetResponse - 28, // [28:33] is the sub-list for method output_type - 23, // [23:28] is the sub-list for method input_type - 23, // [23:23] is the sub-list for extension type_name - 23, // [23:23] is the sub-list for extension extendee - 0, // [0:23] is the sub-list for field type_name + 9, // 10: bpfman.v1.AttachInfo.fentry_attach_info:type_name -> bpfman.v1.FentryAttachInfo + 10, // 11: bpfman.v1.AttachInfo.fexit_attach_info:type_name -> bpfman.v1.FexitAttachInfo + 1, // 12: bpfman.v1.LoadRequest.bytecode:type_name -> bpfman.v1.BytecodeLocation + 11, // 13: bpfman.v1.LoadRequest.attach:type_name -> bpfman.v1.AttachInfo + 24, // 14: bpfman.v1.LoadRequest.metadata:type_name -> bpfman.v1.LoadRequest.MetadataEntry + 25, // 15: bpfman.v1.LoadRequest.global_data:type_name -> bpfman.v1.LoadRequest.GlobalDataEntry + 3, // 16: bpfman.v1.LoadResponse.info:type_name -> bpfman.v1.ProgramInfo + 2, // 17: bpfman.v1.LoadResponse.kernel_info:type_name -> bpfman.v1.KernelProgramInfo + 26, // 18: bpfman.v1.ListRequest.match_metadata:type_name -> bpfman.v1.ListRequest.MatchMetadataEntry + 27, // 19: bpfman.v1.ListResponse.results:type_name -> bpfman.v1.ListResponse.ListResult + 0, // 20: bpfman.v1.PullBytecodeRequest.image:type_name -> bpfman.v1.BytecodeImage + 3, // 21: bpfman.v1.GetResponse.info:type_name -> bpfman.v1.ProgramInfo + 2, // 22: bpfman.v1.GetResponse.kernel_info:type_name -> bpfman.v1.KernelProgramInfo + 3, // 23: bpfman.v1.ListResponse.ListResult.info:type_name -> bpfman.v1.ProgramInfo + 2, // 24: bpfman.v1.ListResponse.ListResult.kernel_info:type_name -> bpfman.v1.KernelProgramInfo + 12, // 25: bpfman.v1.Bpfman.Load:input_type -> bpfman.v1.LoadRequest + 14, // 26: bpfman.v1.Bpfman.Unload:input_type -> bpfman.v1.UnloadRequest + 16, // 27: bpfman.v1.Bpfman.List:input_type -> bpfman.v1.ListRequest + 18, // 28: bpfman.v1.Bpfman.PullBytecode:input_type -> bpfman.v1.PullBytecodeRequest + 20, // 29: bpfman.v1.Bpfman.Get:input_type -> bpfman.v1.GetRequest + 13, // 30: bpfman.v1.Bpfman.Load:output_type -> bpfman.v1.LoadResponse + 15, // 31: bpfman.v1.Bpfman.Unload:output_type -> bpfman.v1.UnloadResponse + 17, // 32: bpfman.v1.Bpfman.List:output_type -> bpfman.v1.ListResponse + 19, // 33: bpfman.v1.Bpfman.PullBytecode:output_type -> bpfman.v1.PullBytecodeResponse + 21, // 34: bpfman.v1.Bpfman.Get:output_type -> bpfman.v1.GetResponse + 30, // [30:35] is the sub-list for method output_type + 25, // [25:30] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name } func init() { file_bpfman_proto_init() } @@ -1953,7 +2102,7 @@ func file_bpfman_proto_init() { } } file_bpfman_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AttachInfo); i { + switch v := v.(*FentryAttachInfo); i { case 0: return &v.state case 1: @@ -1965,7 +2114,7 @@ func file_bpfman_proto_init() { } } file_bpfman_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoadRequest); i { + switch v := v.(*FexitAttachInfo); i { case 0: return &v.state case 1: @@ -1977,7 +2126,7 @@ func file_bpfman_proto_init() { } } file_bpfman_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LoadResponse); i { + switch v := v.(*AttachInfo); i { case 0: return &v.state case 1: @@ -1989,7 +2138,7 @@ func file_bpfman_proto_init() { } } file_bpfman_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UnloadRequest); i { + switch v := v.(*LoadRequest); i { case 0: return &v.state case 1: @@ -2001,7 +2150,7 @@ func file_bpfman_proto_init() { } } file_bpfman_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UnloadResponse); i { + switch v := v.(*LoadResponse); i { case 0: return &v.state case 1: @@ -2013,7 +2162,7 @@ func file_bpfman_proto_init() { } } file_bpfman_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListRequest); i { + switch v := v.(*UnloadRequest); i { case 0: return &v.state case 1: @@ -2025,7 +2174,7 @@ func file_bpfman_proto_init() { } } file_bpfman_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListResponse); i { + switch v := v.(*UnloadResponse); i { case 0: return &v.state case 1: @@ -2037,7 +2186,7 @@ func file_bpfman_proto_init() { } } file_bpfman_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PullBytecodeRequest); i { + switch v := v.(*ListRequest); i { case 0: return &v.state case 1: @@ -2049,7 +2198,7 @@ func file_bpfman_proto_init() { } } file_bpfman_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PullBytecodeResponse); i { + switch v := v.(*ListResponse); i { case 0: return &v.state case 1: @@ -2061,7 +2210,7 @@ func file_bpfman_proto_init() { } } file_bpfman_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRequest); i { + switch v := v.(*PullBytecodeRequest); i { case 0: return &v.state case 1: @@ -2073,6 +2222,30 @@ func file_bpfman_proto_init() { } } file_bpfman_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PullBytecodeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bpfman_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bpfman_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetResponse); i { case 0: return &v.state @@ -2084,7 +2257,7 @@ func file_bpfman_proto_init() { return nil } } - file_bpfman_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + file_bpfman_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListResponse_ListResult); i { case 0: return &v.state @@ -2105,24 +2278,26 @@ func file_bpfman_proto_init() { file_bpfman_proto_msgTypes[3].OneofWrappers = []interface{}{} file_bpfman_proto_msgTypes[7].OneofWrappers = []interface{}{} file_bpfman_proto_msgTypes[8].OneofWrappers = []interface{}{} - file_bpfman_proto_msgTypes[9].OneofWrappers = []interface{}{ + file_bpfman_proto_msgTypes[11].OneofWrappers = []interface{}{ (*AttachInfo_XdpAttachInfo)(nil), (*AttachInfo_TcAttachInfo)(nil), (*AttachInfo_TracepointAttachInfo)(nil), (*AttachInfo_KprobeAttachInfo)(nil), (*AttachInfo_UprobeAttachInfo)(nil), + (*AttachInfo_FentryAttachInfo)(nil), + (*AttachInfo_FexitAttachInfo)(nil), } - file_bpfman_proto_msgTypes[10].OneofWrappers = []interface{}{} - file_bpfman_proto_msgTypes[14].OneofWrappers = []interface{}{} - file_bpfman_proto_msgTypes[19].OneofWrappers = []interface{}{} - file_bpfman_proto_msgTypes[25].OneofWrappers = []interface{}{} + file_bpfman_proto_msgTypes[12].OneofWrappers = []interface{}{} + file_bpfman_proto_msgTypes[16].OneofWrappers = []interface{}{} + file_bpfman_proto_msgTypes[21].OneofWrappers = []interface{}{} + file_bpfman_proto_msgTypes[27].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_bpfman_proto_rawDesc, NumEnums: 0, - NumMessages: 26, + NumMessages: 28, NumExtensions: 0, NumServices: 1, }, diff --git a/clients/gobpfman/v1/bpfman_grpc.pb.go b/clients/gobpfman/v1/bpfman_grpc.pb.go index ddf00d0e2..1dfd13913 100644 --- a/clients/gobpfman/v1/bpfman_grpc.pb.go +++ b/clients/gobpfman/v1/bpfman_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v3.19.4 +// - protoc v3.19.6 // source: bpfman.proto package v1 diff --git a/csi/Cargo.toml b/csi/Cargo.toml index e89aeca83..a9d4e3575 100644 --- a/csi/Cargo.toml +++ b/csi/Cargo.toml @@ -1,9 +1,9 @@ [package] description = "gRPC bindings to the CSI spec" -edition = "2021" -license = "Apache-2.0" +edition.workspace = true +license.workspace = true name = "bpfman-csi" -repository = "https://github.com/bpfman/bpfman" +repository.workspace = true version = "1.8.0" [dependencies] diff --git a/docs/blog/.authors.yml b/docs/blog/.authors.yml index 8dd4539a3..24640445b 100644 --- a/docs/blog/.authors.yml +++ b/docs/blog/.authors.yml @@ -1,7 +1,7 @@ authors: anfredette: name: Andre Fredette - description: Senior Principal Software Engineer at Red Hat. Maintainer on bpfman project. + description: Senior Principal Software Engineer at Red Hat. Maintainer on bpfman project. avatar: https://github.com/anfredette.png shaneutt: name: Shane Utt @@ -11,3 +11,15 @@ authors: name: Dave Tucker description: Senior Principal Software Engineer at Red Hat. Creator of the bpfman project. avatar: https://github.com/dave-tucker.png + billy99: + name: Billy McFall + description: Senior Principal Software Engineer at Red Hat. Maintainer on bpfman project. + avatar: https://github.com/billy99.png + astoycos: + name: Andrew Stoycos + description: Senior Software Engineer at Red Hat. Maintainer on bpfman project. + avatar: https://github.com/astoycos.png + maryamtahhan: + name: Maryam Tahhan + description: Principal Engineer at Red Hat. Working on AF_XDP and integration into Kubernetes. + avatar: https://github.com/maryamtahhan.png diff --git a/docs/blog/posts/a-new-logo.md b/docs/blog/posts/a-new-logo.md index 0b3d6ed66..9bad9db83 100644 --- a/docs/blog/posts/a-new-logo.md +++ b/docs/blog/posts/a-new-logo.md @@ -7,8 +7,10 @@ authors: # A New Logo: Using Generative AI, of course Since we renamed the project to `bpfman` we are in need of a new logo. -Given that the tech buzz around Generative AI is infection, we decided to use -generative AI to create our new logo. +Given that the tech buzz around Generative AI is infectious, we decided to +explore using generative AI to create our new logo. What we found was that +it was a great way to generate ideas, but a human (me) was still needed to +create the final design. @@ -86,23 +88,80 @@ I tried a few different prompts, but the one that worked best was: ![fourth attempt](./img/2021-11-25/bpfman-logo-4.png) -The bottom right was exactly what I was looking for! +The bottom right was exactly what I had in mind! +With a little bit of post-processing, I ended up with this: -## The Result +![fifth attempt](./img/2021-11-25/bpfman-logo-5.png) -I took the bottom right image, converted it to SVG, removed the background, -and added the text. +Now it was time to solicit some feedback. -Here's the final result: +## Gathering Feedback + +After showing the logo to a few others, we decided that the bees +were infact too stern. At this point we had a few options, like reverting +back to our cute bees, however, this section in the [Bing Image Creator Terms of Service] +was pointed out to me: + +> **Use of Creations.** Subject to your compliance with this Agreement, +> the Microsoft Services Agreement, and our Content Policy, you may use +> Creations outside of the Online Services for any legal personal, +> non-commercial purpose. + +This means that we can't use the AI generated images for our logo. + +## Was it all for nothing? + +Was it all for nothing? No! We learnt a lot from this process. + +Generative AI is great for generating ideas. Some of the logo compositions +produced were great! + +It was also very useful to adjust the prompt based on feedback from team +members so we could incorporate their ideas into the design. + +We also learnt that the AI is not great at text, so we should avoid using it +for that. + +And finally, we learnt that we can't use the AI generated images for our logo. +Well, not with the generator we used anyway. + +## The (Semi) Final Design Process + +I started from scratch, taking inspiration from the AI generated images. +The bees were drawn first and composed around a hive - as our AI overlords +suggested. I then added the text, and colours, but it still felt like it was +missing something. + +What if we added a force field around the hive? That might be cool! +And so, I added a force field around the hive and played around with the +colours until I was happy. + +Here's what we ended up with: + +![semifinal result](./img/2021-11-25/bpfman-logo-semifinal.png) + +We consulted a few more people and got some feedback. The general consensus +was that the logo was too busy... However, the reception to the force field +was that the favicon I'd mocked would work better as the logo. + +## The Final Design + +Here's the final design: ![final result](./img/2021-11-25/bpfman-logo-final.png) -## Conclusion +Pretty cool, right? Even if I do say so myself. + +Our mascot is a queen bee, because she's the manager of the hive. -I'm really happy with the result! It was significantly easier than trying to -draw the bees myself, and I think it looks great! What do you think? +The force field, is now no longer a force field - It's a pheramone cloud +that represents the Queen Mandibular Pheromone (QMP) that the queen bee +produces to keep the hive organized. + +## Conclusion -We're not quite ready to replace the logo on the website yet, but we will soon! -So if you have opinions, now is the time to share them on the [Slack]. +I'm really happy with the result! I'm not a designer, so I'm sure there are +things that could be improved, but I think it's a good start. +What do you think? Join us on [Slack] and let us know! [Slack]: https://kubernetes.slack.com/archives/C04UJBW2553 diff --git a/docs/blog/posts/af_xdp-dp-integration.md b/docs/blog/posts/af_xdp-dp-integration.md new file mode 100644 index 000000000..21c689f96 --- /dev/null +++ b/docs/blog/posts/af_xdp-dp-integration.md @@ -0,0 +1,250 @@ +--- +date: 2024-02-27 +authors: + - maryamtahhan +--- +# bpfman's Integration with the AF_XDP Device Plugin and CNI for Kubernetes + +[AF_XDP] is an address/socket family that is optimized for high performance packet processing. +It takes advantage of [XDP] (an in Kernel fastpath), which essentially runs an eBPF +program as early as possible on a network driver's receive path, and redirects the +packet to an AF_XDP socket. + +![AF_XDP](./img/2024-02-27/AF_XDP-overview.png) + +AF_XDP sockets (XSKs) are created in Userspace and have a 1:1 mapping with netdev queues. +An XSKMAP is an eBPF map of AF_XDP sockets for a particular netdev. It's a simple key:value +map where the key is the netdev's queue-id and the value is the AF_XDP socket that's attached +to that queue. The eBPF program (at the XDP hook) will leverage the XSKMAP and the XDP_REDIRECT +action to redirect packets to an AF_XDP socket. In the image below the XDP program is redirecting +an incoming packet to the XSK attached to Queue 2. + +> **NOTE**: If no XSK is attached to a queue, the XDP program will simply pass the packet to the +Kernel Network Stack. + +```sh ++---------------------------------------------------+ +| XSK A | XSK B | XSK C |<---+ Userspace +=========================================================|========== +| Queue 0 | Queue 1 | Queue 2 | | Kernel space ++---------------------------------------------------+ | +| Netdev eth0 | | ++---------------------------------------------------+ | +| +=============+ | | +| | key | xsk | | | +| +---------+ +=============+ | | +| | | | 0 | xsk A | | | +| | | +-------------+ | | +| | | | 1 | xsk B | | | +| | BPF | +-------------+ | | +| | prog |-- redirect -->| 2 | xsk C |-------------+ +| | (XDP | +-------------+ | +| | HOOK) | xskmap | +| | | | +| +---------+ | +| | ++---------------------------------------------------+ +``` + +The [AF_XDP Device Plugin and CNI] project provides the Kubernetes components to +provision, advertise and manage AF_XDP networking devices for Kubernetes pods. These +networking devices are typically used as a Secondary networking interface for a pod. +A key goal of this project is to enable pods to run without any special privileges, +without it pods that wish to use AF_XDP will need to run with elevated privileges +in order to manage the eBPF program on the interface. The infrastructure +will have little to no control over what these pods can load. Therefore it's ideal +to leverage a central/infrastructure centric eBPF program management approach. This blog +will discuss the eBPF program management journey for the AF_XDP Device Plugin and CNI. + +[AF_XDP]:https://www.kernel.org/doc/html/next/networking/af_xdp.html +[XDP]: https://docs.cilium.io/en/latest/bpf/progtypes/#xdp +[AF_XDP Device Plugin and CNI]: https://github.com/intel/afxdp-plugins-for-kubernetes + + + +## What does the AF_XDP Device Plugin and CNI do? + +For pods to create and use AF_XDP sockets on their interfaces, they can either: + +1. Create the AF_XDP socket on an interface already plumbed to the Pod (via SR-IOV + Device Plugin and the Host CNI) --> But this requires CAP_BPF or CAP_SYS_ADMIN + privileges in order to load the BPF program on the netdev. + + OR + +2. Use the AF_XDP Device Plugin (DP) and CNI in order to support a Pod without the + aforementioned root like privileges. + + > **NOTE**: Prior to kernel 5.19, all BPF sys calls required CAP_BPF, which are + used to access maps shared between the BPF program and the userspace program. + In kernel 5.19, a change went in that only requires CAP_BPF for map creation + (BPF_MAP_CREATE) and loading programs (BPF_PROG_LOAD). + + In this scenario, the ``AF_XDP DP``, will advertise resource pools (of netdevs) to + ``Kubelet``. When a Pod requests a resource from these pools, ``Kubelet`` will + `Allocate()` one of these devices through the ``AF_XDP DP``. The ``AF_XDP DP`` will load the eBPF + program (to redirect packets to an AF_XDP socket) on the allocated device. + + The default behaviour of the ``AF_XDP DP`` (unless otherwise configured) is to take note of the + XSKMAP File Descriptor (FD) for that netdev. It will also mount a Unix Domain Socket + (UDS), as a hostpath mount, in the Pod. This UDS will be used by the AF_XDP application + to perform a handshake with the ``AF_XDP DP`` to retrieve the XSKMAP FD. The application needs + the XSKMAP FD to "attach" AF_XDP sockets it creates to the netdev queues. + + > **NOTE**: Newer versions of the ``AF_XDP DP`` support eBPF map pinning which eliminate the + need to perform this (non trivial) handshake with AF_XDP pods. It now mounts the + pinned XSKMAP into the Pod using a hostpath mount. The downside of this approach is that + the ``AF_XDP DP`` now needs to manage several eBPF File Systems (BPFFS), one per pod. + + The ``AF_XDP CNI`` (like any CNI) has the task of moving the netdev (with the loaded eBPF + program) into the Pod namespace. It also does a few other important things: + + - It does not rename the netdev (to allow the DP to avoid IF_INDEX clashes as it manages + the AF_XDP resource pools). + - The CNI is also capable of configuring hardware filters on the NIC. + - Finally, the CNI also unloads the eBPF program from the netdev and clear any hardware + filters when the Pod is terminated. + + > **NOTE 1**: The ``AF_XDP CNI`` manages the unloading of the eBPF program due to the ``AF_XDP DP`` + not being aware of when a pod terminates (it's only invoked by ``Kubelet`` during pod creation). + + > **NOTE 2**: Prior to bpfman integration, the CNI was extended to signal the AF_XDP DP on pod + termination (via gRPC) in an effort to support eBPF map pinning directly in the AF_XDP DP. The + AF_XDP DP was managing BPFFS(es) for map pinning and needed to be signalled to clean them up. + +### bpfman Integration + +Prior to bpfman integration the AF_XDP Device Plugin and CNI managed the eBPF program +for redirecting incoming packets to AF_XDP sockets, its associated map (XSKMAP), and/or several BPFFS. + +#### Integration benefits + +So what are the benefits of bpfman integration for the AF_XDP DP and CNI? + +- Removes code for loading and managing eBPF from the AF_XDP DP and CNI codebase. + + - This presented a difficulty particularly when trying to find/update appropriate + base container images to use for the AF_XDP device plugin. Different images + supported different versions of eBPF management libraries (i.e libbpf or libxdp) which + forced multiple changes around the loading and attaching of the base eBPF program. + + - Additionally the CNI runs as a binary on the Kubernetes node so we would need to + statically compile libbpf/libxdp as part of the CNI. + +- More diverse XDP program support through bpfman's eBPF Bytecode Image Specification. Not + only do the AF_XDP eBPF programs no longer need to be stored in the Device Plugin + itself, but it's now configurable on a per pool basis. + +- No longer required to leverage Hostpath volume mounts to mount the AF_XDP maps inside + a Pod. But rather take advantage of the bpfman CSI support to ensure that maps are + pinned in the context of the Pod itself and not in a BPFFS on the host (then shared + to the Pod). + +#### AF_XDP Device Plugin eBPF program/map management + +The role of the ``AF_XDP DP`` in eBPF program/map management **prior to bpfman integration**: + +- Loads the default AF_XDP BPF prog onto the netdev at Pod creation and manages info +regarding the XSKMAP for that netdev. + +- Mounts a UDS as a hostpath volume in the Pod OR creates a BPFFS per netdev and pins the + XSKMAP to it, then mounts this BPFFS as a hostpath volume in the Pod. + +- Shares the XSKMAP file descriptor via UDS (involves a handshake with the Pod). + +The role of the ``AF_XDP DP`` in eBPF program/map management **after bpfman integration**: + +- Uses bpfman's client APIs to load the BPF prog. + +- Shares the XSKMAP (that bpfman pinned ) with the Pod as a hostpath volume. + +#### AF_XDP CNI eBPF program/map management + +The role of the ``AF_XDP CNI`` in eBPF program/map management **prior to bpfman integration**: + +- Unloads the eBPF program when a device is returned to the Host network namespace. + +The role of the ``AF_XDP CNI`` in eBPF program/map management **after bpfman integration**: + +- Uses gRPC to signal to the Device Plugin to request bpfman to unload the eBPF program + using the client APIs. + +## Is there a working example? + +The bpfman integration with the AF_XDP Device Plugin and CNI was demo'ed as part +of a series of demos that show the migration of a DPDK application to AF_XDP (without) +any application modification. The demo can be watched below: + +[](https://www.youtube.com/embed/wcmSO5HwNJQ) + +## AF_XDP DP and CNI's integration with bpfman in images + +The following sections will present the evolution of the AF_XDP DP and CNI +from independent eBPF program management to leveraging bpfman to manage eBPF programs on +their behalf. + +### AF_XDP DP and CNI managing eBPF programs independently + +The following diagram details how the AF_XDP DP and CNI worked prior to bpfman integration. + +![AF_XDP DP and CNI managing eBPF programs independently](./img/2024-02-27/af_xdp.png) + +1. Setup Subfunctions on the network devices (if the are supported/being used). + +2. Create an AF_XDP DP and CNI configuration file to setup the device resource pools and + deploy the DP and CNI. + +3. When the AF_XDP DP runs it will discover the netdevs on the host and create the resource pools. + +4. The AF_XDP DP registers the resource pools with Kubelet. + +5. When a pod (that requests an AF_XDP resource) is started, Kubelet will send an `Allocate()` + request to the AF_XDP DP. The AF_XDP DP loads the eBPF program on the interface and mounts the + UDS in the pod and sets some environment variables in the pod using the Downward API. + + > **NOTE**: In the case where eBPF map pinning is used rather than the UDS, the AF_XDP + DP will create a BPFFS where it pins the XSKMAP and mounts the BPFFS as a hostpath volume + in the pod. + +6. The AF_XDP DP signals success to the Kubelet so that the device is added to the pod. + +7. Kubelet triggers multus, which in turn triggers the AF_XDP CNI. The CNI does the relevant network + configuration and moves the netdev into the pod network namespace. + +8. The application in the pod start and initiates a handshake with the AF_XDP DP over the mounted UDS + to retrieve the XSKMAP FD. + +### AF_XDP DP and CNI integrated with bpfman (no csi) + +The following diagram details how the AF_XDP DP and CNI worked after bpfman integration. + +![AF_XDP DP and CNI integrated with bpfman (no csi)](./img/2024-02-27/af_xdp-bpfman-no-csi.png) + +The main difference here is that when the `Allocate()` request comes in from Kubelet, the AF_XDP +DP uses the bpfman client API to load the eBPF program on the relevant netdev. It takes note of +where bpfman pins the XSKMAP and mounts this directory as a hostpath volume in the pod. + +### AF_XDP DP and CNI integrated with bpfman (with csi) + +The following diagram details how the AF_XDP DP and CNI will work with bpfman leveraging +the new CSI implementation. + +![AF_XDP DP and CNI integrated with bpfman (with csi)](./img/2024-02-27/af_xdp-bpfman-csi.png) + +The pod will include a volume definition as follows: + +```yaml + volumes: + - name: bpf-maps + csi: + driver: csi.bpfman.dev + volumeAttributes: + csi.bpfman.dev/thru-annotations: true +``` + +The idea here is when the `Allocate()` request comes in from Kubelet, the AF_XDP +DP uses the bpfman client API to load the eBPF program on the relevant netdev. The +AF_XDP DP will annotate the pod with the XdpProgram name, map and mountpath. When the +bpfman CSI plugin is triggered by Kubelet, it will retrieve the information it needs +from the pod annotations in order to pin the map inside the Pod. diff --git a/docs/blog/posts/community/2024/2024-01-04-meeting.md b/docs/blog/posts/community/2024/2024-01-04-meeting.md new file mode 100644 index 000000000..630c80e33 --- /dev/null +++ b/docs/blog/posts/community/2024/2024-01-04-meeting.md @@ -0,0 +1,98 @@ +--- +date: 2024-01-04 +authors: + - billy99 +categories: + - Community Meeting + - "2024" +--- + +# Community Meeting: January 4, 2024 + +## Welcome to 2024! + +Welcome to the first `bpfman` Community Meeting of 2024. +We are happy to start off a new year and excited for all the changes in store +for `bpfman` in 2024! + +Below were some of the discussion points from this weeks Community Meeting. + +* bpfman-csi Needs To Become Its Own Binary +* Kubernetes Support For Attaching uprobes In Containers +* Building The Community + + + +## bpfman-csi Needs To Become Its Own Binary + +Some of the next work items for `bpfman` revolve around removing the async code +from the code base, make `bpfman-core` a rust library, and removing all the +gRPC logic. +Dave ([@dave-tucker]) is currently investigating this. +One area to help out is to take the `bpfman-csi` thread and making it it's own +binary. +This may require making `bpfman` a bin and lib crate (which is fine, just needs +a lib.rs and to be very careful about what we’re exporting). +Andrew ([@astoycos]) is starting to take a look at this. + +## Kubernetes Support For Attaching uprobes In Containers + +Base support for attaching uprobes in containers is currently merged. +Andre ([@anfredette]) pushed [PR#875] for the integration with Kubernetes. +The hard problems are solved, like getting the Container PID, but the current +PR has some shortcuts to get the functionality working before the holiday +break. +So the [PR#875] is not ready for review, but Dave ([@dave-tucker]) and +Andre ([@anfredette]) may have a quick review to verify the design principles. + +[PR#875]: https://github.com/bpfman/bpfman/pull/875 + +## Building The Community + +Short discussion on building the Community. +In a previous meeting, Dave ([@dave-tucker]) suggested capturing the meeting minutes +in blogs. +By placing in a blog, they become searchable from search engines. +Billy ([@billy99]) re-raised this topic and volunteered to start capturing the content. +In future meetings, we may use the transcript feature from Google Meet to +capture the content and try generating the blog via ChatGTP. + +## Light-hearted Moments and Casual Conversations + +Amidst the technical discussions, the community members took a moment to share +some light-hearted moments and casual conversations. +Topics ranged from the challenges of post-holiday credit card bills to the +complexities of managing family schedules during exam week. +The discussion touched on the quirks of public school rules and the unique +challenges of parenting during exam periods. + +The meeting ended on a friendly note, with plans for further collaboration and +individual tasks assigned for the upcoming days. +Participants expressed their commitment to pushing updates and improvements, +with a promise to reconvene in the near future. + +## Attendees + +* Andre Fredette (Red Hat) +* Andrew Stoycos (Red Hat) +* Billy McFall (Red Hat) +* Dave Tucker (Red Hat) + +[@anfredette]: https://github.com/anfredette +[@astoycos]: https://github.com/astoycos +[@billy99]: https://github.com/billy99 +[@dave-tucker]: https://github.com/dave-tucker + +# bpfman Community Info + +A friendly reminder that the Community Meetings are every Thursday 10am-11am +Eastern US Time and all are welcome! + +Google Meet joining info: + +* [Google Meet] +* Or dial: (US) +1 984-221-0859 PIN: 613 588 790# +* [Agenda Document] + +[Google Meet]: https://meet.google.com/ggz-zkmp-pxx +[Agenda Document]: https://docs.google.com/document/d/17l96_3NMOQS-1a3gfJPPcTnhTrqJb3KWYxFWbRgD-yk/edit diff --git a/docs/blog/posts/community/2024/2024-01-19-meeting.md b/docs/blog/posts/community/2024/2024-01-19-meeting.md new file mode 100644 index 000000000..a62690e44 --- /dev/null +++ b/docs/blog/posts/community/2024/2024-01-19-meeting.md @@ -0,0 +1,138 @@ +--- +date: 2024-01-19 +authors: + - billy99 +categories: + - Community Meeting + - "2024" +--- + +# Community Meeting: January 11 and 18, 2024 + +## Hit the Ground Running + +Another set of `bpfman` Community Meetings for 2024. +There is a lot going on with `bpfman` in Q1 of 2024. +Spending a lot of time making `bpfman` [daemonless]. +I bailed for a ski trip after the Jan 11 meeting, so the notes didn't get written up. +So this summary will include two weeks of meetings. + +Below were some of the discussion points from the last two weeks Community Meetings. + +* Manpage/CLI TAB Completion Questions (Jan 11) +* Kubernetes Support for Attaching uprobes in Containers (Jan 11) +* netify Preview in Github Removed (Jan 11) +* RPM Builds and Socket Activation (Jan 18) +* KubeCon EU Discussion (Jan 18) + + + +[daemonless]: https://bpfman.io/main/design/daemonless/ + +## January 11, 2024 + +### Manpage/CLI TAB Completion Questions (Jan 11) + +The `bpfman` CLI now has TAB Completion and man pages. +However, a couple nits need to be cleaned up [Issue#913] and Billy ([@billy99]) +wanted to clarify a few issues encountered. +The current implementation for both features is using an environment variable to +set the destination directory for the generated files. +Other features don't work this way and there was a discussion on the proper location +for the generated files. +The decision was to use `.output/.`. + +There was another discussion around `clap` (Rust CLI crate) and passing variables +to `clap` from the Cargo.toml file. +In the CLI code, `#[command(author, version, about, long_about = None)]` implies +to pull the values from the Config.toml file, but we aren’t setting any of those +variables. +Also, for `cargo xtask build-man-page` and `cargo xtask build-completion` they pull +from the xtask Cargo.toml file. +The decision was to set the variables implicitly in code and not pull from Cargo.toml. + +[Issue#913]: https://github.com/bpfman/bpfman/issues/913 + +### Kubernetes Support for Attaching uprobes in Containers (Jan 11) + +Andre ([@anfredette]) is working on a feature to enable attaching uprobes in other +Containers. +Currently, `bpfman` only supports attaching uprobes within the `bpfman` container. +There was a discussion on proper way to format a query to the KubeAPI server to +match on NodeName on a Pod list. +The discussion included so code walk through. +Andrew ([@astoycos]) found a possible solution [client-go:Issue#410] and +Dave ([@dave-tucker]) suggested [kubernetes-api:podspec-v1-core]. + +[client-go:Issue#410]: https://github.com/kubernetes/client-go/issues/410 +[kubernetes-api:podspec-v1-core]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#podspec-v1-core + +### netify Preview in Github Removed (Jan 11) + +Lastly, there was a discussion on the `netify` preview being removed from github +and a reminder why. +Dave ([@dave-tucker]) explained that with the docs release history now in place, +"current" is from a branch and it is not easy to preview. +So for now, document developers need to run mkdocs locally (See [generate-documention]). + +[generate-documention]: https://bpfman.io/main/developer-guide/documentation/#generate-documentation + +### Attendees (Jan 11) + +* Andre Fredette (Red Hat) +* Andrew Stoycos (Red Hat) +* Billy McFall (Red Hat) +* Dave Tucker (Red Hat) +* Shane Utt (Kong) + +## January 18, 2024 + +### RPM Builds and Socket Activation (Jan 18) + +RPM Builds for `bpfman` went in fairly recently and Billy ([@billy99]) +had some questions around their implementation. +RPM and Socket Activation were developed and merged around the same +time and the RPM builds are not installing socket activation properly. +Just verifying that RPMs should be installing the `bpfman.socket` file. +And they should. +There were also some questions on how to build RPMs locally. +Verified that `packit build locally` is the way forward. + +> **Note:** Socket activation was added to RPM Builds along with documentation +> on building and using RPMs in [PR#922] + +[PR#922]: https://github.com/bpfman/bpfman/pull/922 + +### KubeCon EU Discussion (Jan 18) + +With KubeCon EU just around the corner (March 19-22, 2024 in Paris), discussion +around bpfman talks and who was attending. +Dave ([@dave-tucker]) is probably attending and Shane ([@shaneutt]) might attend. +So if you are planning on attending KubeCon EU and are interested in `bpfman` or +just eBPF, keep an eye out for these guys for some lively discussions! + +### Attendees (Jan 18) + +* Billy McFall (Red Hat) +* Dave Tucker (Red Hat) +* Shane Utt (Kong) + +[@anfredette]: https://github.com/anfredette +[@astoycos]: https://github.com/astoycos +[@billy99]: https://github.com/billy99 +[@dave-tucker]: https://github.com/dave-tucker +[@shaneutt]: https://github.com/shaneutt + +# bpfman Community Info + +A friendly reminder that the Community Meetings are every Thursday 10am-11am +Eastern US Time and all are welcome! + +Google Meet joining info: + +* [Google Meet] +* Or dial: (US) +1 984-221-0859 PIN: 613 588 790# +* [Agenda Document] + +[Google Meet]: https://meet.google.com/ggz-zkmp-pxx +[Agenda Document]: https://docs.google.com/document/d/17l96_3NMOQS-1a3gfJPPcTnhTrqJb3KWYxFWbRgD-yk/edit diff --git a/docs/blog/posts/ebpf-container-challenges.md b/docs/blog/posts/ebpf-container-challenges.md new file mode 100644 index 000000000..71ac2d13a --- /dev/null +++ b/docs/blog/posts/ebpf-container-challenges.md @@ -0,0 +1,546 @@ +--- +date: 2024-02-26 +authors: + - anfredette +--- + +# Technical Challenges for Attaching eBPF Programs in Containers + +We recently added support for attaching uprobes inside containers. The purpose +of this blog is to give a brief overview of the feature, to document the +technical challenges encountered, and describe our solutions for those +challenges. In particular, how to attach an eBPF program inside of a container, +and how to find the host Process ID (PID) on the node for the container? + +The solutions seem relatively straightforward now that they are done, but we +found limited information elsewhere, so we thought it would be helpful to +document them here. + +The uprobe implementation will be used as the example in this blog, but the +concepts can (and will eventually) be applied to other program types. + + + +## Introduction + +A "uprobe" (user probe) is a type of eBPF program that can be attached to a +specific location in a user-space application. This allows developers and system +administrators to dynamically instrument a user-space binary to inspect its +behavior, measure performance, or debug issues without modifying the +application's source code or binary. When the program execution reaches the +location to which the uprobe is attached, the eBPF program associated with the +uprobe is executed. + +bpfman support for uprobes has existed for some time. We recently extended this +support to allow users to attach uprobes inside of containers both in the +general case of a container running on a Linux server and also for containers +running in a Kubernetes cluster. + +The following is a bpfman command line example for loading a uprobe inside a +container: + +```bash +bpfman load image --image-url quay.io/bpfman-bytecode/uprobe:latest uprobe --fn-name "malloc" --target "libc" --container-pid 102745 +``` + +The above command instructs bpfman to attach a uprobe to the `malloc` function +in the `libc` library for the container with PID 102745. The main addition here +is the ability to specify a `container-pid`, which is the PID of the container +as it is known to the host server. + +The term "target" as used in the above bpfman command (and the CRD below) +describes the library or executable that we want to attach the uprobe to. The +fn-name (the name of the function within that target) and/or an explicit +"offset" can be used to identify a specific offset from the beginning of the +target. We also use the term "target" more generally to describe the intended +location of the uprobe. + +For Kubernetes, the CRD has been extended to include a "container selector" to +describe one or more containers as shown in the following example. + +```yaml +apiVersion: bpfman.io/v1alpha1 +kind: UprobeProgram +metadata: + labels: + app.kubernetes.io/name: uprobeprogram + name: uprobe-example-containers +spec: + # Select all nodes + nodeselector: {} + bpffunctionname: my_uprobe + func_name: malloc + # offset: 0 # optional offset w/in function + target: libc + retprobe: false + # pid: 0 # optional pid to execute uprobe for + bytecode: + image: + url: quay.io/bpfman-bytecode/uprobe:latest + containers: <=== New section for specifying containers to attach uprobe to + namespace: bpfman + pods: + matchLabels: + name: bpfman-daemon + containernames: + - bpfman + - bpfman-agent +``` + +In the Kubernetes case, the container selector (`containers`) is used to +identify one or more containers in which to attach the uprobe. If `containers` +identifies any containers on a given node, the bpfman agent on that node will +determine their host PIDs and make the calls to bpfman to attach the uprobes. + +## Attaching uprobes in containers + +A Linux "mount namespace" is a feature that isolates the mount points seen by a +group of processes. This means that processes in different mount namespaces can +have different views of the filesystem. A container typically has its own mount +namespace that is isolated both from those of other containers and its parent. +Because of this, files that are visible in one container are likely not visible +to other containers or even to the parent host (at least not directly). To +attach a uprobe to a file in a container, we need to have access to that +container's mount namespace so we can see the file to which the uprobe needs to +be attached. + +From a high level, attaching a uprobe to an executable or library in a container +is relatively straight forward. `bpfman` needs to change to the mount namespace +of the container, attach the uprobe to the target in that container, and then +return to our own mount namespace so that we can save the needed state and +continue processing other requests. + +The main challenges are: + +1. Changing to the mount namespace of the target container. +1. Returning to the bpfman mount namespace. +1. `setns` (at least for the mount namespace) can't be called from a + multi-threaded application, and bpfman is currently multithreaded. +1. How to find the right PID for the target container. + +### The Mount Namespace + +To enter the container namespace, `bpfman` uses the +[sched::setns](https://docs.rs/nix/latest/nix/sched/fn.setns.html) function from +the Rust [nix](https://crates.io/crates/nix) crate. The `setns` function +requires the file descriptor for the mount namespace of the target container. + +For a given container PID, the namespace file needed by the `setns` function can +be found in the `/proc//ns/` directory. An example listing for the PID +102745 directory is shown below: + +```bash +sudo ls -l /proc/102745/ns/ +total 0 +lrwxrwxrwx 1 root root 0 Feb 15 12:10 cgroup -> 'cgroup:[4026531835]' +lrwxrwxrwx 1 root root 0 Feb 15 12:10 ipc -> 'ipc:[4026532858]' +lrwxrwxrwx 1 root root 0 Feb 15 12:10 mnt -> 'mnt:[4026532856]' +lrwxrwxrwx 1 root root 0 Feb 15 12:07 net -> 'net:[4026532860]' +lrwxrwxrwx 1 root root 0 Feb 15 12:10 pid -> 'pid:[4026532859]' +lrwxrwxrwx 1 root root 0 Feb 15 12:10 pid_for_children -> 'pid:[4026532859]' +lrwxrwxrwx 1 root root 0 Feb 15 12:10 time -> 'time:[4026531834]' +lrwxrwxrwx 1 root root 0 Feb 15 12:10 time_for_children -> 'time:[4026531834]' +lrwxrwxrwx 1 root root 0 Feb 15 12:10 user -> 'user:[4026531837]' +lrwxrwxrwx 1 root root 0 Feb 15 12:10 uts -> 'uts:[4026532857]' +``` + +In this case, the mount namespace file is `/proc/102745/ns/mnt`. + +> ***NOTE:*** How to find the PID and the relationship between parent and child +> PIDs is described in the "Finding The PID" section [below](#finding-the-pid). + +When running directly on a Linux server, `bpfman` has access to the host `/proc` +directory and can access the mount namespace file for any PID. However, on +Kubernetes, `bpfman` runs in a container, so it doesn't have access to the +namespace files of other containers or the `/proc` directory of the host by +default. Therefore, in the Kubernetes implementation, `/proc` is mounted in the +`bpfman` container so it has access to the ns directories of other containers. + +### Returning to the `bpfman` Mount Namespace + +After `bpfman` does a `setns` to the target container mount namespace, it +has access to the target binary in that container. However, it only has access +to that container's view of the filesystem, and in most cases, this does not +include access to bpfman's filesystem or the host filesystem. As a result, +bpfman loses the ability to access its own mount namespace file. + +However, before calling setns, `bpfman` has access to it's own mount namespace +file. Therefore, to avoid getting stranded in a different mount namespace, +`bpfman` also opens its own mount namespace file prior to calling `setns` so it +already has the file descriptor that will allow it to call `setns` to return to +its own mount namespace. + +### Running `setns` From a Multi-threaded Process + +Calling `setns` to a mount namespace doesn't work from a multi-threaded process. + +To work around this issue, the logic was moved to a standalone single-threaded +executable called +[bpfman-ns](https://github.com/bpfman/bpfman/blob/main/bpfman-ns/src/main.rs) +that does the job of entering the namespace, attaching the uprobe, and then +returning to the bpfman namespace to save the needed info. + +### Finding the PID + +#### Finding a Host Container PID on a Linux Server + +This section provides an overview of PID namespaces and shows several ways to +find the host PID for a container. + +##### tl;dr + +If you used Podman or Docker to run your container, **and** you gave the +container a unique name, the following commands can be used to find the host PID +of a container. + +```bash +podman inspect -f '{{.State.Pid}}' +``` + +or, similarly, + +```bash +docker inspect -f '{{.State.Pid}}' +``` + +##### Overview of PID namespaces and Container Host PIDs + +Each container has a PID namespace. Each PID namespace (other than the root) is +contained within a parent PID namespace. In general, this relationship is +hierarchical and PID namespaces can be nested within other PID namespaces. In +this section, we will just cover the case of a root PID namepsace on a Linux +server that has containers with PID namespaces that are direct children of the +root. The multi-level case is described in the section on Nested Containers with +kind below. + +The PID namespaces can be listed using the `lsns -t pid` command. Before we +start any containers, we just have the one root pid namespace as shown below. + +```bash +sudo lsns -t pid + NS TYPE NPROCS PID USER COMMAND +4026531836 pid 325 1 root /usr/lib/systemd/systemd rhgb --switched-root --system --deserialize 30 +``` + +Now lets start a container with the following command in a new shell: + +```bash +podman run -it --name=container_1 fedora:latest /bin/bash +``` + +> **NOTE:** In this section, we are using `podman` to run containers. However, +> all of the same commands can also be used with `docker`. + +Now back on the host we have: + +```bash +sudo lsns -t pid + NS TYPE NPROCS PID USER COMMAND +4026531836 pid 337 1 root /usr/lib/systemd/systemd rhgb --switched-root --system --deserialize 30 +4026532948 pid 1 150342 user_abcd /bin/bash +``` + +We can see that the host PID for the container we just started is 150342. + +Now let's start another container in a new shell with the same command (except +with a different name), and run the `lsns` command again on the host. + +```bash +podman run -it --name=container_2 fedora:latest /bin/bash +``` + +On the host: + +```bash +sudo lsns -t pid + NS TYPE NPROCS PID USER COMMAND +4026531836 pid 339 1 root /usr/lib/systemd/systemd rhgb --switched-root --system --deserialize 30 +4026532948 pid 1 150342 user_abcd /bin/bash +4026533041 pid 1 150545 user_abcd /bin/bash +``` + +We now have 3 pid namespaces -- one for root and two for the containers. Since +we already know that the first container had PID 150342 we can conclude that the +second container has PID 150545. However, what would we do if we didn't already +know the PID for one of the containers? + +If the container we were interested in was running a unique command, we could +use that to disambiguate. However, in this case, both are running the same +`/bin/bash` command. + +If something unique is running inside of the container, we can use the `ps -e -o +pidns,pid,args` command to get some info. + +For example, run `sleep 1111` in `container_1`, then + +```bash +sudo ps -e -o pidns,pid,args | grep 'sleep 1111' +4026532948 150778 sleep 1111 +4026531836 151002 grep --color=auto sleep 1111 +``` + +This tells us that the `sleep 1111` command is running in PID namespace +4026532948. And, + +```bash +sudo lsns -t pid | grep 4026532948 +4026532948 pid 2 150342 user_abcd /bin/bash +``` + +Tells us that the container's host PID is 150342. + +Alternatively, we could run `lsns` inside of `container_1`. + +```bash +dnf install -y util-linux +lsns -t pid + NS TYPE NPROCS PID USER COMMAND +4026532948 pid 2 1 root /bin/bash +``` + +This tells us a few interesting things. + +1. Inside the container, the PID is 1, +1. We can't see any of the other PID namespaces inside the container. +1. The container PID namespace is 4026532948. + +With the container PID namespace, we can run the `lsns -t pid | grep 4026532948` +command as we did above to find the container's host PID + +Finally, the container runtime knows the pid mapping. As mentioned at the +beginning of this section, if the unique name of the container is known, the +following command can be used to get the host PID. + +```bash +podman inspect -f '{{.State.Pid}}' container_1 +150342 +``` + +### How bpfman Agent Finds the PID on Kubernetes + +When running on Kubernetes, the "containers" field in the UprobeProgram CRD can +be used to identify one or more containers using the following information: + +- Namespace +- Pod Label +- Container Name + +If the container selector matches any containers on a given node, the +`bpfman-agent` determines the host PID for those containers and then calls +`bpfman` to attach the uprobe in the container with the given PID. + +From what we can tell, there is no way to find the host PID for a container +running in a Kubernetes pod from the Kubernetes interface. However, the +[container runtime](https://kubernetes.io/docs/concepts/architecture/cri/) does +know this mapping. + +The `bpfman-agent` implementation uses multiple steps to find the set of PIDs on +a given node (if any) for the containers that are identified by the container +selector. + +1. It uses the Kubernetes interface to get a list of pods on the local node that + match the container selector. +1. It uses use [crictl] with the names of the pods found to get the pod IDs +1. It uses `crictl` with the pod ID to find the containers in those pods and + then checks whether any match the container selector. +1. Finally, it uses `crictl` with the pod IDs found to get the host PIDs for the + containers. + +[crictl]:https://kubernetes.io/docs/tasks/debug/debug-cluster/crictl/ + +As an example, the [bpfman.io_v1alpha1_uprobe_uprobeprogram_containers.yaml] +file can be used with the `kubectl apply -f` command to install uprobes on two +of the containers in the `bpfman-agent` pod. The bpfman code does this +programmatically, but we will step through the process of finding the host PIDs +for the two containers here using cli commands to demonstrate how it works. + +We will use a [kind](https://kind.sigs.k8s.io/) deployment with bpfman for this +demo. See [Deploy Locally via KIND] for instructions on how to get this running. + +[bpfman.io_v1alpha1_uprobe_uprobeprogram_containers.yaml]:https://github.com/bpfman/bpfman/blob/main/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram_containers.yaml +[Deploy Locally via KIND]:../../developer-guide/operator-quick-start.md#deploy-locally-via-kind + +The container selector in the above yaml file is the following. + +```yaml + containers: + namespace: bpfman + pods: + matchLabels: + name: bpfman-daemon + containernames: + - bpfman + - bpfman-agent +``` + +`bpfman` accesses the Kubernetes API and uses `crictl` from the `bpfman-agent` +container. However, the `bpfman-agent` container doesn't have a shell by +default, so we will run the examples from the `bpfman-deployment-control-plane` +node, which will yield the same results. `bpfman-deployment-control-plane` is a +docker container in our kind cluster, so enter the container. + +```bash +docker exec -it c84cae77f800 /bin/bash +``` +Install `crictl`. + +```bash +apt update +apt install wget +VERSION="v1.28.0" +wget https://github.com/kubernetes-sigs/cri-tools/releases/download/$VERSION/crictl-$VERSION-linux-amd64.tar.gz +tar zxvf crictl-$VERSION-linux-amd64.tar.gz -C /usr/local/bin +rm -f crictl-$VERSION-linux-amd64.tar.gz +``` + +First use `kubectl` to get the list of pods that match our container selector. + +```bash +kubectl get pods -n bpfman -l name=bpfman-daemon +NAME READY STATUS RESTARTS AGE +bpfman-daemon-cv9fm 3/3 Running 0 6m54s +``` + +> ***NOTE:*** The bpfman code also filters on the local node, but we only have +> one node in this deployment, so we'll ignore that here. + +Now, use `crictl` with the name of the pod found to get the pod ID. + +```bash +crictl pods --name bpfman-daemon-cv9fm +POD ID CREATED STATE NAME NAMESPACE ATTEMPT RUNTIME +e359900d3eca5 46 minutes ago Ready bpfman-daemon-cv9fm bpfman 0 (default) +``` + +Now, use the pod ID to get the list of containers in the pod. + +```bash +crictl ps --pod e359900d3eca5 +CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD +5eb3b4e5b45f8 50013f94a28d1 48 minutes ago Running node-driver-registrar 0 e359900d3eca5 bpfman-daemon-cv9fm +629172270a384 e507ecf33b1f8 48 minutes ago Running bpfman-agent 0 e359900d3eca5 bpfman-daemon-cv9fm +6d2420b80ddf0 86a517196f329 48 minutes ago Running bpfman 0 e359900d3eca5 bpfman-daemon-cv9fm +``` + +Now use the container IDs for the containers identified in the container +selector to get the PIDs of the containers. + +```bash +# Get PIDs for bpfman-agent container +crictl inspect 629172270a384 | grep pid + "pid": 2158, + "pid": 1 + "type": "pid" + +# Get PIDs for bpfman container +crictl inspect 6d2420b80ddf0 | grep pid + "pid": 2108, + "pid": 1 + "type": "pid" +``` + +From the above output, we can tell that the host PID for the `bpfman-agent` +container is 2158, and the host PID for the `bpfman` container is 2108. So, now +`bpfman-agent` would have the information needed to call `bpfman` with a request +to install a uprobe in the containers. + +### Nested Containers with kind + +kind is a tool for running local Kubernetes clusters using Docker container +“nodes”. The kind cluster we used for the previous section had a single node. + +```bash +$ kubectl get nodes +NAME STATUS ROLES AGE VERSION +bpfman-deployment-control-plane Ready control-plane 24h v1.27.3 +``` + +We can see the container for that node on the base server from Docker as +follows. + +```bash +docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +c84cae77f800 kindest/node:v1.27.3 "/usr/local/bin/entr…" 25 hours ago Up 25 hours 127.0.0.1:36795->6443/tcp bpfman-deployment-control-plane +``` + +Our cluster has a number of pods as shown below. + +```bash +kubectl get pods -A +NAMESPACE NAME READY STATUS RESTARTS AGE +bpfman bpfman-daemon-cv9fm 3/3 Running 0 24h +bpfman bpfman-operator-7f67bc7c57-bpw9v 2/2 Running 0 24h +kube-system coredns-5d78c9869d-7tw9b 1/1 Running 0 24h +kube-system coredns-5d78c9869d-wxwfn 1/1 Running 0 24h +kube-system etcd-bpfman-deployment-control-plane 1/1 Running 0 24h +kube-system kindnet-lbzw4 1/1 Running 0 24h +kube-system kube-apiserver-bpfman-deployment-control-plane 1/1 Running 0 24h +kube-system kube-controller-manager-bpfman-deployment-control-plane 1/1 Running 0 24h +kube-system kube-proxy-sz8v9 1/1 Running 0 24h +kube-system kube-scheduler-bpfman-deployment-control-plane 1/1 Running 0 24h +local-path-storage local-path-provisioner-6bc4bddd6b-22glj 1/1 Running 0 24h +``` + +Using the `lsns` command in the node's docker container, we can see that it has +a number of PID namespaces (1 for each container that is running in the pods in +the cluster), and all of these containers are nested inside of the docker "node" +container shown above. + +```bash +lsns -t pid + NS TYPE NPROCS PID USER COMMAND +# Note: 12 rows have been deleted below to save space +4026532861 pid 17 1 root /sbin/init +4026532963 pid 1 509 root kube-scheduler --authentication-kubeconfig=/etc/kubernetes/scheduler.conf --authorization-kubeconfig=/etc/kubernetes/scheduler.conf --bind-addre +4026532965 pid 1 535 root kube-controller-manager --allocate-node-cidrs=true --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf --authorization-kubeconfi +4026532967 pid 1 606 root kube-apiserver --advertise-address=172.18.0.2 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/etc/kubernetes/pki/ca.crt +4026532969 pid 1 670 root etcd --advertise-client-urls=https://172.18.0.2:2379 --cert-file=/etc/kubernetes/pki/etcd/server.crt --client-cert-auth=true --data-dir=/var/lib +4026532972 pid 1 1558 root local-path-provisioner --debug start --helper-image docker.io/kindest/local-path-helper:v20230510-486859a6 --config /etc/config/config.json +4026533071 pid 1 957 root /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=bpfman-deployment-control-plane +4026533073 pid 1 1047 root /bin/kindnetd +4026533229 pid 1 1382 root /coredns -conf /etc/coredns/Corefile +4026533312 pid 1 1896 65532 /usr/local/bin/kube-rbac-proxy --secure-listen-address=0.0.0.0:8443 --upstream=http://127.0.0.1:8174/ --logtostderr=true --v=0 +4026533314 pid 1 1943 65532 /bpfman-operator --health-probe-bind-address=:8175 --metrics-bind-address=127.0.0.1:8174 --leader-elect +4026533319 pid 1 2108 root ./bpfman system service --timeout=0 --csi-support +4026533321 pid 1 2158 root /bpfman-agent --health-probe-bind-address=:8175 --metrics-bind-address=127.0.0.1:8174 +4026533323 pid 1 2243 root /csi-node-driver-registrar --v=5 --csi-address=/csi/csi.sock --kubelet-registration-path=/var/lib/kubelet/plugins/csi-bpfman/csi.sock +``` +We can see the bpfman containers we were looking at earlier in the output above. +Let's take a deeper look at the `bpfman-agent` container that has a PID of 2158 +on the Kubernetes node container and a PID namespace of 4026533321. If we go +back to the base server, we can find the container's PID there. + +```bash +sudo lsns -t pid | grep 4026533321 +4026533321 pid 1 222225 root /bpfman-agent --health-probe-bind-address=:8175 --metrics-bind-address=127.0.0.1:8174 +``` + +This command tells us that the PID of our `bpfman-agent` is 222225 on the base +server. The information for this PID is contained in `/proc/222225`. The +following command will show the PID mappings for that one container at each +level. + +```bash +sudo grep NSpid /proc/222225/status +NSpid: 222225 2158 1 +``` + +The output above tells us that the PIDs for the `bpfman-agent` container are +222225 on the base server, 2158 in the Docker "node" container, and 1 inside the +container itself. + +## Moving Forward + +As always, there is more work to do. The highest priority goals are to support +additional eBPF program types and to use the Container Runtime Interface +directly. + +We chose uprobes first because we had a user with a specific need. However, +there are use cases for other eBPF program types. + +We used `crictl` in this first implementation because it already exists, +supports multiple container runtimes, handles the corner cases, and is +maintained. This allowed us to focus on the bpfman implementation and get the +feature done more quickly. However, it would be better to access the container +runtime interface directly rather than using an external executable. diff --git a/docs/blog/posts/img/2021-11-25/bpfman-logo-5.png b/docs/blog/posts/img/2021-11-25/bpfman-logo-5.png new file mode 100644 index 000000000..ba2e3ac47 Binary files /dev/null and b/docs/blog/posts/img/2021-11-25/bpfman-logo-5.png differ diff --git a/docs/blog/posts/img/2021-11-25/bpfman-logo-final.png b/docs/blog/posts/img/2021-11-25/bpfman-logo-final.png index ba2e3ac47..da1810833 100644 Binary files a/docs/blog/posts/img/2021-11-25/bpfman-logo-final.png and b/docs/blog/posts/img/2021-11-25/bpfman-logo-final.png differ diff --git a/docs/blog/posts/img/2021-11-25/bpfman-logo-semifinal.png b/docs/blog/posts/img/2021-11-25/bpfman-logo-semifinal.png new file mode 100644 index 000000000..7ebb67626 Binary files /dev/null and b/docs/blog/posts/img/2021-11-25/bpfman-logo-semifinal.png differ diff --git a/docs/blog/posts/img/2024-01-15/data-flow.png b/docs/blog/posts/img/2024-01-15/data-flow.png new file mode 100644 index 000000000..ab0c77486 Binary files /dev/null and b/docs/blog/posts/img/2024-01-15/data-flow.png differ diff --git a/docs/blog/posts/img/2024-02-27/AF_XDP-overview.png b/docs/blog/posts/img/2024-02-27/AF_XDP-overview.png new file mode 100644 index 000000000..c15bec629 Binary files /dev/null and b/docs/blog/posts/img/2024-02-27/AF_XDP-overview.png differ diff --git a/docs/blog/posts/img/2024-02-27/af_xdp-bpfman-csi.png b/docs/blog/posts/img/2024-02-27/af_xdp-bpfman-csi.png new file mode 100644 index 000000000..f0312b316 Binary files /dev/null and b/docs/blog/posts/img/2024-02-27/af_xdp-bpfman-csi.png differ diff --git a/docs/blog/posts/img/2024-02-27/af_xdp-bpfman-no-csi.png b/docs/blog/posts/img/2024-02-27/af_xdp-bpfman-no-csi.png new file mode 100644 index 000000000..55b65b89a Binary files /dev/null and b/docs/blog/posts/img/2024-02-27/af_xdp-bpfman-no-csi.png differ diff --git a/docs/blog/posts/img/2024-02-27/af_xdp.png b/docs/blog/posts/img/2024-02-27/af_xdp.png new file mode 100644 index 000000000..643e74b79 Binary files /dev/null and b/docs/blog/posts/img/2024-02-27/af_xdp.png differ diff --git a/docs/blog/posts/introduction-to-bpfman.md b/docs/blog/posts/introduction-to-bpfman.md index 7e7d53403..db2ec3e65 100644 --- a/docs/blog/posts/introduction-to-bpfman.md +++ b/docs/blog/posts/introduction-to-bpfman.md @@ -45,7 +45,7 @@ discuss the problems bpfman can help solve, and how to deploy and use it. While some organizations have had success developing, deploying, and maintaining production software which includes eBPF programs, the barrier to entry is still -very high. +very high. Following the basic eBPF development workflow, which often involves many hours trying to interpret and fix mind-bending [eBPF verifier] errors, the process of @@ -71,7 +71,6 @@ what eBPF does, and how it can help reduce the costs associated with deploying and managing eBPF-powered workloads. [eBPF verifier]:https://docs.kernel.org/bpf/verifier.html -[bpfman]:https://bpfman.io ## bpfman Overview @@ -80,7 +79,7 @@ lifecycle of eBPF programs. In particular, it can load, unload, modify, and monitor eBPF programs on a single host, or across a full Kubernetes cluster. The key components of bpfman include the bpfman daemon itself which can run independently on any Linux box, an accompanying Kubernetes Operator designed -to bring first-class support to clusters via Custom Resource Definitions (CRDs), +to bring first-class support to clusters via Custom Resource Definitions (CRDs), and eBPF program packaging. These components will be covered in more detail in the following sections. @@ -137,7 +136,6 @@ interact. [Aya]:https://aya-rs.dev/ [multi-prog]:https://github.com/xdp-project/xdp-tools/blob/master/lib/libxdp/protocol.org - ### bpfman Kubernetes Support The benefits of bpfman are brought to Kubernetes by the bpfman operator. The bpfman @@ -281,8 +279,9 @@ status: status: "True" type: ReconcileSuccess ``` -More details about this process can be seen -[here](https://bpfman.io/getting-started/example-bpf-k8s/) + +More details about this process can be seen [here] +[here]:../../getting-started/example-bpf-k8s.md #### eBPF program packaging @@ -315,7 +314,7 @@ using bpfman, only the bpfman daemon, which can be tightly controlled, needs the privileges required to load eBPF programs, while access to the API can be controlled via standard RBAC methods on a per-application and per-CRD basis. Additionally, the signing and validating of bytecode images enables supply chain -security. +security. #### Visibility and Debuggability @@ -336,6 +335,7 @@ this for you so you don't have to. eBPF bytecode images help here as well by simplifying the distribution of eBPF bytecode to multiple nodes in a cluster, and also allowing separate fine-grained versioning control for user space and kernel space code. + ### Demonstration This demonstration is adapted from the instructions documented by Andrew Stoycos @@ -344,10 +344,12 @@ This demonstration is adapted from the instructions documented by Andrew Stoycos These instructions use kind and bpfman release v0.2.1. It should also be possible to run this demo on other environments such as minikube or an actual cluster. -Another option is to [build the code -yourself](https://bpfman.io/getting-started/building-bpfman/) and use [`make -run-on-kind`](https://bpfman.io/getting-started/example-bpf-k8s/) to create the -cluster as is described in the given links. Then, start with step 5. +Another option is to [build the code yourself] and use [make run-on-kind] + +[build the code yourself]:../../getting-started/building-bpfman.md#development-environment-setup +[make run-on-kind]:../../getting-started/example-bpf-k8s.md + +to create the cluster as is described in the given links. Then, start with step 5. #### Run the demo @@ -439,7 +441,7 @@ Notes: for the pod's primary node interface, which may not have a lot of traffic. However, running the `kubectl` command below generates traffic on that interface, so run the command a few times and give it a few seconds in between -to confirm whether the counters are incrementing. +to confirm whether the counters are incrementing. - Replace "go-xdp-counter-ds-9lpgp" with the go-xdp-counter pod name for your deployment. @@ -605,4 +607,4 @@ to see you there! [bpfman-disc]:https://github.com/bpfman/bpfman/discussions/new/choose [bpfman-iss]:https://github.com/bpfman/bpfman/issues/new [k8s-slack]:https://kubernetes.slack.com -[sync]:https://bpfman.io/governance/meetings/ +[sync]:../../governance/MEETINGS.md diff --git a/docs/blog/posts/sled-integration.md b/docs/blog/posts/sled-integration.md new file mode 100644 index 000000000..2a7a27017 --- /dev/null +++ b/docs/blog/posts/sled-integration.md @@ -0,0 +1,338 @@ +--- +date: 2024-01-15 +authors: + - astoycos +--- + +# bpfman's Shift Towards a Daemonless Design and Using Sled: a High Performance Embedded Database + +As part of [issue #860] the community +has steadily been converting all of the internal state management to go through +a [sled] database instance which is part of the larger effort to make +[bpfman completely damonless]. + +This article will go over the reasons behind the change and dive into some +of the details of the actual implementation. + +[bpfman completely damonless]:https://github.com/bpfman/bpfman/blob/main/docs/design/daemonless.md +[issue #860]:https://github.com/bpfman/bpfman/issues/860 + + + +## Why? + +State management in bpfman has always been a headache, not +because there's a huge amount of disparate data but there's multiple +representations of the same data. Additionally the delicate filesystem +interactions and layout previously used to ensure persistence across restarts +often led to issues. + +Understanding the existing flow of data in bpfman can help make this a bit clearer: + +![bpfman-data-flow](img/2024-01-15/data-flow.png) + +With this design there was a lot of data wrangling required to convert the +tonic generated rust bindings for the protocol buffer API into data +structures that were useful for bpfman. Specifically, data would arrive +via GRPC server as specified in `bpfman.v1.rs` where rust types are inferred +from the protobuf definition. In `rpc.rs` data was then converted to an +internal set of structures defined in `command.rs`. Prior to [pull request #683] +there was an explosion of types, with each bpfman command having it's own set of +internal structures and enums. Now, most of the data for a program that bpfman +needs internally for all commands to manage an eBPF program is stored in +the `ProgramData` structure, which we'll take a deeper look at a bit later. +Additionally, there is extra complexity for XDP and TC program types +which rely on an eBPF dispatcher program to provide multi-program support on +a single network interface, however this article will try to instead focus +on the simpler examples. + +The tree of data stored by bpfman is quite complex and this is made even more +complicated since bpfman has to be persistent across restarts. +To support this, raw data was often flushed to disk in the form of JSON files +(all types in `command.rs` needed to implement [serde's] `Serialize` and +`Deserialize`). Specific significance would also be encoded to bpfman's +directory structure, i.e all program related information was encoded in +`/run/bpfd/programs/`. The extra infrastructure and failure modes introduced +by this process was a constant headache, pushing the community to find a better +solution. + +[pull request #683]:https://github.com/bpfman/bpfman/pull/683 +[serde's]:https://serde.rs/ + +## Why Sled? + +[Sled] is an open source project described in github as "the champagne of beta +embedded databases". The "reasons" for choosing an embedded database from the +[project website][sled] are pretty much spot on: + +```yaml +Embedded databases are useful in several cases: + +- you want to store data on disk, without facing the complexity of files +- you want to be simple, without operating an external database +- you want to be fast, without paying network costs +- using disk storage as a building block in your system +``` + +As discussed in the previous section, persistence across restarts, is one of +bpfman's core design constraints, and with sled we *almost* get it for free! +Additionally due to the pervasive nature of data management to bpfman's core +workflow the data-store needed to be kept as simple and light weight as possible, +ruling out heavier production-ready external database systems such as MySQL or +Redis. + +Now this mostly focused on why embedded dbs in general, but why did we choose +sled...well because it's written in :crab: Rust :crab: of course! Apart from the +obvious we took a small dive into the project before rewriting everything by +[transitioning the OCI bytecode image library][oci] +to use the db rather than the filesystem. Overall the experience was extremely +positive due to the following: + +- No more dealing directly with the filesystem, the sled instance is flushed to + the fs automatically every 500 ms by default and for good measure we manually + flush it before shutting down. +- The API is extremely simple, traditional get and insert operations function + as expected. +- Error handling with `sled:Error` is relatively simple and easy to map explicitly + to a `bpfmanError` +- The db "tree" concept makes it easy to have separate key-spaces within the same + instance. + +[oci]:https://github.com/bpfman/bpfman/pull/861 + +## Transitioning to Sled + +Using the new embedded database started with the creation of a sled instance +which could be easily shared across all of the modules in bpfman. To do this we +utilized a globally available [`lazy_static`] variable called `ROOT_DB` in +`main.rs`: + +```rust +#[cfg(not(test))] +lazy_static! { + pub static ref ROOT_DB: Db = Config::default() + .path(STDIR_DB) + .open() + .expect("Unable to open root database"); +} + +#[cfg(test)] +lazy_static! { + pub static ref ROOT_DB: Db = Config::default() + .temporary(true) + .open() + .expect("Unable to open temporary root database"); +} +``` + +This block creates OR opens the filesystem backed database at `/var/lib/bpfman/db` +database only when the `ROOT_DB` variable is first accessed, and also allows for +the creation of a temporary db instance if running in unit tests. With this setup +all of the modules within bpfman can now easily access the database instance +by simply using it i.e `use crate::ROOT_DB`. + +Next the existing bpfman structures needed to be flattened in order to work +with the db, the central `ProgramData` can be used to demonstrate how this was +completed. Prior to the recent sled conversion that structure looked like: + +```rust +/// ProgramInfo stores information about bpf programs that are loaded and managed +/// by bpfd. +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub(crate) struct ProgramData { + // known at load time, set by user + name: String, + location: Location, + metadata: HashMap, + global_data: HashMap>, + map_owner_id: Option, + + // populated after load + kernel_info: Option, + map_pin_path: Option, + maps_used_by: Option>, + + // program_bytes is used to temporarily cache the raw program data during + // the loading process. It MUST be cleared following a load so that there + // is not a long lived copy of the program data living on the heap. + #[serde(skip_serializing, skip_deserializing)] + program_bytes: Vec, +} +``` + +This worked well enough, but as mentioned before the process of flushing the data +to disk involved manual serialization to JSON, which needed to occur at a specific +point in time (following program load) which made disaster recovery almost +impossible and could sometimes result in lost or partially reconstructed state. + +With sled the first idea was to completely flatten ALL of bpfman's data into a +single key-space, so that `program.name` now simply turns into a `db.get("program__name")`, +however removing all of the core structures would have resulted in a complex diff +which would have been hard to review and merge. Therefore a more staged approach +was taken, the `ProgramData` structure was kept around, and now looks like: + +```rust +/// ProgramInfo stores information about bpf programs that are loaded and managed +/// by bpfman. +#[derive(Debug, Clone)] +pub(crate) struct ProgramData { + // Prior to load this will be a temporary Tree with a random ID, following + // load it will be replaced with the main program database tree. + db_tree: sled::Tree, + + // populated after load, randomly generated prior to load. + id: u32, + + // program_bytes is used to temporarily cache the raw program data during + // the loading process. It MUST be cleared following a load so that there + // is not a long lived copy of the program data living on the heap. + program_bytes: Vec, +} +``` + +All of the fields are now removed in favor of a private reference to the unique +[`sled::Tree`] instance for this `ProgramData` which is named using the unique +kernel id for the program. Each `sled::Tree` represents a single logical +key-space / namespace / bucket which allows key generation to be kept simple, i.e +`db.get("program__name")` now can be `db_tree_prog_0000.get("program_name)`. +Additionally getters and setters are now built for each existing field so that +access to the db can be controlled and the serialization/deserialization process +can be hidden from the caller: + +```rust +... +pub(crate) fn set_name(&mut self, name: &str) -> Result<(), BpfmanError> { + self.insert("name", name.as_bytes()) +} + +pub(crate) fn get_name(&self) -> Result { + self.get("name").map(|v| bytes_to_string(&v)) +} +... +``` + +Therefore, `ProgramData` is now less of a container for program data and more of a +wrapper for accessing program data. The getters/setters act as a bridge +between standard Rust types and the raw bytes stored in the database, +i.e the [`sled::IVec` type]. + +Once this was completed for all the relevant fields on all the relevant types, +[see pull request #874], the data bpfman needed for it's managed eBPF programs +was now automatically synced to disk :partying_face: + +[`lazy_static`]:https://blog.logrocket.com/rust-lazy-static-pattern +[`sled::Tree`]:https://docs.rs/sled/latest/sled/struct.Tree.html +[`sled::IVec` type]:https://docs.rs/sled/latest/sled/struct.IVec.html +[see pull request #874]:https://github.com/bpfman/bpfman/pull/874 + +## Tradeoffs + +All design changes come with some tradeoffs: for bpfman's conversion to using +sled the main negative ended up being with the complexity introduced with the [`sled::IVec` type]. +It is basically just a thread-safe reference-counting pointer to a raw byte slice, +and the only type raw database operations can be performed with. Previously when +using `serde_json` all serialization/deserialization was automatically handled, +however with sled the conversion is manual handled internally. Therefore, instead +of a library handling the conversion of a rust string (`std::string::String`) to +raw bytes `&[u8]` bpfman has to handle it internally, using [`std::string::String::as_bytes`] +and `bpfman::utils::bytes_to_string`: + +```rust +pub(crate) fn bytes_to_string(bytes: &[u8]) -> String { + String::from_utf8(bytes.to_vec()).expect("failed to convert &[u8] to string") +} +``` + +For strings, conversion was simple enough, but when working with more complex rust +data types like `HashMaps` and `Vectors` this became a bit more of an issue. +For `Vectors`, we simply flatten the structure into a group of key/values with +indexes encoded into the key: + +```rust + pub(crate) fn set_kernel_map_ids(&mut self, map_ids: Vec) -> Result<(), BpfmanError> { + let map_ids = map_ids.iter().map(|i| i.to_ne_bytes()).collect::>(); + + map_ids.iter().enumerate().try_for_each(|(i, v)| { + sled_insert(&self.db_tree, format!("kernel_map_ids_{i}").as_str(), v) + }) + } +``` + +The sled `scan_prefix()` api then allows for easy fetching and rebuilding of +the vector: + +```rust + pub(crate) fn get_kernel_map_ids(&self) -> Result, BpfmanError> { + self.db_tree + .scan_prefix("kernel_map_ids_".as_bytes()) + .map(|n| n.map(|(_, v)| bytes_to_u32(v.to_vec()))) + .map(|n| { + n.map_err(|e| { + BpfmanError::DatabaseError("Failed to get map ids".to_string(), e.to_string()) + }) + }) + .collect() + } +``` + +For `HashMaps`, we follow a similar paradigm, except the map key is encoded in +the database key: + +```rust + pub(crate) fn set_metadata( + &mut self, + data: HashMap, + ) -> Result<(), BpfmanError> { + data.iter().try_for_each(|(k, v)| { + sled_insert( + &self.db_tree, + format!("metadata_{k}").as_str(), + v.as_bytes(), + ) + }) + } + + pub(crate) fn get_metadata(&self) -> Result, BpfmanError> { + self.db_tree + .scan_prefix("metadata_") + .map(|n| { + n.map(|(k, v)| { + ( + bytes_to_string(&k) + .strip_prefix("metadata_") + .unwrap() + .to_string(), + bytes_to_string(&v).to_string(), + ) + }) + }) + .map(|n| { + n.map_err(|e| { + BpfmanError::DatabaseError("Failed to get metadata".to_string(), e.to_string()) + }) + }) + .collect() + } +``` + +The same result could be achieved by creating individual database trees for +each `Vector`/`HashMap` instance, however our goal was to keep the layout +as flat as possible. Although this resulted in some extra complexity within the +data layer, the overall benefits still outweighed the extra code once the +conversion was complete. + +[`std::string::String::as_bytes`]:https://doc.rust-lang.org/std/string/struct.String.html#method + +## Moving forward and Getting Involved + +Once the conversion to sled is fully complete, see [issue #860], +the project will be able to completely transition to becoming a library without +having to worry about data and state management. + +If you are interested in in memory databases, eBPF, Rust, or any of the technologies +discussed today please don't hesitate to reach out at [kubernetes slack] on channel +`#bpfman` or join one of the [community meetings] to get involved. + +[sled]:https://github.com/spacejam/sled +[kubernetes slack]:https://kubernetes.slack.com/archives/C04UJBW2553 +[community meetings]:../../governance/MEETINGS.md diff --git a/docs/developer-guide/api-spec.md b/docs/developer-guide/api-spec.md index 01c13064b..0f1eb39fb 100644 --- a/docs/developer-guide/api-spec.md +++ b/docs/developer-guide/api-spec.md @@ -1,3 +1,3 @@ # API Specification -REPLACE_WITH_GENERATED_CONTENT \ No newline at end of file +--8<-- "./bpfman-operator/apidocs.html" diff --git a/docs/developer-guide/configuration.md b/docs/developer-guide/configuration.md index 249a18d70..b2a0c26c4 100644 --- a/docs/developer-guide/configuration.md +++ b/docs/developer-guide/configuration.md @@ -11,20 +11,13 @@ There is an example at `scripts/bpfman.toml`, similar to: [interface.eth0] xdp_mode = "hw" # Valid xdp modes are "hw", "skb" and "drv". Default: "skb". -[[grpc.endpoints]] - type = "tcp" - enabled = true - address = "::1" - port = 50051 - -[[grpc.endpoints]] - type = "unix" - enabled = false - path = "/run/bpfman/bpfman.sock" -``` +[signing] +allow_unsigned = true -`bpfman-agent` (which is only used in Kubernetes type deployments) will also read the -bpfman configuration file (`/etc/bpfman/bpfman.toml`) to retrieve the bpfman-client certificate file locations. +[database] +max_retries = 10 +millisec_delay = 1000 +``` ### Config Section: [interfaces] @@ -46,14 +39,29 @@ Valid fields: - **xdp_mode**: XDP Mode for a given interface. Valid values: ["drv"|"hw"|"skb"] -### Config Section: [grpc.endpoints] +### Config Section: [signing] + +This section of the configuration file allows control over whether OCI packaged eBPF +bytecode as container images are required to be signed via +[cosign](https://docs.sigstore.dev/signing/overview/) or not. +By default, unsigned images are allowed. +See [eBPF Bytecode Image Specifications](./shipping-bytecode.md) for more details on +building and shipping bytecode in a container image. + +Valid fields: + +- **allow_unsigned**: Flag indicating whether unsigned images are allowed or not. + Valid values: ["true"|"false"] + +### Config Section: [database] -In this section different endpoints can be configured for bpfman to listen on. We currently only support Unix sockets. -Unix domain sockets provide a simpler communication with no encryption. These sockets are owned by the bpfman -user and user group when running as a systemd or non-root process. +`bpfman` uses an embedded database to store state and persistent data on disk which +can only be accessed synchronously by a single process at a time. +To avoid returning database lock errors and enhance the user experience, bpfman performs +retries when opening of the database. +The number of retries and the time between retries is configurable. Valid fields: -- **type**: Specify if the endpoint will listen on a TCP or Unix domain socket. Valid values: ["unix"] -- **enabled**: Configure whether bpfman should listen on the endpoint. Valid values: ["true"|"false"] -- **path**: Exclusive to Unix sockets. Specify the path where the socket will be created. Valid values: A valid unix path. +- **max_retries**: The number of times to retry opening the database on a given request. +- **millisec_delay**: Time in milliseconds to wait between retry attempts. diff --git a/docs/developer-guide/debugging.md b/docs/developer-guide/debugging.md index ccda95a1a..7aa91515d 100644 --- a/docs/developer-guide/debugging.md +++ b/docs/developer-guide/debugging.md @@ -11,7 +11,7 @@ "program": "/github.com/bpfman/bpfman/target/debug/bpfman", // Local path to latest debug binary. "initCommands": [ "platform select remote-linux", // Execute `platform list` for a list of available remote platform plugins. - "platform connect connect://:8081", // replace + "platform connect connect://:8175", // replace "settings set target.inherit-env false", ], "env": { diff --git a/docs/developer-guide/develop-operator.md b/docs/developer-guide/develop-operator.md index 51c32d078..e75c69999 100644 --- a/docs/developer-guide/develop-operator.md +++ b/docs/developer-guide/develop-operator.md @@ -12,9 +12,9 @@ The following diagram depicts how all these components work together to create a ![bpfman on K8s](../img/bpfman-on-k8s.png) -## Building and deploying +## Building and Deploying -For building and deploying the bpfman-operator simply see the attached `Make help` +For building and deploying the bpfman-operator simply see the attached `make help` output. ```bash @@ -29,6 +29,10 @@ General Local Dependencies kustomize Download kustomize locally if necessary. controller-gen Download controller-gen locally if necessary. + register-gen Download register-gen locally if necessary. + informer-gen Download informer-gen locally if necessary. + lister-gen Download lister-gen locally if necessary. + client-gen Download client-gen locally if necessary. envtest Download envtest-setup locally if necessary. opm Download opm locally if necessary. @@ -73,114 +77,266 @@ Openshift Deployment undeploy-openshift Undeploy bpfman-operator from the Openshift cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. ``` -## Running Locally in KIND - -To run locally in a kind cluster with an up to date build simply run: - -```bash -make run-on-kind -``` - -The container images used for `bpfman`,`bpfman-agent`, and `bpfman-operator` can also be manually configured, -by default local image builds will be used for the kind deployment. - -```bash -BPFMAN_IMG= BPFMAN_AGENT_IMG= BPFMAN_OPERATOR_IMG= make run-on-kind -``` - -Then rebuild and load a fresh build run: - -```bash -make kind-reload-images -``` - -Which will rebuild the bpfman-operator, bpfman-agent, and bpfman images and load them into the kind cluster. - -## Testing Locally - -To run all of the **Unit Tests** defined in the bpfman-operator controller code simply run `make test`. - -To run **Integration Tests** locally: - -1. Build the images locally with the `int-test` tag. - -```bash - BPFMAN_AGENT_IMG=quay.io/bpfman/bpfman-agent:int-test BPFMAN_IMG=quay.io/bpfman/bpfman:int-test BPFMAN_OPERATOR_IMG=quay.io/bpfman/bpfman-operator:int-test make build-images -``` - -2. Run the integration test suite. - -```bash - BPFMAN_AGENT_IMG=quay.io/bpfman/bpfman-agent:int-test BPFMAN_IMG=quay.io/bpfman/bpfman:int-test BPFMAN_OPERATOR_IMG=quay.io/bpfman/bpfman-operator:int-test make test-integration -``` - -Additionally the integration test can be configured with the following environment variables: - -* **KEEP_TEST_CLUSTER**: If set to `true` the test cluster will not be torn down after the integration test - suite completes. -* **USE_EXISTING_KIND_CLUSTER**: If this is set to the name of the existing kind cluster the integration test - suite will use that cluster instead of creating a new one. - -## Project Layout +### Project Layout The bpfman-operator project layout is guided by the recommendations from both the [operator-sdk framework](https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/#project-layout) and the [standard golang project-layout](https://github.com/golang-standards/project-layout). -The following is a brief description of the main directories and their contents. +The following is a brief description of the main directories under `bpfman-operator/` and their contents. **NOTE: Bolded directories contain auto-generated code** - -- **`/apis`**: Contains the K8s CRD api definitions(`*_types.go`) for each version along with the - auto-generated register and deepcopy methods(`zz_generated.deepcopy.go` and `zz_generate_register.go`). -- `/bundle`: Contains the OLM bundle manifests and metadata for the operator. +- `apis/v1alpha1/*_types.go`: Contains the K8s CRD api definitions (`*_types.go`) for each version. +- **apis/v1alpha1/zz_generated.*.go**: Contains the auto-generated register (`zz_generate.register.go`) + and deepcopy (`zz_generated.deepcopy.go`) methods. +- `bundle/`: Contains the OLM bundle manifests and metadata for the operator. More details can be found in the operator-sdk documentation. -- `/cmd`: Contains the main entry-points for the bpfman-operator and bpfman-agent processes. -- `/config`: Contains the configuration files for launching the bpfman-operator on a cluster. - - `/bpfman-deployment`: Contains static deployment yamls for the bpfman-daemon, this includes two containers, +- `cmd/`: Contains the main entry-points for the bpfman-operator and bpfman-agent processes. +- `config/`: Contains the configuration files for launching the bpfman-operator on a cluster. + - `bpfman-deployment/`: Contains static deployment yamls for the bpfman-daemon, this includes two containers, one for `bpfman` and the other for the `bpfman-agent`. This DaemonSet yaml is NOT deployed statically by kustomize, instead it's statically copied into the operator image which is then responsible for deploying and configuring the bpfman-daemon DaemonSet. Lastly, this directory also contains the default config used to configure the bpfman-daemon, along with the cert-manager certificates used to encrypt communication between the bpfman-agent and bpfman. - - `/bpfman-operator-deployment:` Contains the static deployment yaml for the bpfman-operator. This is deployed - statically by kustomize. - - `/crd`: Contains the CRD manifests for all of the bpfman-operator APIs. - - **`/bases`**: Is where the actual CRD definitions are stored. + - `bpfman-operator-deployment/`: Contains the static deployment yaml for the bpfman-operator. + This is deployed statically by kustomize. + - `crd/`: Contains the CRD manifests for all of the bpfman-operator APIs. + - **bases/**: Is where the actual CRD definitions are stored. These definitions are auto-generated by [controller-gen](https://book.kubebuilder.io/reference/controller-gen.html). - - `/default`: Contains the default deployment configuration for the bpfman-operator. - - `/manifests`: Contains the bases for generating OLM manifests. - - `/openshift`: Contains the Openshift specific deployment configuration for the bpfman-operator. - - `/prometheus`: Contains the prometheus manifests used to deploy Prometheus to a cluster. + - `patches/`: Contains kustomize patch files for each Program Type, which enables a conversion webhook for + the CRD and adds a directive for certmanager to inject CA into the CRD. + - `default/`: Contains the default deployment configuration for the bpfman-operator. + - `manifests/`: Contains the bases for generating OLM manifests. + - `openshift/`: Contains the Openshift specific deployment configuration for the bpfman-operator. + - `prometheus/`: Contains the prometheus manifests used to deploy Prometheus to a cluster. At the time of writing this the bpfman-operator is NOT exposing any metrics to prometheus, but this is a future goal. - - **`/rbac`**: Contains rbac yamls for getting bpfman and the bpfman-operator up and running on Kubernetes. - **`/bpfman-agent`**: Contains the rbac yamls for the bpfman-agent. + - **rbac/**: Contains rbac yamls for getting bpfman and the bpfman-operator up and running on Kubernetes. + - **bpfman-agent/**: Contains the rbac yamls for the bpfman-agent. They are automatically generated by kubebuilder via build tags in the bpfman-agent controller code. - **`/bpfman-operator`**: Contains the rbac yamls for the bpfman-operator. + - **bpfman-operator/**: Contains the rbac yamls for the bpfman-operator. They are automatically generated by kubebuilder via build tags in the bpfman-operator controller code. - - `/samples`: Contains sample CR definitions that can be deployed by users for each of our supported APIs. - - `/scorecard`: Contains the scorecard manifests used to deploy scorecard to a cluster. At the time of writing + - `samples/`: Contains sample CR definitions that can be deployed by users for each of our supported APIs. + - `scorecard/`: Contains the scorecard manifests used to deploy scorecard to a cluster. At the time of writing this the bpfman-operator is NOT running any scorecard tests. - - `/test`: Contains the test manifests used to deploy the bpfman-operator to a kind cluster for integration testing. -- `/controllers`: Contains the controller implementations for all of the bpfman-operator APIs. + - `test/`: Contains the test manifests used to deploy the bpfman-operator to a kind cluster for integration testing. +- `controllers/`: Contains the controller implementations for all of the bpfman-operator APIs. Each controller is responsible for reconciling the state of the cluster with the desired state defined by the user. This is where the source of truth for the auto-generated RBAC can be found, keep an eye out for `//+kubebuilder:rbac:groups=bpfman.io` comment tags. - - `/bpfmanagent`: Contains the controller implementations which reconcile user created `*Program` types to multiple + - `bpfmanagent/`: Contains the controller implementations which reconcile user created `*Program` types to multiple `BpfProgram` objects. - - `/bpfmanoperator`: Contains the controller implementations which reconcile global `BpfProgram` object state back to + - `bpfmanoperator/`: Contains the controller implementations which reconcile global `BpfProgram` object state back to the user by ensuring the user created `*Program` objects are reporting the correct status. -- `/hack`: Contains any scripts+static files used by the bpfman-operator to facilitate development. -- `/internal`: Contains all private library code and is used by the bpfman-operator and bpfman-agent controllers. -- **`/pkg`**: Contains all public library code this is consumed externally and internally. - - **`/client`**: Contains the autogenerated clientset, informers and listers for all of the bpfman-operator APIs. +- `hack/`: Contains any scripts+static files used by the bpfman-operator to facilitate development. +- `internal/`: Contains all private library code and is used by the bpfman-operator and bpfman-agent controllers. +- `pkg/`: Contains all public library code this is consumed externally and internally. + - **client/**: Contains the autogenerated clientset, informers and listers for all of the bpfman-operator APIs. These are autogenerated by the [k8s.io/code-generator project](https://github.com/kubernetes/code-generator), and can be consumed by users wishing to programmatically interact with bpfman specific APIs. - - `/helpers`: Contains helper functions which can be consumed by users wishing to programmatically interact with + - `helpers/`: Contains helper functions which can be consumed by users wishing to programmatically interact with bpfman specific APIs. -- `/test/integration`: Contains integration tests for the bpfman-operator. +- `test/integration/`: Contains integration tests for the bpfman-operator. These tests are run against a kind cluster and are responsible for testing the bpfman-operator in a real cluster environment. It uses the [kubernetes-testing-framework project](https://github.com/Kong/kubernetes-testing-framework) to programmatically spin-up all of the required infrastructure for our unit tests. - `Makefile`: Contains all of the make targets used to build, test, and generate code used by the bpfman-operator. + +### RPC Protobuf Generation + +Technically part of the `bpfman` API, the RPC Protobufs are usually not coded until a bpfman feature is +integrated into the `bpfman-operator` and `bpfman-agent` code. +To modify the RPC Protobuf definition, edit +[proto/bpfman.proto](https://github.com/bpfman/bpfman/blob/main/proto/bpfman.proto). +Then to generate the protobufs from the updated RPC Protobuf definitions: + +```bash +cd bpfman/ +cargo xtask build-proto +``` + +This will generate: + +- **bpfman-api/src/bpfman.v1.rs**: Generated Rust Protobuf source code. +- **clients/gobpfman/v1/**: Directory that contains the generated Go Client code for interacting + with bpfman over RPC from a Go application. + +When editing +[proto/bpfman.proto](https://github.com/bpfman/bpfman/blob/main/proto/bpfman.proto), +follow best practices describe in +[Proto Best Practices](https://protobuf.dev/programming-guides/dos-donts/). + +**Note:** `cargo xtask build-proto` also pulls in +[proto/csi.proto](https://github.com/bpfman/bpfman/blob/main/proto/csi.proto) (which is in the +same directory as +[proto/bpfman.proto](https://github.com/bpfman/bpfman/blob/main/proto/bpfman.proto)). +[proto/csi.proto](https://github.com/bpfman/bpfman/blob/main/proto/csi.proto) is taken from +[container-storage-interface/spec/csi.proto](https://github.com/container-storage-interface/spec/blob/master/csi.proto). +See [container-storage-interface/spec/spec.md](https://github.com/container-storage-interface/spec/blob/master/spec.md) +for more details. + +### Generated Files + +The [operator-sdk framework](https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/#project-layout) +will generate multiple categories of files (Custom Resource Definitions (CRD), RBAC ClusterRole, Webhook Configuration, +typed client, listeners and informers code, etc). +If any of the +[bpfman-operator/apis/v1alpha1/*Program_types.go](https://github.com/bpfman/bpfman/tree/main/bpfman-operator/apis/v1alpha1) +files are modified, then regenerate these files using: + +```bash +cd bpfman/bpfman-operator/ +make generate +``` + +This command will generate all auto-generated code. +There are commands to generate each sub-category if needed. +See `make help` to list all the generate commands. + +### Building + +To run in Kubernetes, bpfman components need to be containerized. +However, building container images can take longer than just building the code. +During development, it may be quicker to find and fix build errors by just building the code. +To build the code: + +```bash +cd bpfman/bpfman-operator/ +make build +``` + +To build the container images, run the following command: + +```bash +cd bpfman/bpfman-operator/ +make build-images +``` + +If the `make build` command is skipped above, the code will be built in the build-images command. +If the `make build` command is run, the built code will be leveraged in this step. +This command generates the following images: + +```bash +docker images +REPOSITORY TAG IMAGE ID CREATED SIZE +quay.io/bpfman/bpfman latest 69df038ccea3 43 seconds ago 515MB +quay.io/bpfman/bpfman-agent latest f6af33c5925b 2 minutes ago 464MB +quay.io/bpfman/bpfman-operator latest 4fe444b7abf1 2 minutes ago 141MB +: +``` + +## Running Locally in KIND + +[Deploying the bpfman-operator](./operator-quick-start.md) goes into more detail on ways to +launch bpfman in a Kubernetes cluster. +To run locally in a Kind cluster with an up to date build simply run: + +```bash +cd bpfman/bpfman-operator/ +make run-on-kind +``` + +The `make run-on-kind` will run the `make build-images` if the images do not exist or need updating. + +Then rebuild and load a fresh build run: + +```bash +cd bpfman/bpfman-operator/ +make build-images +make kind-reload-images +``` + +Which will rebuild the bpfman-operator, bpfman-agent, and bpfman images and load them into the kind cluster. + +By default, the `make run-on-kind` uses the `quay.io/bpfman/bpfman*` images described above. +The container images used for `bpfman`, `bpfman-agent`, and `bpfman-operator` can also be manually configured: + +```bash +BPFMAN_IMG= BPFMAN_AGENT_IMG= BPFMAN_OPERATOR_IMG= make run-on-kind +``` + +## Testing Locally + +See [Kubernetes Operator Tests](https://bpfman.io/main/developer-guide/testing/#kubernetes-operator-tests). + +## Troubleshooting + +### Metrics/Health port issues + +In some scenarios, the health and metric ports may are already in use by other services on the system. +When this happens the bpfman-agent container fails to deploy. +The ports currently default to 8175 and 8174. + +The ports are passed in through the [daemonset.yaml] for the `bpfman-daemon` and [deployment.yaml] and +[manager_auth_proxy_patch.yaml] for the `bpfman-operator`. +The easiest way to change which ports are used is to update these yaml files and rebuild the container images. +The container images need to be rebuilt because the `bpfman-daemon` is deployed from the `bpfman-operator` +and the associated yaml files are copied into the `bpfman-operator` image. + +If rebuild the container images is not desirable, then the ports can be changed on the fly. +For the `bpfman-operator`, the ports can be updated by editing the `bpfman-operator` Deployment. + +```console +kubectl edit deployment -n bpfman bpfman-operator + +apiVersion: apps/v1 +kind: Deployment +: +spec: + template: + : + spec: + containers: + -args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8174/ <-- UPDATE + - --logtostderr=true + - --v=0 + name: kube-rbac-proxy + : + - args: + - --health-probe-bind-address=:8175 <-- UPDATE + - --metrics-bind-address=127.0.0.1:8174 <-- UPDATE + - --leader-elect + : + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 8175 <-- UPDATE + scheme: HTTP + : + name: bpfman-operator + readinessProbe: + failureThreshold: 3 + httpGet: + path: /readyz + port: 8175 <-- UPDATE + scheme: HTTP + : +``` + +For the `bpfman-daemon`, the ports could be updated by editing the `bpfman-daemon` DaemonSet. +However, if `bpfman-daemon` is restarted for any reason by the `bpfman-operator`, the changes +will be lost. So it is recommended to update the ports for the `bpfman-daemon` via the bpfman +`bpfman-config` ConfigMap. + +```console +kubectl edit configmap -n bpfman bpfman-config + +apiVersion: v1 +data: + bpfman.agent.healthprobe.addr: :8175 <-- UPDATE + bpfman.agent.image: quay.io/bpfman/bpfman-agent:latest + bpfman.agent.log.level: info + bpfman.agent.metric.addr: 127.0.0.1:8174 <-- UPDATE + bpfman.image: quay.io/bpfman/bpfman:latest + bpfman.log.level: debug +kind: ConfigMap +: +``` + +[daemonset.yaml]: https://github.com/bpfman/bpfman/blob/main/bpfman-operator/config/bpfman-deployment/daemonset.yaml +[deployment.yaml]: https://github.com/bpfman/bpfman/blob/main/bpfman-operator/config/bpfman-operator-deployment/deployment.yaml +[manager_auth_proxy_patch.yaml]: https://github.com/bpfman/bpfman/blob/main/bpfman-operator/config/default/manager_auth_proxy_patch.yaml diff --git a/docs/developer-guide/documentation.md b/docs/developer-guide/documentation.md index 13b03614a..b82116fa5 100644 --- a/docs/developer-guide/documentation.md +++ b/docs/developer-guide/documentation.md @@ -50,14 +50,10 @@ The file [docs/developer-guide/api-spec.md](https://github.com/bpfman/bpfman/blob/main/docs/developer-guide/api-spec.md) documents the CRDs used in a Kubernetes deployment. The contents are auto-generated when PRs are pushed to Github. -The script [scripts/make-docs.sh](https://github.com/bpfman/bpfman/blob/main/scripts/make-docs.sh) -manages the generation of this file. -## Generate Documentation +The contents can be generated locally by running the command `make -C bpfman-operator apidocs.html` from the root bpfman directory. -On each PR pushed to https://github.com/bpfman/bpfman the documentation is generated. -To preview of the generated site, click on the `Details` link of the -`netlify/bpfman/deploy-preview` Check from the Github GUI. +## Generate Documentation If you would like to test locally, build and preview the generated documentation, from the bpfman root directory, use `mkdocs` to build: @@ -67,6 +63,9 @@ cd bpfman/ mkdocs build ``` +>**NOTE:** If `mkdocs build` gives you an error, make sure you have the mkdocs +packages listed below installed. + To preview from a build on a local machine, start the mkdocs dev-server with the command below, then open up `http://127.0.0.1:8000/` in your browser, and you'll see the default home page being displayed: @@ -88,9 +87,7 @@ mkdocs serve -a 0.0.0.0:8000 The recommended installation method is using `pip`. ```console -pip install mkdocs -pip install pymdown-extensions -pip install mkdocs-material +pip install -r requirements.txt ``` Once installed, ensure the `mkdocs` is in your PATH: @@ -99,3 +96,12 @@ Once installed, ensure the `mkdocs` is in your PATH: mkdocs -V mkdocs, version 1.4.3 from /home/$USER/.local/lib/python3.11/site-packages/mkdocs (Python 3.11) ``` + +>**NOTE:** If you have an older version of mkdocs installed, you may need to use +the `--upgrade` option (e.g., `pip install --upgrade mkdocs`) to get it to work. + +## Document Images + +Source of images used in the example documentation can be found in +[bpfman Upstream Images](https://docs.google.com/presentation/d/1wU4xu6xeyk9cB3G-Nn-dzkf90j1-EI4PB167G7v-Xl4/edit?usp=sharing). +Request access if required. diff --git a/docs/developer-guide/logging.md b/docs/developer-guide/logging.md index c83cdbaed..81447dc06 100644 --- a/docs/developer-guide/logging.md +++ b/docs/developer-guide/logging.md @@ -52,6 +52,7 @@ CapabilityBoundingSet=CAP_BPF CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_PERFMON CAP_ Start the service: ```console +sudo systemctl daemon-reload sudo systemctl start bpfman.service ``` @@ -109,10 +110,9 @@ To view the `bpfman-agent` logs: ```console kubectl logs -n bpfman bpfman-daemon-dgqzw -c bpfman-agent -2023-05-05T14:41:27Z INFO controller-runtime.metrics Metrics server is starting to listen {"addr": ":8080"} -2023-05-05T14:41:27Z INFO tls-internal Reading... - {"Default config path": "/etc/bpfman/bpfman.toml"} -2023-05-05T14:41:27Z INFO setup Waiting for active connection to bpfman at %s {"addr": "localhost:50051", "creds": {}} +{"level":"info","ts":"2023-12-20T20:15:34Z","logger":"controller-runtime.metrics","msg":"Metrics server is starting to listen","addr":":8174"} +{"level":"info","ts":"2023-12-20T20:15:34Z","logger":"setup","msg":"Waiting for active connection to bpfman"} +{"level":"info","ts":"2023-12-20T20:15:34Z","logger":"setup","msg":"starting Bpfman-Agent"} : ``` @@ -129,11 +129,6 @@ data: bpfman.image: quay.io/bpfman/bpfman:latest bpfman.log.level: info <==== Set bpfman Log Level Here bpfman.agent.log.level: info <==== Set bpfman agent Log Level Here - bpfman.toml: | - [[grpc.endpoints]] - type = "unix" - path = "/bpfman-sock/bpfman.sock" - enabled = true kind: ConfigMap metadata: creationTimestamp: "2023-05-05T14:41:19Z" @@ -171,10 +166,10 @@ To view the `bpfman-operator` logs: ```console kubectl logs -n bpfman bpfman-operator-7fbf4888c4-z8w76 -c bpfman-operator -{"level":"info","ts":"2023-05-09T18:37:11Z","logger":"controller-runtime.metrics","msg":"Metrics server is starting to listen","addr":"127.0.0.1:8080"} +{"level":"info","ts":"2023-05-09T18:37:11Z","logger":"controller-runtime.metrics","msg":"Metrics server is starting to listen","addr":"127.0.0.1:8174"} {"level":"info","ts":"2023-05-09T18:37:11Z","logger":"setup","msg":"starting manager"} -{"level":"info","ts":"2023-05-09T18:37:11Z","msg":"Starting server","kind":"health probe","addr":"[::]:8081"} -{"level":"info","ts":"2023-05-09T18:37:11Z","msg":"Starting server","path":"/metrics","kind":"metrics","addr":"127.0.0.1:8080"} +{"level":"info","ts":"2023-05-09T18:37:11Z","msg":"Starting server","kind":"health probe","addr":"[::]:8175"} +{"level":"info","ts":"2023-05-09T18:37:11Z","msg":"Starting server","path":"/metrics","kind":"metrics","addr":"127.0.0.1:8174"} I0509 18:37:11.262885 1 leaderelection.go:248] attempting to acquire leader lease bpfman/8730d955.bpfman.io... I0509 18:37:11.268918 1 leaderelection.go:258] successfully acquired lease bpfman/8730d955.bpfman.io {"level":"info","ts":"2023-05-09T18:37:11Z","msg":"Starting EventSource","controller":"configmap","controllerGroup":"","controllerKind":"ConfigMap","source":"kind source: *v1.ConfigMap"} @@ -218,8 +213,8 @@ spec: - args: : - args: - - --health-probe-bind-address=:8081 - - --metrics-bind-address=127.0.0.1:8080 + - --health-probe-bind-address=:8175 + - --metrics-bind-address=127.0.0.1:8174 - --leader-elect command: - /bpfman-operator diff --git a/docs/developer-guide/operator-quick-start.md b/docs/developer-guide/operator-quick-start.md index ae8d7373e..53d473082 100644 --- a/docs/developer-guide/operator-quick-start.md +++ b/docs/developer-guide/operator-quick-start.md @@ -5,7 +5,15 @@ This operator was built utilizing some great tooling provided by the [operator-sdk library](https://sdk.operatorframework.io/). A great first step in understanding some of the functionality can be to just run `make help`. -## Deploy Locally via KIND +## Deploy bpfman Operation + +The `bpfman-operator` is running as a Deployment with a ReplicaSet of one. +It runs on the control plane and is composed of the containers `bpfman-operator` and +`kube-rbac-proxy`. +The operator is responsible for launching the bpfman Daemonset, which runs on every node. +The bpfman Daemonset is composed of the containers `bpfman`, `bpfman-agent`, and `node-driver-registrar`. + +### Deploy Locally via KIND After reviewing the possible make targets it's quick and easy to get bpfman deployed locally on your system via a [KIND cluster](https://kind.sigs.k8s.io/) with: @@ -20,12 +28,13 @@ CSI requires Kubernetes v1.26 due to a PR ([kubernetes/kubernetes#112597](https://github.com/kubernetes/kubernetes/pull/112597)) that addresses a gRPC Protocol Error that was seen in the CSI client code and it doesn't appear to have been backported. +It is recommended to install kind v0.20.0 or later. -## Deploy To Openshift Cluster +### Deploy To Openshift Cluster First deploy the operator with one of the following two options: -### 1. Manually with Kustomize +#### 1. Manually with Kustomize To install manually with Kustomize and raw manifests simply run the following commands. @@ -43,7 +52,7 @@ Which can then be cleaned up at a later time with: make undeploy-openshift ``` -### 2. Via the OLM bundle +#### 2. Via the OLM bundle The other option for installing the bpfman-operator is to install it using [OLM bundle](https://www.redhat.com/en/blog/deploying-operators-olm-bundles). @@ -81,8 +90,6 @@ you will see the bpfman-daemon and bpfman-operator pods running without errors: ```bash kubectl get pods -n bpfman NAME READY STATUS RESTARTS AGE -bpfman-daemon-bt5xm 3/3 Running 0 130m -bpfman-daemon-ts7dr 3/3 Running 0 129m bpfman-daemon-w24pr 3/3 Running 0 130m bpfman-operator-78cf9c44c6-rv7f2 2/2 Running 0 132m ``` @@ -159,7 +166,15 @@ See [api-spec.md](./api-spec.md) for a more detailed description of all the bpfm The multiple `*Program` CRDs are the bpfman Kubernetes API objects most relevant to users and can be used to understand clusterwide state for an eBPF program. It's designed to express how, and where eBPF programs are to be deployed within a Kubernetes cluster. -Currently bpfman supports the use of `xdpProgram`, `tcProgram` and `tracepointProgram` objects. +Currently bpfman supports: + +* `fentryProgram` +* `fexitProgram` +* `kprobeProgram` +* `tcProgram` +* `tracepointProgram` +* `uprobeProgram` +* `xdpProgram` ## BpfProgram CRD diff --git a/docs/developer-guide/testing.md b/docs/developer-guide/testing.md index af1fd49ca..729ec7877 100644 --- a/docs/developer-guide/testing.md +++ b/docs/developer-guide/testing.md @@ -1,13 +1,18 @@ # Testing This document describes the automated testing that is done for each pull request -submitted to [bpfman](https://github.com/bpfman/bpfman). +submitted to [bpfman](https://github.com/bpfman/bpfman), and also provides +instructions for running them locally when doing development. ## Unit Testing -Unit testing is executed as part of the `build` job by running the `cargo test` +Unit testing is executed as part of the `build` job by running the following command in the top-level bpfman directory. +``` + cargo test +``` + ## Go Example Tests Tests are run for each of the example programs found in directory `examples` @@ -79,6 +84,44 @@ However, as a word of caution, be aware that existing integration tests will start using the new programs immediately, so this should only be done if the modified program is backward compatible. -## Kubernetes Integration Tests +## Kubernetes Operator Tests + +### Kubernetes Operator Unit Tests + +To run all of the unit tests defined in the bpfman-operator controller code run +`make test` in the bpfman-operator directory. + +### Kubernetes Operator Integration Tests + +To run the Kubernetes Operator integration tests locally: + +1. Build the example test code images. + +```bash + # in bpfman/examples + make build-us-images + make build-bc-images +``` + +2. Build the bpfman images locally with the `int-test` tag. + +```bash + # in bpfman/bpfman-operator + BPFMAN_AGENT_IMG=quay.io/bpfman/bpfman-agent:int-test BPFMAN_IMG=quay.io/bpfman/bpfman:int-test BPFMAN_OPERATOR_IMG=quay.io/bpfman/bpfman-operator:int-test make build-images +``` + +3. Run the integration test suite. + +```bash + # in bpfman/bpfman-operator + BPFMAN_AGENT_IMG=quay.io/bpfman/bpfman-agent:int-test BPFMAN_IMG=quay.io/bpfman/bpfman:int-test BPFMAN_OPERATOR_IMG=quay.io/bpfman/bpfman-operator:int-test make test-integration +``` + +Additionally the integration test can be configured with the following environment variables: + +* **KEEP_TEST_CLUSTER**: If set to `true` the test cluster will not be torn down + after the integration test suite completes. +* **USE_EXISTING_KIND_CLUSTER**: If this is set to the name of the existing kind + cluster the integration test suite will use that cluster instead of creating a + new one. -Detailed decription TBD diff --git a/docs/getting-started/tutorial.md b/docs/developer-guide/xdp-overview.md similarity index 52% rename from docs/getting-started/tutorial.md rename to docs/developer-guide/xdp-overview.md index 9b240ce27..d7b2a9fef 100644 --- a/docs/getting-started/tutorial.md +++ b/docs/developer-guide/xdp-overview.md @@ -1,52 +1,45 @@ -# Tutorial - -This tutorial will show you how to use `bpfman`. -There are several ways to launch and interact with `bpfman` and `bpfman`: - -* **Local Host** - Run `bpfman` as a privileged process straight from build directory. - See [Local Host](#local-host). -* **Systemd Service** - Run `bpfman` as a systemd service. - See [Systemd Service](#systemd-service). - -## Local Host - -### Step 1: Build `bpfman` - -Perform the following steps to build `bpfman`. -If this is your first time using bpfman, follow the instructions in -[Setup and Building bpfman](./building-bpfman.md) to setup the prerequisites for building. - -```console -cd $HOME/src/bpfman/ -cargo xtask build-ebpf --libbpf-dir $HOME/src/libbpf -cargo build -``` - -### Step 2: Setup `bpfman` environment - -`bpfman` supports both communication over a Unix socket. -All examples, both using `bpfman` and the gRPC API use this socket. - -### Step 3: Start `bpfman` - -While learning and experimenting with `bpfman`, it may be useful to run `bpfman` in the foreground -(which requires a second terminal to run the `bpfman` commands below). -For more details on how logging is handled in bpfman, see [Logging](../developer-guide/logging.md). - -```console -sudo RUST_LOG=info ./target/debug/bpfman -``` - -### Step 4: Load your first program +# XDP Tutorial + +The XDP hook point is unique in that the associated eBPF program attaches to +an interface and only one eBPF program is allowed to attach to the XDP hook +point for a given interface. +Due to this limitation, the +[libxdp protocol](https://github.com/xdp-project/xdp-tools/blob/master/lib/libxdp/protocol.org) +was written. +The one program that is attached to the XDP hook point is an eBPF dispatcher +program. +The dispatcher program contains a list of 10 stub functions. +When XDP programs wish to be loaded, they are loaded as extension programs +which are then called in place of one of the stub functions. + +bpfman is leveraging the libxdp protocol to allow it's users to load up to 10 +XDP programs on a given interface. +This tutorial will show you how to use `bpfman` to load multiple XDP programs +on an interface. + +**Note:** The TC hook point is also associated with an interface. +Within bpfman, TC is implemented in a similar fashion to XDP in that it uses a dispatcher with +stub functions. +TCX is a fairly new kernel feature that improves how the kernel handles multiple TC programs +on a given interface. +bpfman is on the process of integrating TCX support, which will replace the dispatcher logic +for TC. +Until then, assume TC behaves in a similar fashion to XDP. + +See [Launching bpfman](../getting-started/launching-bpfman.md) +for more detailed instructions on building and loading bpfman. +This tutorial assumes bpfman has been built and the `bpfman` CLI is in $PATH. + +## Load XDP program We will load the simple `xdp-pass` program, which permits all traffic to the attached interface, -`vethff657c7` in this example. -The section in the object file that contains the program is "pass". -Finally, we will use the priority of 100. -Find a deeper dive into CLI syntax in [CLI Guide](./cli-guide.md). +`eno3` in this example. +We will use the priority of 100. +Find a deeper dive into CLI syntax in [CLI Guide](../getting-started/cli-guide.md). ```console -sudo ./target/debug/bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pass:latest xdp --iface vethff657c7 --priority 100 +sudo bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pass:latest xdp \ + --iface eno3 --priority 100 Bpfman State --------------- Name: pass @@ -58,13 +51,13 @@ sudo ./target/debug/bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pa Map Owner ID: None Map Used By: 6213 Priority: 100 - Iface: vethff657c7 + Iface: eno3 Position: 0 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6213 + Program ID: 6213 Name: pass Type: xdp Loaded At: 2023-07-17T17:48:10-0400 @@ -80,12 +73,12 @@ sudo ./target/debug/bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pa ``` `bpfman load image` returns the same data as a `bpfman get` command. -From the output, the id of `6213` can be found in the `Kernel State` section. +From the output, the Program Id of `6213` can be found in the `Kernel State` section. This id can be used to perform a `bpfman get` to retrieve all relevant program data and a `bpfman unload` when the program needs to be unloaded. ```console -sudo ./target/debug/bpfman list +sudo bpfman list Program ID Name Type Load Time 6213 pass xdp 2023-07-17T17:48:10-0400 ``` @@ -93,7 +86,7 @@ sudo ./target/debug/bpfman list We can recheck the details about the loaded program with the `bpfman get` command: ```console -sudo ./target/debug/bpfman get 6213 +sudo bpfman get 6213 Bpfman State --------------- Name: pass @@ -105,13 +98,13 @@ sudo ./target/debug/bpfman get 6213 Map Owner ID: None Map Used By: 6213 Priority: 100 - Iface: vethff657c7 + Iface: eno3 Position: 0 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6213 + Program ID: 6213 Name: pass Type: xdp Loaded At: 2023-07-17T17:48:10-0400 @@ -129,12 +122,14 @@ sudo ./target/debug/bpfman get 6213 From the output above you can see the program was loaded to position 0 on our interface and thus will be executed first. -### Step 5: Loading more programs +## Loading Additional XDP Programs -We will now load 2 more programs with different priorities to demonstrate how bpfman will ensure they are ordered correctly: +We will now load 2 more programs with different priorities to demonstrate how bpfman +will ensure they are ordered correctly: ```console -sudo ./target/debug/bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pass:latest xdp --iface vethff657c7 --priority 50 +sudo bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pass:latest xdp \ + --iface eno3 --priority 50 Bpfman State --------------- Name: pass @@ -146,20 +141,21 @@ sudo ./target/debug/bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pa Map Owner ID: None Map Used By: 6215 Priority: 50 - Iface: vethff657c7 + Iface: eno3 Position: 0 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6215 + Program ID: 6215 Name: pass Type: xdp : ``` ```console -sudo ./target/debug/bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pass:latest xdp --iface vethff657c7 --priority 200 +sudo bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pass:latest xdp \ + --iface eno3 --priority 200 Bpfman State --------------- Name: pass @@ -171,13 +167,13 @@ sudo ./target/debug/bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pa Map Owner ID: None Map Used By: 6217 Priority: 200 - Iface: vethff657c7 + Iface: eno3 Position: 2 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6217 + Program ID: 6217 Name: pass Type: xdp : @@ -186,7 +182,7 @@ sudo ./target/debug/bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pa Using `bpfman list` we can see all the programs that were loaded. ```console -sudo ./target/debug/bpfman list +sudo bpfman list Program ID Name Type Load Time 6213 pass xdp 2023-07-17T17:48:10-0400 6215 pass xdp 2023-07-17T17:52:46-0400 @@ -201,57 +197,57 @@ As can be seen from the detailed output for each command below: * Program `6217` is at position `2` with a priority of `200` ```console -sudo ./target/debug/bpfman get 6213 +sudo bpfman get 6213 Bpfman State --------------- Name: pass : Priority: 100 - Iface: vethff657c7 + Iface: eno3 Position: 1 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6213 + Program ID: 6213 Name: pass Type: xdp : ``` ```console -sudo ./target/debug/bpfman get 6215 +sudo bpfman get 6215 Bpfman State --------------- Name: pass : Priority: 50 - Iface: vethff657c7 + Iface: eno3 Position: 0 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6215 + Program ID: 6215 Name: pass Type: xdp : ``` ```console -sudo ./target/debug/bpfman get 6217 +sudo bpfman get 6217 Bpfman State --------------- Name: pass : Priority: 200 - Iface: vethff657c7 + Iface: eno3 Position: 2 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6217 + Program ID: 6217 Name: pass Type: xdp : @@ -264,7 +260,8 @@ then the program can be loaded with those additional return values using the `pr parameter (see `bpfman load image xdp --help` for list of valid values): ```console -sudo ./target/debug/bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pass:latest xdp --iface vethff657c7 --priority 150 --proceed-on "pass" --proceed-on "dispatcher_return" +sudo bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pass:latest xdp \ + --iface eno3 --priority 150 --proceed-on "pass" --proceed-on "dispatcher_return" Bpfman State --------------- Name: pass @@ -276,13 +273,13 @@ sudo ./target/debug/bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pa Map Owner ID: None Map Used By: 6219 Priority: 150 - Iface: vethff657c7 + Iface: eno3 Position: 2 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6219 + Program ID: 6219 Name: pass Type: xdp : @@ -291,12 +288,12 @@ sudo ./target/debug/bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pa Which results in being loaded in position `2` because it was loaded at priority `150`, which is lower than the previous program at that position with a priority of `200`. -### Step 6: Delete a program +## Delete XDP Program Let's remove the program at position 1. ```console -sudo ./target/debug/bpfman list +sudo bpfman list Program ID Name Type Load Time 6213 pass xdp 2023-07-17T17:48:10-0400 6215 pass xdp 2023-07-17T17:52:46-0400 @@ -305,13 +302,13 @@ sudo ./target/debug/bpfman list ``` ```console -sudo ./target/debug/bpfman unload 6213 +sudo bpfman unload 6213 ``` And we can verify that it has been removed and the other programs re-ordered: ```console -sudo ./target/debug/bpfman list +sudo bpfman list Program ID Name Type Load Time 6215 pass xdp 2023-07-17T17:52:46-0400 6217 pass xdp 2023-07-17T17:53:57-0400 @@ -319,7 +316,7 @@ sudo ./target/debug/bpfman list ``` ```console -./target/debug/bpfman get 6215 +bpfman get 6215 Bpfman State --------------- Name: pass @@ -331,20 +328,20 @@ sudo ./target/debug/bpfman list Map Owner ID: None Map Used By: 6215 Priority: 50 - Iface: vethff657c7 + Iface: eno3 Position: 0 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6215 + Program ID: 6215 Name: pass Type: xdp : ``` ``` -./target/debug/bpfman get 6217 +bpfman get 6217 Bpfman State --------------- Name: pass @@ -356,20 +353,20 @@ sudo ./target/debug/bpfman list Map Owner ID: None Map Used By: 6217 Priority: 200 - Iface: vethff657c7 + Iface: eno3 Position: 2 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6217 + Program ID: 6217 Name: pass Type: xdp : ``` ``` -./target/debug/bpfman get 6219 +bpfman get 6219 Bpfman State --------------- Name: pass @@ -381,140 +378,14 @@ sudo ./target/debug/bpfman list Map Owner ID: None Map Used By: 6219 Priority: 150 - Iface: vethff657c7 + Iface: eno3 Position: 1 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6219 + Program ID: 6219 Name: pass Type: xdp : ``` - -When `bpfman` is stopped, all remaining programs will be unloaded automatically. - -### Step 7: Clean-up - -To unwind all the changes, stop `bpfman` and then run the following script: - -```console -sudo ./scripts/setup.sh uninstall -``` - -**WARNING:** `setup.sh uninstall` cleans everything up, so `/etc/bpfman/programs.d/` -and `/run/bpfman/bytecode/` are deleted. Save any changes or files that were created if needed. - -## Systemd Service - -To run `bpfman` as a systemd service, the binaries will be placed in a well known location -(`/usr/sbin/.`) and a service configuration file will be added -(`/usr/lib/systemd/system/bpfman.service`). -When run as a systemd service, the set of linux capabilities are limited to only the needed set. -If permission errors are encountered, see [Linux Capabilities](../developer-guide/linux-capabilities.md) -for help debugging. - -### Step 1 - -Same as Step 1 above, build `bpfman` if needed: - -```console -cd $HOME/src/bpfman/ -cargo xtask build-ebpf --libbpf-dir $HOME/src/libbpf -cargo build -``` - -### Step 2: Setup `bpfman` environment - -Run the following command to copy the `bpfman` and `bpfman` binaries to `/usr/sbin/` and copy a -default `bpfman.service` file to `/usr/lib/systemd/system/`. -This option will also start the systemd service `bpfman.service` by default: - -```console -sudo ./scripts/setup.sh install -``` - -> **_NOTE:_** Prior to **kernel 5.19**, all eBPF sys calls required CAP_BPF, which are used to access maps shared -between the BFP program and the userspace program. -So userspace programs that are accessing maps and running on kernels older than 5.19 will require either `sudo` -or the CAP_BPF capability (`sudo /sbin/setcap cap_bpf=ep ./`). - -To update the configuration settings associated with running `bpfman` as a service, edit the -service configuration file: - -```console -sudo vi /usr/lib/systemd/system/bpfman.service -sudo systemctl daemon-reload -``` - -If `bpfman` or `bpfman` is rebuilt, the following command can be run to install the update binaries -without regenerating the certifications. -The `bpfman` service will is automatically restarted. - -```console -sudo ./scripts/setup.sh reinstall -``` - -### Step 3: Start `bpfman` - -To manage `bpfman` as a systemd service, use `systemctl`. `sudo ./scripts/setup.sh install` will start the service, -but the service can be manually stopped and started: - -```console -sudo systemctl stop bpfman.service -... -sudo systemctl start bpfman.service -``` - -### Step 4-6 - -Same as above except `bpfman` is now in $PATH: - -```console -sudo bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pass:latest xdp --iface vethff657c7 --priority 100 -: - - -sudo bpfman list - Program ID Name Type Load Time - 6213 pass xdp 2023-07-17T17:48:10-0400 - - -sudo bpfman unload 6213 -``` - -### Step 7: Clean-up - -To unwind all the changes performed while running `bpfman` as a systemd service, run the following -script. This command cleans up everything, including stopping the `bpfman` service if it is still -running. - -```console -sudo ./scripts/setup.sh uninstall -``` - -**WARNING:** `setup.sh uninstall` cleans everything up, so `/etc/bpfman/programs.d/` -and `/run/bpfman/bytecode/` are deleted. Save any changes or files that were created if needed. - - -## Build and Run Local eBPF Programs - -In the examples above, all the eBPF programs were pulled from pre-built images. -This tutorial uses examples from the [xdp-tutorial](https://github.com/xdp-project/xdp-tutorial). -The pre-built container images can be found here: -[https://quay.io/organization/bpfman-bytecode](https://quay.io/organization/bpfman-bytecode) - -To build these examples locally, check out the -[xdp-tutorial](https://github.com/xdp-project/xdp-tutorial) git repository and -compile the examples. -[eBPF Bytecode Image Specifications](../developer-guide/shipping-bytecode.md) describes how eBPF -bytecode ispackaged in container images. - -To load these programs locally, use the `bpfman load file` command in place of the -`bpfman load image` command. -For example: - -```console -sudo ./target/debug/bpfman load file --path /$HOME/src/xdp-tutorial/basic01-xdp-pass/xdp_pass_kern.o --name "pass" xdp --iface vethff657c7 --priority 100 -``` diff --git a/docs/getting-started/building-bpfman.md b/docs/getting-started/building-bpfman.md index 3edc3a0b2..2a36c1718 100644 --- a/docs/getting-started/building-bpfman.md +++ b/docs/getting-started/building-bpfman.md @@ -5,9 +5,11 @@ If this is the first time building bpfman, jump to the [Development Environment Setup](#development-environment-setup) section for help installing the tooling. -There is also an option to run images from a given release as opposed to building locally. +There is also an option to run images from a given release, or from an RPM, as opposed to +building locally. Jump to the [Run bpfman From Release Image](./running-release.md) section for installing -from a fixed release. +from a fixed release or jump to the [Run bpfman From RPM](./running-rpm.md) section for installing +from an RPM. ## Kernel Versions @@ -30,8 +32,11 @@ Major kernel features leveraged by bpfman: * **BPF Perf Link:** Support BPF perf link for tracing programs (Tracepoint, Uprobe and Kprobe) which enables pinning for these program types. Introduced in Kernel 5.15. +* **Relaxed CAP_BPF Requirement:** Prior to Kernel 5.19, all eBPF system calls required CAP_BPF. + This required userspace programs that wanted to access eBPF maps to have the CAP_BPF Linux capability. + With the kernel 5.19 change, CAP_BPF is only required for load and unload requests. -Tested kernel versions: +bpfman tested on older kernel versions: * Fedora 34: Kernel 5.17.6-100.fc34.x86_64 * XDP, TC, Tracepoint, Uprobe and Kprobe programs all loaded with bpfman running on localhost @@ -56,11 +61,11 @@ Tested kernel versions: ## Clone the bpfman Repo -You can build and run bpfman from anywhere. However, if you plan to make changes -to the bpfman operator, it will need to be under your `GOPATH` because Kubernetes -Code-generator does not work outside of `GOPATH` [issue -86753](https://github.com/kubernetes/kubernetes/issues/86753). Assuming your -`GOPATH` is set to the typical `$HOME/go`, your repo should live in +You can build and run bpfman from anywhere. However, if you plan to make changes to the bpfman +operator, specifically run `make generate`, it will need to be under your `GOPATH` because +Kubernetes Code-generator does not work outside of `GOPATH` +[Issue 86753](https://github.com/kubernetes/kubernetes/issues/86753). +Assuming your `GOPATH` is set to the typical `$HOME/go`, your repo should live in `$HOME/go/src/github.com/bpfman/bpfman` ``` @@ -81,7 +86,8 @@ If you are building bpfman for the first time OR the eBPF code has changed: cargo xtask build-ebpf --libbpf-dir /path/to/libbpf ``` -If protobuf files have changed: +If protobuf files have changed (see +[RPC Protobuf Generation](../developer-guide/develop-operator.md#rpc-protobuf-generation)): ```console cargo xtask build-proto @@ -93,6 +99,75 @@ To build bpfman: cargo build ``` +## Building CLI TAB completion files + +Optionally, to build the CLI TAB completion files, run the following command: + +```console +cargo xtask build-completion +``` + +Files are generated for different shells: + +```console +ls .output/completions/ +_bpfman bpfman.bash bpfman.elv bpfman.fish _bpfman.ps1 +``` + +### bash + +For `bash`, this generates a file that can be used by the linux `bash-completion` +utility (see [Install bash-completion](#install-bash-completion) for installation +instructions). + +If the files are generated, they are installed automatically when using the install +script (i.e. `sudo ./scripts/setup.sh install` - See +[Run as a systemd Service](example-bpf-local.md#run-as-a-systemd-service)). +To install the files manually, copy the file associated with a given shell to +`/usr/share/bash-completion/completions/`. +For example: + +```console +sudo cp .output/completions/bpfman.bash /usr/share/bash-completion/completions/. + +bpfman g +``` + +### Other shells + +Files are generated other shells (Elvish, Fish, PowerShell and zsh). +For these shells, generated file must be manually installed. + +## Building CLI Manpages + +Optionally, to build the CLI Manpage files, run the following command: + +```console +cargo xtask build-man-page +``` + +If the files are generated, they are installed automatically when using the install +script (i.e. `sudo ./scripts/setup.sh install` - See +[Run as a systemd Service](example-bpf-local.md#run-as-a-systemd-service)). +To install the files manually, copy the generated files to `/usr/local/share/man/man1/`. +For example: + +```console +sudo cp .output/manpage/bpfman*.1 /usr/local/share/man/man1/. +``` + +Once installed, use `man` to view the pages. + +```console +man bpfman list +``` + +> **NOTE:** +> `bpfman` commands with subcommands (specifically `bpfman load`) have `-` in the +> manpage subcommand generation. +> So use `bpfman load-file`, `bpfman load-image`, `bpfman load-image-xdp`, etc. to +> display the subcommand manpage files. + ## Development Environment Setup To build bpfman, the following packages must be installed. @@ -170,12 +245,78 @@ sudo dnf install perl sudo apt install perl ``` +### Install docker + +To build the `bpfman-agent` and `bpfman-operator` using the provided Makefile and the +`make build-images` command, `docker` needs to be installed. +There are several existing guides: + +* Fedora: [https://developer.fedoraproject.org/tools/docker/docker-installation.html](https://developer.fedoraproject.org/tools/docker/docker-installation.html) +* Linux: [https://docs.docker.com/engine/install/](https://docs.docker.com/engine/install/) + +### Install Kind + +Optionally, to test `bpfman` running in Kubernetes, the easiest method and the one documented +throughout the `bpfman` documentation is to run a Kubernetes Kind cluster. +See [kind](https://kind.sigs.k8s.io/) for documentation and installation instructions. +`kind` also requires `docker` to be installed. + +>> **NOTE:** By default, bpfman-operator deploys bpfman with CSI enabled. +CSI requires Kubernetes v1.26 due to a PR +([kubernetes/kubernetes#112597](https://github.com/kubernetes/kubernetes/pull/112597)) +that addresses a gRPC Protocol Error that was seen in the CSI client code and it doesn't appear to have +been backported. +It is recommended to install kind v0.20.0 or later. + +If the following error is seen, it means there is an older version of Kubernetes running and it +needs to be upgraded. + +```console +kubectl get pods -A +NAMESPACE NAME READY STATUS RESTARTS AGE +bpfman bpfman-daemon-2hnhx 2/3 CrashLoopBackOff 4 (38s ago) 2m20s +bpfman bpfman-operator-6b6cf97857-jbvv4 2/2 Running 0 2m22s +: + +kubectl logs -n bpfman bpfman-daemon-2hnhx -c node-driver-registrar +: +E0202 15:33:12.342704 1 main.go:101] Received NotifyRegistrationStatus call: &RegistrationStatus{PluginRegistered:false,Error:RegisterPlugin error -- plugin registration failed with err: rpc error: code = Internal desc = stream terminated by RST_STREAM with error code: PROTOCOL_ERROR,} +E0202 15:33:12.342723 1 main.go:103] Registration process failed with error: RegisterPlugin error -- plugin registration failed with err: rpc error: code = Internal desc = stream terminated by RST_STREAM with error code: PROTOCOL_ERROR, restarting registration container. +``` + +### Install bash-completion + +`bpfman` uses the Rust crate `clap` for the CLI implementation. +`clap` has an optional Rust crate `clap_complete`. For `bash` shell, it leverages +`bash-completion` for CLI Command completion. +So in order for CLI completion to work in a `bash` shell, `bash-completion` +must be installed. +This feature is optional. + +For the CLI completion to work after installation, `/etc/profile.d/bash_completion.sh` +must be sourced in the running sessions. +New login sessions should pick it up automatically. + +`dnf` based OS: + +```console +sudo dnf install bash-completion +source /etc/profile.d/bash_completion.sh +``` + +`apt` based OS: + +```console +sudo apt install bash-completion +source /etc/profile.d/bash_completion.sh +``` + ### Install Yaml Formatter As part of CI, the Yaml files are validated with a Yaml formatter. Optionally, to verify locally, install the [YAML Language Support by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) -VsCode Extension, or to format in bulk, install`prettier`. +VsCode Extension, or to format in bulk, install `prettier`. To install `prettier`: @@ -194,3 +335,18 @@ And to write changes in place, run: ```console prettier -f "*.yaml" ``` + +### Install toml Formatter + +As part of CI, the toml files are validated with a toml formatter. +Optionally, to verify locally, install `taplo`. + +```console +cargo install taplo-cli +``` + +And to verify locally: + +```console +taplo fmt --check +``` diff --git a/docs/getting-started/cli-guide.md b/docs/getting-started/cli-guide.md index bd2564e0e..806eee3df 100644 --- a/docs/getting-started/cli-guide.md +++ b/docs/getting-started/cli-guide.md @@ -37,22 +37,21 @@ Below are the commands supported by `bpfman`. ```console sudo bpfman --help -A system daemon for loading BPF programs +An eBPF manager focusing on simplifying the deployment and administration of eBPF programs. Usage: bpfman Commands: - load Load an eBPF program from a local .o file - unload Unload an eBPF program using the program id - list List all eBPF programs loaded via bpfman - get Get an eBPF program using the program id - image eBPF Bytecode Image related commands - system Run bpfman as a service - help Print this message or the help of the given subcommand(s) + load Load an eBPF program on the system + unload Unload an eBPF program using the Program Id + list List all eBPF programs loaded via bpfman + get Get an eBPF program using the Program Id + image eBPF Bytecode Image related commands + help Print this message or the help of the given subcommand(s) Options: - -h, --help Print help - -V, --version Print version + -h, --help + Print help (see a summary with '-h') ``` ## bpfman load @@ -70,22 +69,21 @@ sudo bpfman load file --help Load an eBPF program from a local .o file Usage: bpfman load file [OPTIONS] --path --name ------- Commands: ---------- xdp Install an eBPF program on the XDP hook point for a given interface tc Install an eBPF program on the TC hook point for a given interface tracepoint Install an eBPF program on a Tracepoint - kprobe Install an eBPF kprobe or kretprobe - uprobe Install an eBPF uprobe or uretprobe + kprobe Install a kprobe or kretprobe eBPF probe + uprobe Install a uprobe or uretprobe eBPF probe + fentry Install a fentry eBPF probe + fexit Install a fexit eBPF probe help Print this message or the help of the given subcommand(s) Options: --------- -p, --path - Required: Location of local bytecode file as fully qualified file path. - Example: --path $HOME/src/bpfman/examples/go-xdp-counter/bpf_bpfel.o + Required: Location of local bytecode file + Example: --path /run/bpfman/examples/go-xdp-counter/bpf_bpfel.o -n, --name Required: The name of the function that is the entry point for the BPF program @@ -93,7 +91,7 @@ Options: -g, --global ... Optional: Global variables to be set when program is loaded. Format: = - + This is a very low level primitive. The caller is responsible for formatting the byte string appropriately considering such things as size, endianness, alignment and packing of data structures. @@ -102,12 +100,13 @@ Options: Optional: Specify Key/Value metadata to be attached to a program when it is loaded by bpfman. Format: = - - This can later be used to list a certain subset of programs which contain + + This can later be used to `list` a certain subset of programs which contain the specified metadata. + Example: --metadata owner=acme --map-owner-id - Optional: Program id of loaded eBPF program this eBPF program will share a map with. + Optional: Program Id of loaded eBPF program this eBPF program will share a map with. Only used when multiple eBPF programs need to share a map. Example: --map-owner-id 63178 @@ -127,8 +126,10 @@ Commands: xdp Install an eBPF program on the XDP hook point for a given interface tc Install an eBPF program on the TC hook point for a given interface tracepoint Install an eBPF program on a Tracepoint - kprobe Install an eBPF kprobe or kretprobe - uprobe Install an eBPF uprobe or uretprobe + kprobe Install a kprobe or kretprobe eBPF probe + uprobe Install a uprobe or uretprobe eBPF probe + fentry Install a fentry eBPF probe + fexit Install a fexit eBPF probe help Print this message or the help of the given subcommand(s) Options: @@ -173,7 +174,7 @@ Options: Example: --metadata owner=acme --map-owner-id - Optional: Program id of loaded eBPF program this eBPF program will share a map with. + Optional: Program Id of loaded eBPF program this eBPF program will share a map with. Only used when multiple eBPF programs need to share a map. Example: --map-owner-id 63178 @@ -192,10 +193,8 @@ sudo bpfman load file xdp --help Install an eBPF program on the XDP hook point for a given interface Usage: bpfman load file --path --name xdp [OPTIONS] --iface --priority ------- Options: --------- -i, --iface Required: Interface to load program on @@ -235,10 +234,8 @@ sudo bpfman load file tc -h Install an eBPF program on the TC hook point for a given interface Usage: bpfman load file --path --name tc [OPTIONS] --direction --iface --priority ------- Options: --------- -d, --direction Required: Direction to apply program. @@ -277,48 +274,62 @@ would be set as shown in the following snippet: SEC("classifier/pass") int accept(struct __sk_buff *skb) { + : +} ``` ### Additional Load Examples Below are some additional examples of `bpfman load` commands: -XDP +#### Fentry ```console -sudo bpfman load file --path $HOME/src/bpfman/examples/go-xdp-counter/bpf_bpfel.o --name "xdp_stats" xdp --iface vethb2795c7 --priority 35 +sudo bpfman load image --image-url quay.io/bpfman-bytecode/fentry:latest fentry -f do_unlinkat ``` -TC +#### Fexit ```console -sudo bpfman load file --path $HOME/src/bpfman/examples/go-tc-counter/bpf_bpfel.o --name "stats"" tc --direction ingress --iface vethb2795c7 --priority 110 +sudo bpfman load image --image-url quay.io/bpfman-bytecode/fexit:latest fexit -f do_unlinkat ``` -Kprobe +#### Kprobe ```console sudo bpfman load image --image-url quay.io/bpfman-bytecode/kprobe:latest kprobe -f try_to_wake_up ``` -Kretprobe +#### Kretprobe ```console sudo bpfman load image --image-url quay.io/bpfman-bytecode/kretprobe:latest kprobe -f try_to_wake_up -r ``` -Uprobe +#### TC + +```console +sudo bpfman load file --path $HOME/src/bpfman/examples/go-tc-counter/bpf_bpfel.o --name "stats"" tc --direction ingress --iface vethb2795c7 --priority 110 +``` + +#### Uprobe ```console sudo bpfman load image --image-url quay.io/bpfman-bytecode/uprobe:latest uprobe -f "malloc" -t "libc" ``` -Uretprobe +#### Uretprobe ```console sudo bpfman load image --image-url quay.io/bpfman-bytecode/uretprobe:latest uprobe -f "malloc" -t "libc" -r ``` +#### XDP + +```console +sudo bpfman load file --path $HOME/src/bpfman/examples/go-xdp-counter/bpf_bpfel.o --name "xdp_stats" xdp --iface vethb2795c7 --priority 35 +``` + ### Setting Global Variables in eBPF Programs Global variables can be set for any eBPF program type when loading as follows: @@ -373,7 +384,7 @@ sudo bpfman load file --path $HOME/src/bpfman/examples/go-xdp-counter/bpf_bpfel. 6373 ``` -Use the `bpfman get ` command to display the configuration: +Use the `bpfman get ` command to display the configuration: ```console sudo bpfman list @@ -459,16 +470,17 @@ sudo bpfman list --all 167 dump_bpf_prog tracing 2023-05-03T12:53:52-0400 455 cgroup_device 2023-05-03T12:58:26-0400 : - 6190 cgroup_skb 2023-07-17T17:15:23-0400 - 6191 cgroup_device 2023-07-17T17:15:23-0400 - 6192 cgroup_skb 2023-07-17T17:15:23-0400 - 6193 cgroup_skb 2023-07-17T17:15:23-0400 6194 cgroup_device 2023-07-17T17:15:23-0400 6201 pass xdp 2023-07-17T17:17:53-0400 6202 sys_enter_openat tracepoint 2023-07-17T17:19:09-0400 6203 dispatcher tc 2023-07-17T17:20:14-0400 6204 stats tc 2023-07-17T17:20:14-0400 6207 xdp xdp 2023-07-17T17:27:13-0400 + 6210 test_fentry tracing 2023-07-17T17:28:34-0400 + 6212 test_fexit tracing 2023-07-17T17:29:02-0400 + 6223 my_uprobe probe 2023-07-17T17:31:45-0400 + 6225 my_kretprobe probe 2023-07-17T17:32:27-0400 + 6928 my_kprobe probe 2023-07-17T17:33:49-0400 ``` To filter on a given program type, include the `--program-type` parameter: @@ -480,10 +492,14 @@ sudo bpfman list --all --program-type tc 6204 stats tc 2023-07-17T17:20:14-0400 ``` +Note: The list filters by the Kernel Program Type. +`kprobe`, `kretprobe`, `uprobe` and `uretprobe` all map to the `probe` Kernel Program Type. +`fentry` and `fexit` both map to the `tracing` Kernel Program Type. + ## bpfman get To retrieve detailed information for a loaded eBPF program, use the -`bpfman get ` command. +`bpfman get ` command. If the eBPF program was loaded via bpfman, then there will be a `Bpfman State` section with bpfman related attributes and a `Kernel State` section with kernel information. @@ -510,7 +526,7 @@ sudo bpfman get 6204 Kernel State ---------------------------------- - ID: 6204 + Program ID: 6204 Name: stats Type: tc Loaded At: 2023-07-17T17:20:14-0400 @@ -533,7 +549,7 @@ NONE Kernel State ---------------------------------- -ID: 6190 +Program ID: 6190 Name: None Type: cgroup_skb Loaded At: 2023-07-17T17:15:23-0400 @@ -571,7 +587,7 @@ by a load command. ```console sudo bpfman image pull --help -Pull a bytecode image for future use by a load command +Pull an eBPF bytecode image from a remote registry Usage: bpfman image pull [OPTIONS] --image-url @@ -610,7 +626,7 @@ Then when loaded, the local image will be used: sudo bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pass:latest --pull-policy IfNotPresent xdp --iface vethff657c7 --priority 100 Bpfman State --------------- -Name: pass + Name: pass Image URL: quay.io/bpfman-bytecode/xdp_pass:latest Pull Policy: IfNotPresent Global: None @@ -625,7 +641,7 @@ Name: pass Kernel State ---------------------------------- -ID: 406681 + Program ID: 406681 Name: pass Type: xdp Loaded At: 1917-01-27T01:37:06-0500 diff --git a/docs/getting-started/example-bpf-k8s.md b/docs/getting-started/example-bpf-k8s.md index 3690384e4..2c645ed05 100644 --- a/docs/getting-started/example-bpf-k8s.md +++ b/docs/getting-started/example-bpf-k8s.md @@ -1,9 +1,7 @@ # Deploying Example eBPF Programs On Kubernetes -This section will describe loading bytecode on a Kubernetes cluster and launching the userspace -program. +This section will describe launching eBPF enabled applications on a Kubernetes cluster. The approach is slightly different when running on a Kubernetes cluster. -The eBPF bytecode should be loaded by an administrator, not the userspace program itself. This section assumes there is already a Kubernetes cluster running and `bpfman` is running in the cluster. See [Deploying the bpfman-operator](../developer-guide/operator-quick-start.md) for details on @@ -14,20 +12,19 @@ cd bpfman/bpfman-operator/ make run-on-kind ``` -### Loading eBPF Bytecode On Kubernetes - -![go-xdp-counter On Kubernetes](../img/gocounter-on-k8s.png) +## Loading eBPF Programs On Kubernetes Instead of using the userspace program or CLI to load the eBPF bytecode as done in previous sections, the bytecode will be loaded by creating a Kubernetes CRD object. There is a CRD object for each eBPF program type bpfman supports. -Edit the sample yaml files to customize any configuration values: -* TcProgram CRD: [go-tc-counter/bytecode.yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-tc-counter/bytecode.yaml) -* TracepointProgram CRD: [go-tracepoint-counter/bytecode.yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-tracepoint-counter/bytecode.yaml) -* XdpProgram CRD: [go-xdp-counter/bytecode.yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-xdp-counter/bytecode.yaml) -* KprobeProgram CRD: [bpfman-operator/config/samples/bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml](https://github.com/bpfman/bpfman/blob/main/bpfman-operator/config/samples/bpfman.io_v1alpha1_kprobe_kprobeprogram.yaml) -* UprobeProgram CRD: [bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml](https://github.com/bpfman/bpfman/blob/main/bpfman-operator/config/samples/bpfman.io_v1alpha1_uprobe_uprobeprogram.yaml) +* FentryProgram CRD: [Fentry Sample yaml](https://github.com/bpfman/bpfman/blob/main/bpfman-operator/config/samples/bpfman.io_v1alpha1_fentry_fentryprogram.yaml) +* FexitProgram CRD: [Fexit Sample yaml](https://github.com/bpfman/bpfman/blob/main/bpfman-operator/config/samples/bpfman.io_v1alpha1_fexit_fexitprogram.yaml) +* KprobeProgram CRD: [Kprobe Examples yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-kprobe-counter/bytecode.yaml) +* TcProgram CRD: [TcProgram Examples yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-tc-counter/bytecode.yaml) +* TracepointProgram CRD: [Tracepoint Examples yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-tracepoint-counter/bytecode.yaml) +* UprobeProgram CRD: [Uprobe Examples yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-uprobe-counter/bytecode.yaml) +* XdpProgram CRD: [XdpProgram Examples yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-xdp-counter/bytecode.yaml) Sample bytecode yaml with XdpProgram CRD: ```console @@ -52,21 +49,20 @@ spec: Note that all the sample yaml files are configured with the bytecode running on all nodes (`nodeselector: {}`). -This can be change to run on specific nodes, but the DaemonSet yaml for the userspace program, which +This can be configured to run on specific nodes, but the DaemonSet yaml for the userspace program, which is described below, should have an equivalent change. -Make any changes to the `go-xdp-counter-bytecode.yaml`, then repeat for `go-tc-counter-bytecode.yaml` -and `go-tracepoint-counter-bytecode.yaml` and then apply the updated yamls: + +Assume the following command is run: ```console kubectl apply -f examples/config/base/go-xdp-counter/bytecode.yaml xdpprogram.bpfman.io/go-xdp-counter-example created +``` -kubectl apply -f examples/config/base/go-tc-counter/bytecode.yaml - tcprogram.bpfman.io/go-tc-counter-example created +The diagram below shows `go-xdp-counter` example, but the other examples operate in +a similar fashion. -kubectl apply -f examples/config/base/go-tracepoint-counter/bytecode.yaml - tracepointprogram.bpfman.io/go-tracepoint-counter-example created -``` +![go-xdp-counter On Kubernetes](../img/gocounter-on-k8s.png) Following the diagram for XDP example (Blue numbers): @@ -80,8 +76,9 @@ When it sees a `XdpProgram` object created or modified, it makes sure a `BpfProg node exists. The name of the `BpfProgram` object is the `XdpProgram` object name with the node name and interface or attach point appended. +On a KIND Cluster, it would be similar to `go-xdp-counter-example-bpfman-deployment-control-plane-eth0`. 3. `bpfman-agent` then determines if it should be running on the given node, loads or unloads as needed -by making gRPC calls the `bpfman`. +by making gRPC calls the `bpfman-rpc`, which calls into the `bpfman` Library. `bpfman` behaves the same as described in the running locally example. 4. `bpfman-agent` finally updates the status of the `BpfProgram` object. 5. `bpfman-operator` watches all `BpfProgram` objects, and updates the status of the `XdpProgram` @@ -91,8 +88,8 @@ To retrieve information on the `XdpProgram` objects: ```console kubectl get xdpprograms -NAME PRIORITY DIRECTION -go-xdp-counter-example 55 +NAME BPFFUNCTIONNAME NODESELECTOR STATUS +go-xdp-counter-example xdp_stats {} ReconcileSuccess kubectl get xdpprograms go-xdp-counter-example -o yaml @@ -138,19 +135,12 @@ To retrieve information on the `BpfProgram` objects: ```console kubectl get bpfprograms -NAME AGE +NAME TYPE STATUS AGE : -4822-bpfman-deployment-control-plane 60m -4825-bpfman-deployment-control-plane 60m -go-tc-counter-example-bpfman-deployment-control-plane-eth0 61m -go-tracepoint-counter-example-bpfman-deployment-control-plane-syscalls-sys-enter-kill 61m -go-xdp-counter-example-bpfman-deployment-control-plane-eth0 61m -go-xdp-counter-sharing-map-example-bpfman-deployment-control-plane-eth0 60m -tc-dispatcher-4805-bpfman-deployment-control-plane 60m -xdp-dispatcher-4816-bpfman-deployment-control-plane 60m +go-xdp-counter-example-bpfman-deployment-control-plane-eth0 xdp bpfmanLoaded 11m -kubectl get go-xdp-counter-example-bpfman-deployment-control-plane-eth0 -o yaml +kubectl get bpfprograms go-xdp-counter-example-bpfman-deployment-control-plane-eth0 -o yaml apiVersion: bpfman.io/v1alpha1 kind: BpfProgram metadata: @@ -185,7 +175,7 @@ status: type: Loaded ``` -### Loading Userspace Container On Kubernetes +## Deploying an eBPF enabled application On Kubernetes Here, a userspace container is deployed to consume the map data generated by the eBPF counter program. @@ -232,18 +222,22 @@ spec: csi.bpfman.io/maps: xdp_stats_map <==== 1c) Map to be exposed to the container ``` -#### Loading A Userspace Container Image +### Loading A Userspace Container Image The userspace programs have been pre-built and can be found here: -* `quay.io/bpfman-userspace/go-tc-counter:latest` -* `quay.io/bpfman-userspace/go-tracepoint-counter:latest` -* `quay.io/bpfman-userspace/go-xdp-counter:latest` +* [quay.io/bpfman-userspace/go-kprobe-counter:latest](https://quay.io/organization/bpfman-userspace) +* [quay.io/bpfman-userspace/go-tc-counter:latest](https://quay.io/organization/bpfman-userspace) +* [quay.io/bpfman-userspace/go-tracepoint-counter:latest](https://quay.io/organization/bpfman-userspace) +* [quay.io/bpfman-userspace/go-uprobe-counter:latest](https://quay.io/organization/bpfman-userspace) +* [quay.io/bpfman-userspace/go-xdp-counter:latest](https://quay.io/organization/bpfman-userspace) The example yaml files below are loading from these image. +* [go-kprobe-counter/deployment.yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-kprobe-counter/deployment.yaml) * [go-tc-counter/deployment.yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-tc-counter/deployment.yaml) * [go-tracepoint-counter/deployment.yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-tracepoint-counter/deployment.yaml) +* [go-uprobe-counter/deployment.yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-uprobe-counter/deployment.yaml) * [go-xdp-counter/deployment.yaml](https://github.com/bpfman/bpfman/tree/main/examples/config/base/go-xdp-counter/deployment.yaml) The userspace program in a Kubernetes Deployment doesn't interacts directly with `bpfman` like it @@ -264,10 +258,13 @@ can be created for each program type as follows: ```console cd bpfman/ kubectl create -f examples/config/base/go-xdp-counter/deployment.yaml -kubectl create -f examples/config/base/go-tc-counter/deployment.yaml -kubectl create -f examples/config/base/go-tracepoint-counter/deployment.yaml ``` +This creates the `go-xdp-counter` userspace pod, but the other examples operate in +a similar fashion. + +![go-xdp-counter On Kubernetes](../img/gocounter-on-k8s.png) + Following the diagram for the XDP example (Green numbers): 1. The userspace program queries the KubeApiServer for a specific `BpfProgram` object. @@ -277,11 +274,10 @@ periodically read the counter values. To see if the userspace programs are working, view the logs: ```console +kubectl get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE -bpfman bpfman-daemon-jsgdh 3/3 Running 0 11m -bpfman bpfman-operator-6c5c8887f7-qk28x 2/2 Running 0 12m -go-tc-counter go-tc-counter-ds-9jv4g 1/1 Running 0 5m37s -go-tracepoint-counter go-tracepoint-counter-ds-2gzbt 1/1 Running 0 5m35s +bpfman bpfman-daemon-jsgdh 3/3 Running 0 11m +bpfman bpfman-operator-6c5c8887f7-qk28x 2/2 Running 0 12m go-xdp-counter go-xdp-counter-ds-2hs6g 1/1 Running 0 6m12s : @@ -302,15 +298,9 @@ To cleanup: ```console kubectl delete -f examples/config/base/go-xdp-counter/deployment.yaml kubectl delete -f examples/config/base/go-xdp-counter/bytecode.yaml - -kubectl delete -f examples/config/base/go-tc-counter/deployment.yaml -kubectl delete -f examples/config/base/go-tc-counter/bytecode.yaml - -kubectl delete -f examples/config/base/go-tracepoint-counter/deployment.yaml -kubectl delete -f examples/config/base/go-tracepoint-counter/bytecode.yaml ``` -#### Automated Deployment +### Automated Deployment The steps above are automated in the `Makefile` in the examples directory. Run `make deploy` to load each of the example bytecode and userspace yaml files, then @@ -319,72 +309,64 @@ Run `make deploy` to load each of the example bytecode and userspace yaml files, ```console cd bpfman/examples/ make deploy + for target in deploy-tc deploy-tracepoint deploy-xdp deploy-xdp-ms deploy-kprobe deploy-target deploy-uprobe ; do \ + make $target || true; \ + done + make[1]: Entering directory '/home/bmcfall/go/src/github.com/bpfman/bpfman/examples' sed 's@URL_BC@quay.io/bpfman-bytecode/go-tc-counter:latest@' config/default/go-tc-counter/patch.yaml.env > config/default/go-tc-counter/patch.yaml - cd config/default/go-tc-counter && /home/bmcfall/src/bpfman/examples/bin/kustomize edit set image quay.io/bpfman-userspace/go-tc-counter=quay.io/bpfman-userspace/go-tc-counter:latest - /home/bmcfall/src/bpfman/examples/bin/kustomize build config/default/go-tc-counter | kubectl apply -f - + cd config/default/go-tc-counter && /home/bmcfall/go/src/github.com/bpfman/bpfman/examples/bin/kustomize edit set image quay.io/bpfman-userspace/go-tc-counter=quay.io/bpfman-userspace/go-tc-counter:latest namespace/go-tc-counter created serviceaccount/bpfman-app-go-tc-counter created - clusterrolebinding.rbac.authorization.k8s.io/bpfman-app-rolebinding-go-tc-counter created - clusterrolebinding.rbac.authorization.k8s.io/privileged-scc-tc created daemonset.apps/go-tc-counter-ds created tcprogram.bpfman.io/go-tc-counter-example created - sed 's@URL_BC@quay.io/bpfman-bytecode/go-tracepoint-counter:latest@' config/default/go-tracepoint-counter/patch.yaml.env > config/default/go-tracepoint-counter/patch.yaml - cd config/default/go-tracepoint-counter && /home/bmcfall/src/bpfman/examples/bin/kustomize edit set image quay.io/bpfman-userspace/go-tracepoint-counter=quay.io/bpfman-userspace/go-tracepoint-counter:latest - /home/bmcfall/src/bpfman/examples/bin/kustomize build config/default/go-tracepoint-counter | kubectl apply -f - - namespace/go-tracepoint-counter created - serviceaccount/bpfman-app-go-tracepoint-counter created - clusterrolebinding.rbac.authorization.k8s.io/bpfman-app-rolebinding-go-tracepoint-counter created - clusterrolebinding.rbac.authorization.k8s.io/privileged-scc-tracepoint created - daemonset.apps/go-tracepoint-counter-ds created - tracepointprogram.bpfman.io/go-tracepoint-counter-example created - sed 's@URL_BC@quay.io/bpfman-bytecode/go-xdp-counter:latest@' config/default/go-xdp-counter/patch.yaml.env > config/default/go-xdp-counter/patch.yaml - cd config/default/go-xdp-counter && /home/bmcfall/src/bpfman/examples/bin/kustomize edit set image quay.io/bpfman-userspace/go-xdp-counter=quay.io/bpfman-userspace/go-xdp-counter:latest - /home/bmcfall/src/bpfman/examples/bin/kustomize build config/default/go-xdp-counter | kubectl apply -f - - namespace/go-xdp-counter unchanged - serviceaccount/bpfman-app-go-xdp-counter unchanged - clusterrolebinding.rbac.authorization.k8s.io/bpfman-app-rolebinding-go-xdp-counter unchanged - clusterrolebinding.rbac.authorization.k8s.io/privileged-scc-xdp unchanged - daemonset.apps/go-xdp-counter-ds configured - xdpprogram.bpfman.io/go-xdp-counter-example unchanged - sed 's@URL_BC@quay.io/bpfman-bytecode/go-xdp-counter:latest@' config/default/go-xdp-counter-sharing-map/patch.yaml.env > config/default/go-xdp-counter-sharing-map/patch.yaml - cd config/default/go-xdp-counter-sharing-map && /home/bmcfall/src/bpfman/examples/bin/kustomize edit set image quay.io/bpfman-userspace/go-xdp-counter=quay.io/bpfman-userspace/go-xdp-counter:latest - /home/bmcfall/src/bpfman/examples/bin/kustomize build config/default/go-xdp-counter-sharing-map | kubectl apply -f - - xdpprogram.bpfman.io/go-xdp-counter-sharing-map-example created + : + sed 's@URL_BC@quay.io/bpfman-bytecode/go-uprobe-counter:latest@' config/default/go-uprobe-counter/patch.yaml.env > config/default/go-uprobe-counter/patch.yaml + cd config/default/go-uprobe-counter && /home/bmcfall/go/src/github.com/bpfman/bpfman/examples/bin/kustomize edit set image quay.io/bpfman-userspace/go-uprobe-counter=quay.io/bpfman-userspace/go-uprobe-counter:latest + namespace/go-uprobe-counter created + serviceaccount/bpfman-app-go-uprobe-counter created + daemonset.apps/go-uprobe-counter-ds created + uprobeprogram.bpfman.io/go-uprobe-counter-example created + make[1]: Leaving directory '/home/bmcfall/go/src/github.com/bpfman/bpfman/examples' # Test Away ... +kubectl get pods -A +NAMESPACE NAME READY STATUS RESTARTS AGE +bpfman bpfman-daemon-md2c5 3/3 Running 0 2d17h +bpfman bpfman-operator-7f67bc7c57-95zf7 2/2 Running 0 2d17h +go-kprobe-counter go-kprobe-counter-ds-8dkls 1/1 Running 0 2m14s +go-target go-target-ds-nbdf5 1/1 Running 0 2m14s +go-tc-counter go-tc-counter-ds-7mtcw 1/1 Running 0 2m19s +go-tracepoint-counter go-tracepoint-counter-ds-bcbs7 1/1 Running 0 2m18s +go-uprobe-counter go-uprobe-counter-ds-j26hc 1/1 Running 0 2m13s +go-xdp-counter go-xdp-counter-ds-nls6s 1/1 Running 0 2m17s + +kubectl get bpfprograms +NAME TYPE STATUS AGE +go-kprobe-counter-example-bpfman-deployment-control-plane-try-to-wake-up kprobe bpfmanLoaded 2m41s +go-tc-counter-example-bpfman-deployment-control-plane-eth0 tc bpfmanLoaded 2m46s +go-tracepoint-counter-example-bpfman-deployment-control-plane-syscalls-sys-enter-kill tracepoint bpfmanLoaded 2m35s +go-uprobe-counter-example-bpfman-deployment-control-plane--go-target-go-target-ds-nbdf5-go-target uprobe bpfmanLoaded 2m29s +go-xdp-counter-example-bpfman-deployment-control-plane-eth0 xdp bpfmanLoaded 2m24s +go-xdp-counter-sharing-map-example-bpfman-deployment-control-plane-eth0 xdp bpfmanLoaded 2m21s + make undeploy + for target in undeploy-tc undeploy-tracepoint undeploy-xdp undeploy-xdp-ms undeploy-kprobe undeploy-uprobe undeploy-target ; do \ + make $target || true; \ + done + make[1]: Entering directory '/home/bmcfall/go/src/github.com/bpfman/bpfman/examples' sed 's@URL_BC@quay.io/bpfman-bytecode/go-tc-counter:latest@' config/default/go-tc-counter/patch.yaml.env > config/default/go-tc-counter/patch.yaml - cd config/default/go-tc-counter && /home/bmcfall/src/bpfman/examples/bin/kustomize edit set image quay.io/bpfman-userspace/go-tc-counter=quay.io/bpfman-userspace/go-tc-counter:latest - /home/bmcfall/src/bpfman/examples/bin/kustomize build config/default/go-tc-counter | kubectl delete --ignore-not-found=false -f - + cd config/default/go-tc-counter && /home/bmcfall/go/src/github.com/bpfman/bpfman/examples/bin/kustomize edit set image quay.io/bpfman-userspace/go-tc-counter=quay.io/bpfman-userspace/go-tc-counter:latest namespace "go-tc-counter" deleted serviceaccount "bpfman-app-go-tc-counter" deleted - clusterrolebinding.rbac.authorization.k8s.io "bpfman-app-rolebinding-go-tc-counter" deleted - clusterrolebinding.rbac.authorization.k8s.io "privileged-scc-tc" deleted daemonset.apps "go-tc-counter-ds" deleted tcprogram.bpfman.io "go-tc-counter-example" deleted - sed 's@URL_BC@quay.io/bpfman-bytecode/go-tracepoint-counter:latest@' config/default/go-tracepoint-counter/patch.yaml.env > config/default/go-tracepoint-counter/patch.yaml - cd config/default/go-tracepoint-counter && /home/bmcfall/src/bpfman/examples/bin/kustomize edit set image quay.io/bpfman-userspace/go-tracepoint-counter=quay.io/bpfman-userspace/go-tracepoint-counter:latest - /home/bmcfall/src/bpfman/examples/bin/kustomize build config/default/go-tracepoint-counter | kubectl delete --ignore-not-found=false -f - - namespace "go-tracepoint-counter" deleted - serviceaccount "bpfman-app-go-tracepoint-counter" deleted - clusterrolebinding.rbac.authorization.k8s.io "bpfman-app-rolebinding-go-tracepoint-counter" deleted - clusterrolebinding.rbac.authorization.k8s.io "privileged-scc-tracepoint" deleted - daemonset.apps "go-tracepoint-counter-ds" deleted - tracepointprogram.bpfman.io "go-tracepoint-counter-example" deleted - sed 's@URL_BC@quay.io/bpfman-bytecode/go-xdp-counter:latest@' config/default/go-xdp-counter/patch.yaml.env > config/default/go-xdp-counter/patch.yaml - cd config/default/go-xdp-counter && /home/bmcfall/src/bpfman/examples/bin/kustomize edit set image quay.io/bpfman-userspace/go-xdp-counter=quay.io/bpfman-userspace/go-xdp-counter:latest - /home/bmcfall/src/bpfman/examples/bin/kustomize build config/default/go-xdp-counter | kubectl delete --ignore-not-found=false -f - - namespace "go-xdp-counter" deleted - serviceaccount "bpfman-app-go-xdp-counter" deleted - clusterrolebinding.rbac.authorization.k8s.io "bpfman-app-rolebinding-go-xdp-counter" deleted - clusterrolebinding.rbac.authorization.k8s.io "privileged-scc-xdp" deleted - daemonset.apps "go-xdp-counter-ds" deleted - xdpprogram.bpfman.io "go-xdp-counter-example" deleted - sed 's@URL_BC@quay.io/bpfman-bytecode/go-xdp-counter:latest@' config/default/go-xdp-counter-sharing-map/patch.yaml.env > config/default/go-xdp-counter-sharing-map/patch.yaml - cd config/default/go-xdp-counter-sharing-map && /home/bmcfall/src/bpfman/examples/bin/kustomize edit set image quay.io/bpfman-userspace/go-xdp-counter=quay.io/bpfman-userspace/go-xdp-counter:latest - /home/bmcfall/src/bpfman/examples/bin/kustomize build config/default/go-xdp-counter-sharing-map | kubectl delete --ignore-not-found=false -f - - xdpprogram.bpfman.io "go-xdp-counter-sharing-map-example" deleted + : + kubectl delete -f config/base/go-target/deployment.yaml + namespace "go-target" deleted + serviceaccount "bpfman-app-go-target" deleted + daemonset.apps "go-target-ds" deleted + make[1]: Leaving directory '/home/bmcfall/go/src/github.com/bpfman/bpfman/examples' ``` Individual examples can be loaded and unloaded as well, for example `make deploy-xdp` and @@ -415,7 +397,7 @@ Build build-us-images Build all example userspace images build-bc-images Build bytecode example userspace images push-us-images Push all example userspace images - push-bc-images Push all example userspace images + push-bc-images Push all example bytecode images load-us-images-kind Build and load all example userspace images into kind Deployment Variables (not commands) @@ -426,6 +408,11 @@ Deployment Variables (not commands) IMAGE_TP_US Tracepoint Userspace image. Example: make deploy-tracepoint IMAGE_TP_US=quay.io/user1/go-tracepoint-counter-userspace:test IMAGE_XDP_BC XDP Bytecode image. Example: make deploy-xdp IMAGE_XDP_BC=quay.io/user1/go-xdp-counter-bytecode:test IMAGE_XDP_US XDP Userspace image. Example: make deploy-xdp IMAGE_XDP_US=quay.io/user1/go-xdp-counter-userspace:test + IMAGE_KP_BC Kprobe Bytecode image. Example: make deploy-kprobe IMAGE_KP_BC=quay.io/user1/go-kprobe-counter-bytecode:test + IMAGE_KP_US Kprobe Userspace image. Example: make deploy-kprobe IMAGE_KP_US=quay.io/user1/go-kprobe-counter-userspace:test + IMAGE_UP_BC Uprobe Bytecode image. Example: make deploy-uprobe IMAGE_UP_BC=quay.io/user1/go-uprobe-counter-bytecode:test + IMAGE_UP_US Uprobe Userspace image. Example: make deploy-uprobe IMAGE_UP_US=quay.io/user1/go-uprobe-counter-userspace:test + IMAGE_GT_US Uprobe Userspace target. Example: make deploy-target IMAGE_GT_US=quay.io/user1/go-target-userspace:test KIND_CLUSTER_NAME Name of the deployed cluster to load example images to, defaults to `bpfman-deployment` ignore-not-found For any undeploy command, set to true to ignore resource not found errors during deletion. Example: make undeploy ignore-not-found=true @@ -438,21 +425,30 @@ Deployment undeploy-xdp Undeploy go-xdp-counter from the cluster specified in ~/.kube/config. deploy-xdp-ms Deploy go-xdp-counter-sharing-map (shares map with go-xdp-counter) to the cluster specified in ~/.kube/config. undeploy-xdp-ms Undeploy go-xdp-counter-sharing-map from the cluster specified in ~/.kube/config. + deploy-kprobe Deploy go-kprobe-counter to the cluster specified in ~/.kube/config. + undeploy-kprobe Undeploy go-kprobe-counter from the cluster specified in ~/.kube/config. + deploy-uprobe Deploy go-uprobe-counter to the cluster specified in ~/.kube/config. + undeploy-uprobe Undeploy go-uprobe-counter from the cluster specified in ~/.kube/config. + deploy-target Deploy go-target to the cluster specified in ~/.kube/config. + undeploy-target Undeploy go-target from the cluster specified in ~/.kube/config. deploy Deploy all examples to the cluster specified in ~/.kube/config. undeploy Undeploy all examples to the cluster specified in ~/.kube/config. ``` -#### Building A Userspace Container Image +### Building A Userspace Container Image To build the userspace examples in a container instead of using the pre-built ones, -from the bpfman code source directory (`quay.io/bpfman-userspace/`), run the following build commands: +from the bpfman examples code source directory, run the following build command: ```console - cd bpfman/examples - make IMAGE_TC_US=quay.io/$USER/go-tc-counter:latest \ - IMAGE_TP_US=quay.io/$USER/go-tracepoint-counter:latest \ - IMAGE_XDP_US=quay.io/$USER/go-xdp-counter:latest \ - build-us-images +cd bpfman/examples +make \ + IMAGE_KP_US=quay.io/$USER/go-kprobe-counter:latest \ + IMAGE_TC_US=quay.io/$USER/go-tc-counter:latest \ + IMAGE_TP_US=quay.io/$USER/go-tracepoint-counter:latest \ + IMAGE_UP_US=quay.io/$USER/go-uprobe-counter:latest \ + IMAGE_XDP_US=quay.io/$USER/go-xdp-counter:latest \ + build-us-images ``` Then **EITHER** push images to a remote repository: @@ -460,8 +456,11 @@ Then **EITHER** push images to a remote repository: ```console docker login quay.io cd bpfman/examples -make IMAGE_TC_US=quay.io/$USER/go-tc-counter:latest \ +make \ + IMAGE_KP_US=quay.io/$USER/go-kprobe-counter:latest \ + IMAGE_TC_US=quay.io/$USER/go-tc-counter:latest \ IMAGE_TP_US=quay.io/$USER/go-tracepoint-counter:latest \ + IMAGE_UP_US=quay.io/$USER/go-uprobe-counter:latest \ IMAGE_XDP_US=quay.io/$USER/go-xdp-counter:latest \ push-us-images ``` @@ -470,8 +469,11 @@ make IMAGE_TC_US=quay.io/$USER/go-tc-counter:latest \ ```console cd bpfman/examples -make IMAGE_TC_US=quay.io/$USER/go-tc-counter:latest \ +make \ + IMAGE_KP_US=quay.io/$USER/go-kprobe-counter:latest \ + IMAGE_TC_US=quay.io/$USER/go-tc-counter:latest \ IMAGE_TP_US=quay.io/$USER/go-tracepoint-counter:latest \ + IMAGE_UP_US=quay.io/$USER/go-uprobe-counter:latest \ IMAGE_XDP_US=quay.io/$USER/go-xdp-counter:latest \ KIND_CLUSTER_NAME=bpfman-deployment \ load-us-images-kind @@ -481,12 +483,19 @@ Lastly, update the yaml to use the private images or override the yaml files usi ```console cd bpfman/examples/ -make deploy-xdp IMAGE_XDP_US=quay.io/$USER/go-xdp-counter:latest -make undeploy-xdp + +make deploy-kprobe IMAGE_XDP_US=quay.io/$USER/go-kprobe-counter:latest +make undeploy-kprobe make deploy-tc IMAGE_TC_US=quay.io/$USER/go-tc-counter:latest make undeploy-tc make deploy-tracepoint IMAGE_TP_US=quay.io/$USER/go-tracepoint-counter:latest make undeploy-tracepoint + +make deploy-uprobe IMAGE_XDP_US=quay.io/$USER/go-uprobe-counter:latest +make undeploy-uprobe + +make deploy-xdp IMAGE_XDP_US=quay.io/$USER/go-xdp-counter:latest +make undeploy-xdp ``` diff --git a/docs/getting-started/example-bpf-local.md b/docs/getting-started/example-bpf-local.md index e0cd9f37c..f3d4dba5f 100644 --- a/docs/getting-started/example-bpf-local.md +++ b/docs/getting-started/example-bpf-local.md @@ -1,127 +1,57 @@ # Deploying Example eBPF Programs On Local Host This section describes running bpfman and the example eBPF programs on a local host. -When running bpfman, it can be run as a process or run as a systemd service. -Examples run the same, independent of how bpfman is deployed. -### Building +## Example Overview -To build directly on a system, make sure all the prerequisites are met, then build. - -#### Prerequisites - -_This assumes bpfman is already installed and running on the system. -If not, see [Setup and Building bpfman](./building-bpfman.md)._ - -1. All [requirements defined by the `cilium/ebpf` package](https://github.com/cilium/ebpf#requirements) -2. libbpf development package to get the required eBPF c headers - - Fedora: - - `sudo dnf install libbpf-devel` - - Ubuntu: - - `sudo apt-get install libbpf-dev` - -3. Cilium's `bpf2go` binary - - `go install github.com/cilium/ebpf/cmd/bpf2go@master` - -#### Building Locally - -To build all the C based eBPF counter bytecode, run: - -```console -cd bpfman/examples/ -make generate -``` - -To build all the Userspace GO Client examples, run: - -```console -cd bpfman/examples/ -make build -``` - -To build only a single example: - -```console -cd bpfman/examples/go-tc-counter/ -go generate -go build -``` - -```console -cd bpfman/examples/go-tracepoint-counter/ -go generate -go build -``` +Assume the following command is run: ```console cd bpfman/examples/go-xdp-counter/ -go generate -go build +sudo ./go-xdp-counter -iface eno3 ``` -### Running On Host - -The most basic way to deploy this example is running directly on a host system. -First, start or ensure `bpfman` is up and running. -[Tutorial](./tutorial.md) will guide you through deploying `bpfman`. -In all the examples of running on a host system, a bpfman-client certificate is used -that is generated by `bpfman` to encrypt the application's connection to `bpfman`. -The diagram below shows `go-xdp-counter` example, but the `go-tc-counter` and -`go-tracepoint-counter` examples operate exactly the same way. +The diagram below shows `go-xdp-counter` example, but the other examples operate in +a similar fashion. ![go-xdp-counter On Host](../img/gocounter-on-host.png) Following the diagram (Purple numbers): -1. When `go-xdp-counter` userspace is started, it will send a gRPC request - over unix socket to `bpfman` requesting `bpfman` to load the `go-xdp-counter` eBPF bytecode located on disk - at `bpfman/examples/go-xdp-counter/bpf_bpfel.o` at a priority of 50 and on interface `ens3`. +1. When `go-xdp-counter` userspace is started, it will send a gRPC request over unix + socket to `bpfman-rpc` requesting `bpfman` to load the `go-xdp-counter` eBPF bytecode located + on disk at `bpfman/examples/go-xdp-counter/bpf_bpfel.o` at a priority of 50 and on interface `eno3`. These values are configurable as we will see later, but for now we will use the defaults (except interface, which is required to be entered). 2. `bpfman` will load it's `dispatcher` eBPF program, which links to the `go-xdp-counter` eBPF program - and return a UUID referencing the running program. + and return a kernel Program ID referencing the running program. 3. `bpfman list` can be used to show that the eBPF program was loaded. 4. Once the `go-xdp-counter` eBPF bytecode is loaded, the eBPF program will write packet counts and byte counts to a shared map. 5. `go-xdp-counter` userspace program periodically reads counters from the shared map and logs the value. -#### Running Privileged +Below are the steps to run the example program described above and then some additional examples +that use the `bpfman` CLI to load and unload other eBPF programs. +See [Launching bpfman](../getting-started/launching-bpfman.md) for more detailed instructions on +building and loading bpfman. +This tutorial assumes bpfman has been built, `bpfman-rpc` is running, and the `bpfman` CLI is in $PATH. -To run the `go-xdp-counter` program, determine the host interface to attach the eBPF -program to and then start the go program with: +## Running Example Programs -```console -cd bpfman/examples/go-xdp-counter/ -sudo ./go-xdp-counter -iface -``` - -or (**NOTE:** TC programs also require a direction, ingress or egress) - -```console -cd bpfman/examples/go-tc-counter/ -sudo ./go-tc-counter -direction ingress -iface -``` - -or - -```console -cd bpfman/examples/go-tracepoint-counter/ -sudo ./go-tracepoint-counter -``` +[Example eBPF Programs](./example-bpf.md) describes how the example programs work, +how to build them, and how to run the different examples. +[Build](./example-bpf.md/#building-locally) the `go-xdp-counter` program before continuing. +To run the `go-xdp-counter` program, determine the host interface to attach the eBPF +program to and then start the go program. +In this example, `eno3` will be used, as shown in the diagram at the top of the page. The output should show the count and total bytes of packets as they pass through the interface as shown below: ```console -sudo ./go-xdp-counter --iface vethff657c7 -2023/07/17 17:43:58 Using Input: Interface=vethff657c7 Priority=50 Source=/home/<$USER>/src/bpfman/examples/go-xdp-counter/bpf_bpfel.o -2023/07/17 17:43:58 Unable to read /etc/bpfman/bpfman.toml, using default configuration values. +sudo ./go-xdp-counter --iface eno3 +2023/07/17 17:43:58 Using Input: Interface=eno3 Priority=50 Source=/home/<$USER>/src/bpfman/examples/go-xdp-counter/bpf_bpfel.o 2023/07/17 17:43:58 Program registered with id 6211 2023/07/17 17:44:01 4 packets received 2023/07/17 17:44:01 580 bytes received @@ -135,7 +65,7 @@ sudo ./go-xdp-counter --iface vethff657c7 : ``` -Use the CLI to show the `go-xdp-counter` eBPF bytecode was loaded. +In another terminal, use the CLI to show the `go-xdp-counter` eBPF bytecode was loaded. ```console sudo bpfman list @@ -155,177 +85,101 @@ Finally, press `+c` when finished with `go-xdp-counter`. 2023/07/17 17:44:35 Unloading Program: 6211 ``` -### Passing eBPF Bytecode In A Container Image - -bpfman can load eBPF bytecode from a container image built following the spec described in -[eBPF Bytecode Image Specifications](../developer-guide/shipping-bytecode.md). -Pre-built eBPF container images for the examples can be loaded from: - -- `quay.io/bpfman-bytecode/go-xdp-counter:latest` -- `quay.io/bpfman-bytecode/go-tc-counter:latest` -- `quay.io/bpfman-bytecode/go-tracepoint-counter:latest` - -To use the container image, pass the URL to the userspace program: - -```console -sudo ./go-xdp-counter -iface ens3 -image quay.io/bpfman-bytecode/go-xdp-counter:latest -2022/12/02 16:28:32 Unable to read /etc/bpfman/bpfman.toml, using default configuration values. -2022/12/02 16:28:32 Using Input: Interface=ens3 Priority=50 Source=quay.io/bpfman-bytecode/go-xdp-counter:latest -2022/12/02 16:28:34 Program registered with id 6223 -2022/12/02 16:28:37 4 packets received -2022/12/02 16:28:37 580 bytes received - -2022/12/02 16:28:40 4 packets received -2022/12/02 16:28:40 580 bytes received - -^C2022/12/02 16:28:42 Exiting... -2022/12/02 16:28:42 Unloading Program: 6223 -``` - -#### Building eBPF Bytecode Container Image - -[eBPF Bytecode Image Specifications](../developer-guide/shipping-bytecode.md) provides detailed -instructions on building and shipping bytecode in a container image. -To build `go-xdp-counter` and `go-tc-counter` eBPF bytecode container image, first make sure the -bytecode has been built (i.e. `bpf_bpfel.o` has been built - see [Building](#building])), then -run the build commands below: - -```console -cd bpfman/examples/go-xdp-counter/ -go generate - -docker build \ - --build-arg PROGRAM_NAME=go-xdp-counter \ - --build-arg BPF_FUNCTION_NAME=xdp_stats \ - --build-arg PROGRAM_TYPE=xdp \ - --build-arg BYTECODE_FILENAME=bpf_bpfel.o \ - --build-arg KERNEL_COMPILE_VER=$(uname -r) \ - -f ../../Containerfile.bytecode . -t quay.io/$USER/go-xdp-counter-bytecode:latest -``` - -and - -```console -cd bpfman/examples/go-tc-counter/ -go generate - -docker build \ - --build-arg PROGRAM_NAME=go-tc-counter \ - --build-arg BPF_FUNCTION_NAME=stats \ - --build-arg PROGRAM_TYPE=tc \ - --build-arg BYTECODE_FILENAME=bpf_bpfel.o \ - --build-arg KERNEL_COMPILE_VER=$(uname -r) \ - -f ../../Containerfile.bytecode . -t quay.io/$USER/go-tc-counter-bytecode:latest -``` +## Using CLI to Manage eBPF Programs -and +bpfman provides a CLI to interact with the `bpfman` Library. +Find a deeper dive into CLI syntax in [CLI Guide](./cli-guide.md). +We will load the simple `xdp-pass` program, which allows all traffic to pass through the attached +interface, `eno3` in this example. +The source code, +[xdp_pass.bpf.c](https://github.com/bpfman/bpfman/blob/main/tests/integration-test/bpf/xdp_pass.bpf.c), +is located in the [integration-test](https://github.com/bpfman/bpfman/tree/main/tests/integration-test) +directory and there is also a prebuilt image: +[quay.io/bpfman-bytecode/xdp_pass:latest](https://quay.io/bpfman-bytecode/). ```console -cd bpfman/examples/go-tracepoint-counter/ -go generate - -docker build \ - --build-arg PROGRAM_NAME=go-tracepoint-counter \ - --build-arg BPF_FUNCTION_NAME=tracepoint_kill_recorder \ - --build-arg PROGRAM_TYPE=tracepoint \ - --build-arg BYTECODE_FILENAME=bpf_bpfel.o \ - --build-arg KERNEL_COMPILE_VER=$(uname -r) \ - -f ../../Containerfile.bytecode . -t quay.io/$USER/go-tracepoint-counter-bytecode:latest -``` - -`bpfman` currently does not provide a method for pre-loading bytecode images -(see [issue #603](https://github.com/bpfman/bpfman/issues/603)), so push the bytecode image to a remote -repository. -For example: +sudo bpfman load image --image-url quay.io/bpfman-bytecode/xdp_pass:latest xdp --iface eno3 --priority 100 + Bpfman State +--------------- + Name: pass + Image URL: quay.io/bpfman-bytecode/xdp_pass:latest + Pull Policy: IfNotPresent + Global: None + Metadata: None + Map Pin Path: /run/bpfman/fs/maps/6213 + Map Owner ID: None + Map Used By: 6213 + Priority: 100 + Iface: eno3 + Position: 0 + Proceed On: pass, dispatcher_return -```console -docker login quay.io -docker push quay.io/$USER/go-xdp-counter-bytecode:latest -docker push quay.io/$USER/go-tc-counter-bytecode:latest + Kernel State +---------------------------------- + Program ID: 6213 + Name: pass + Type: xdp + Loaded At: 2023-07-17T17:48:10-0400 + Tag: 4b9d1b2c140e87ce + GPL Compatible: true + Map IDs: [2724] + BTF ID: 2834 + Size Translated (bytes): 96 + JITed: true + Size JITed (bytes): 67 + Kernel Allocated Memory (bytes): 4096 + Verified Instruction Count: 9 ``` -Then run with the privately built bytecode container image: +`bpfman load image` returns the same data as the `bpfman get` command. +From the output, the Program Id of `6213` can be found in the `Kernel State` section. +The Program Id can be used to perform a `bpfman get` to retrieve all relevant program +data and a `bpfman unload` when the program needs to be unloaded. ```console -sudo ./go-tc-counter -iface ens3 -direction ingress -location image://quay.io/$USER/go-tc-counter-bytecode:latest -2022/12/02 16:38:44 Unable to read /etc/bpfman/bpfman.toml, using default configuration values. -2022/12/02 16:38:44 Using Input: Interface=ens3 Priority=50 Source=quay.io/$USER/go-tc-counter-bytecode:latest -2022/12/02 16:38:45 Program registered with id 6225 -2022/12/02 16:38:48 4 packets received -2022/12/02 16:38:48 580 bytes received - -2022/12/02 16:38:51 4 packets received -2022/12/02 16:38:51 580 bytes received - -^C2022/12/02 16:38:51 Exiting... -2022/12/02 16:38:51 Unloading Program: 6225 +sudo bpfman list + Program ID Name Type Load Time + 6213 pass xdp 2023-07-17T17:48:10-0400 ``` -#### Preloading eBPF Bytecode - -Another way to load the eBPF bytecode is to pre-load the eBPF bytecode and -pass the associated `bpfman` program id to the userspace program. -This is similar to how eBPF programs will be loaded in Kubernetes, except `kubectl` commands will be -used to create Kubernetes CRD objects instead of using the CLI, but that is covered in the next section. -The userspace programs will skip the loading portion and use the program id to find the shared -map and continue from there. - -Referring back to the diagram above, the `load` and `unload` are being done by the CLI and not -`go-xdp-counter` userspace program. - -First, use the CLI to load the `go-xdp-counter` eBPF bytecode: +We can recheck the details about the loaded program with the `bpfman get` command: ```console -sudo bpfman load image --image-url quay.io/bpfman-bytecode/go-xdp-counter:latest xdp --iface ens3 --priority 50 +sudo bpfman get 6213 Bpfman State --------------- - Name: xdp_stats - Image URL: quay.io/bpfman-bytecode/go-xdp-counter:latest + Name: pass + Image URL: quay.io/bpfman-bytecode/xdp_pass:latest Pull Policy: IfNotPresent Global: None Metadata: None - Map Pin Path: /run/bpfman/fs/maps/6229 + Map Pin Path: /run/bpfman/fs/maps/6213 Map Owner ID: None - Map Used By: 6229 - Priority: 50 - Iface: ens3 + Map Used By: 6213 + Priority: 100 + Iface: eno3 Position: 0 Proceed On: pass, dispatcher_return Kernel State ---------------------------------- - ID: 6229 - Name: xdp_stats + Program ID: 6213 + Name: pass Type: xdp Loaded At: 2023-07-17T17:48:10-0400 Tag: 4b9d1b2c140e87ce GPL Compatible: true Map IDs: [2724] BTF ID: 2834 - Size Translated (bytes): 168 + Size Translated (bytes): 96 JITed: true - Size JITed (bytes): 104 + Size JITed (bytes): 67 Kernel Allocated Memory (bytes): 4096 - Verified Instruction Count: 21 -``` - -Then run the `go-xdp-counter` userspace program, passing in the UUID: - -```console -sudo ./go-xdp-counter -iface ens3 -id 6229 -2022/12/02 17:01:38 Using Input: Interface=ens3 Source=6229 -2022/12/02 17:01:41 180 packets received -2022/12/02 17:01:41 26100 bytes received - -2022/12/02 17:01:44 184 packets received -2022/12/02 17:01:44 26680 bytes received - -^C2022/12/02 17:01:46 Exiting... -2022/12/02 17:01:46 Closing Connection for Program: 6229 + Verified Instruction Count: 9 ``` -Then use the CLI to unload the eBPF bytecode: +Then unload the program: ```console -sudo bpfman unload 6229 +sudo bpfman unload 6213 ``` diff --git a/docs/getting-started/example-bpf.md b/docs/getting-started/example-bpf.md index e1fcf57fe..b1c7ff104 100644 --- a/docs/getting-started/example-bpf.md +++ b/docs/getting-started/example-bpf.md @@ -4,41 +4,292 @@ Example applications that use the `bpfman-go` bindings can be found in the [examples/](https://github.com/bpfman/bpfman/tree/main/examples/) directory. Current examples include: +* [examples/go-kprobe-counter/](https://github.com/bpfman/bpfman/tree/main/examples/go-kprobe-counter) * [examples/go-tc-counter/](https://github.com/bpfman/bpfman/tree/main/examples/go-tc-counter) * [examples/go-tracepoint-counter/](https://github.com/bpfman/bpfman/tree/main/examples/go-tracepoint-counter) +* [examples/go-uprobe-counter/](https://github.com/bpfman/bpfman/tree/main/examples/go-uprobe-counter) + * [examples/go-target/](https://github.com/bpfman/bpfman/tree/main/examples/go-target) * [examples/go-xdp-counter/](https://github.com/bpfman/bpfman/tree/main/examples/go-xdp-counter) -These examples and the associated documentation is intended to provide the basics on how to deploy +## Example Code Breakdown + +These examples and the associated documentation are intended to provide the basics on how to deploy and manage an eBPF program using bpfman. Each of the examples contain an eBPF Program written in C -([tc_counter.c](https://github.com/bpfman/bpfman/tree/main/examples/go-tc-counter/bpf/tc_counter.c), -[tracepoint_counter.c](https://github.com/bpfman/bpfman/tree/main/examples/go-tracepoint-counter/bpf/tracepoint_counter.c) and +([kprobe_counter.c](https://github.com/bpfman/bpfman/tree/main/examples/go-kprobe-counter/bpf/kprobe_counter.c), +[tc_counter.c](https://github.com/bpfman/bpfman/tree/main/examples/go-tc-counter/bpf/tc_counter.c), +[tracepoint_counter.c](https://github.com/bpfman/bpfman/tree/main/examples/go-tracepoint-counter/bpf/tracepoint_counter.c) [uprobe_counter.c](https://github.com/bpfman/bpfman/tree/main/examples/go-uprobe-counter/bpf/uprobe_counter.c), +and [xdp_counter.c](https://github.com/bpfman/bpfman/tree/main/examples/go-xdp-counter/bpf/xdp_counter.c)) -that is compiled into eBPF bytecode. +that is compiled into eBPF bytecode (bpf_bpfel.o). Each time the eBPF program is called, it increments the packet and byte counts in a map that is accessible by the userspace portion. Each of the examples also have a userspace portion written in GO. -When run locally, the userspace program makes gRPC calls to `bpfman` requesting `bpfman` to load the eBPF program -at the requested hook point (XDP hook point, TC hook point or Tracepoint). -When run in a Kubernetes deployment, the `bpfman-agent` makes gRPC calls to `bpfman` requesting `bpfman` to load -the eBPF program based on a Custom Resource Definition (CRD), which is described in more detail in that section. -Independent of the deployment, the userspace program then polls the eBPF map every 3 seconds and logs the -current counts. The userspace code is leveraging the [cilium/ebpf library](https://github.com/cilium/ebpf) to manage the maps shared with the eBPF program. The example eBPF programs are very similar in functionality, and only vary where in the Linux networking stack they are inserted. -Read more about XDP and TC programs [here](https://docs.cilium.io/en/latest/bpf/progtypes/). +The userspace program then polls the eBPF map every 3 seconds and logs the current counts. + +The examples were written to either run locally on a host or run in a container in a Kubernetes +deployment. +The userspace code flow is slightly different depending on the deployment, so input parameters +dictate the deployment method. + +### Examples in Local Deployment + +When run locally, the userspace program makes gRPC calls to `bpfman-rpc` requesting `bpfman` to load +the eBPF program at the requested hook point (XDP hook point, TC hook point, Tracepoint, etc). +Data sent in the RPC request is either defaulted or passed in via input parameters. +To make the examples as simple as possible to run, all input data is defaulted (except the interface +TC and XDP programs need to attach to) but can be overwritten if desired. All example programs have +the following common parameters (kprobe does not have any command specific parameters): + +```console +cd bpfman/examples/go-kprobe-counter/ + +./go-kprobe-counter --help +Usage of ./go-kprobe-counter: + -crd + Flag to indicate all attributes should be pulled from the BpfProgram CRD. + Used in Kubernetes deployments and is mutually exclusive with all other + parameters. + -file string + File path of bytecode source. "file" and "image"/"id" are mutually exclusive. + Example: -file /home/$USER/src/bpfman/examples/go-kprobe-counter/bpf_bpfel.o + -id uint + Optional Program ID of bytecode that has already been loaded. "id" and + "file"/"image" are mutually exclusive. + Example: -id 28341 + -image string + Image repository URL of bytecode source. "image" and "file"/"id" are + mutually exclusive. + Example: -image quay.io/bpfman-bytecode/go-kprobe-counter:latest + -map_owner_id int + Program Id of loaded eBPF program this eBPF program will share a map with. + Example: -map_owner_id 9785 +``` + +The location of the eBPF bytecode can be provided four different ways: + +* Defaulted: If nothing is passed in, the code scans the local directory for + a `bpf_bpfel.o` file. If found, that is used. If not, it errors out. +* **file**: Fully qualified path of the bytecode object file. +* **image**: Image repository URL of bytecode source. +* **id**: Kernel program Id of a bytecode that has already been loaded. This + program could have been loaded using `bpftool`, or `bpfman`. + +If two userspace programs need to share the same map, **map_owner_id** is the Program +ID of the first loaded program that has the map the second program wants to share. + +The examples require `sudo` to run because they require access the Unix socket `bpfman-rpc` +is listening on. +[Deploying Example eBPF Programs On Local Host](./example-bpf-local.md) steps through launching +`bpfman` locally and running some of the examples. + +### Examples in Kubernetes Deployment + +When run in a Kubernetes deployment, all the input data is passed to Kubernetes through yaml files. +To indicate to the userspace code that it is in a Kubernetes deployment and not to try to load +the eBPF bytecode, the example is launched in the container with the **crd** flag. +Example: `./go-kprobe-counter -crd` + +For these examples, the bytecode is loaded via one yaml file which creates a *Program CRD Object +(KprobeProgram, TcProgram, TracepointProgram, etc.) and the userspace pod is loaded via another yaml +file. +In a more realistic deployment, the userspace pod may have the logic to send the \*Program CRD Object +create request to the KubeAPI Server, but the two yaml files are load manually for simplicity in the +example code. +The [examples directory](https://github.com/bpfman/bpfman/tree/main/examples) contain yaml files to +load each example, leveraging [Kustomize](https://kustomize.io/) to modify the yaml to load the latest +images from Quay.io, to load custom images or released based images. +It is recommended to use the commands built into the Makefile, which run kustomize, to apply and remove +the yaml files to a Kubernetes cluster. +Use `make help` to see all the make options. +For example: + +```console +cd bpfman/examples/ + +# Deploy then undeploy all the examples +make deploy +make undeploy + +OR + +# Deploy then undeploy just the TC example +make deploy-tc +make undeploy-tc +``` + +[Deploying Example eBPF Programs On Kubernetes](./example-bpf-k8s.md) steps through deploying +bpfman to multiple nodes in a Kubernetes cluster and loading the examples. + +## Building Example Code + +All the examples can be built locally as well as packaged in a container for Kubernetes +deployment. + +### Building Locally + +To build directly on a system, make sure all the prerequisites are met, then build. + +#### Prerequisites + +_This assumes bpfman is already installed and running on the system. +If not, see [Setup and Building bpfman](./building-bpfman.md)._ + +1. All [requirements defined by the `cilium/ebpf` package](https://github.com/cilium/ebpf#requirements) +2. libbpf development package to get the required eBPF c headers + + **Fedora:** `sudo dnf install libbpf-devel` + + **Ubuntu:** `sudo apt-get install libbpf-dev` + +3. Cilium's `bpf2go` binary + + `go install github.com/cilium/ebpf/cmd/bpf2go@v0.11.0` + +#### Build + +To build all the C based eBPF counter bytecode, run: + +```console +cd bpfman/examples/ +make generate +``` + +To build all the Userspace GO Client examples, run: + +```console +cd bpfman/examples/ +make build +``` + +To build only a single example: + +```console +cd bpfman/examples/go-tc-counter/ +go generate +go build +``` + +```console +cd bpfman/examples/go-tracepoint-counter/ +go generate +go build +``` + +_Other program types are the same._ + +### Building eBPF Bytecode Container Image + +[eBPF Bytecode Image Specifications](../developer-guide/shipping-bytecode.md) provides detailed +instructions on building and shipping bytecode in a container image. +Pre-built eBPF container images for the examples can be loaded from: + +- `quay.io/bpfman-bytecode/go-kprobe-counter:latest` +- `quay.io/bpfman-bytecode/go-tc-counter:latest` +- `quay.io/bpfman-bytecode/go-tracepoint-counter:latest` +- `quay.io/bpfman-bytecode/go-uprobe-counter:latest` +- `quay.io/bpfman-bytecode/go-xdp-counter:latest` + +To build the example eBPF bytecode container images, run the build commands below (the `go generate` +requires the [Prerequisites](#prerequisites) described above): + +```console +cd bpfman/examples/go-xdp-counter/ +go generate + +docker build \ + --build-arg PROGRAM_NAME=go-xdp-counter \ + --build-arg BPF_FUNCTION_NAME=xdp_stats \ + --build-arg PROGRAM_TYPE=xdp \ + --build-arg BYTECODE_FILENAME=bpf_bpfel.o \ + --build-arg KERNEL_COMPILE_VER=$(uname -r) \ + -f ../../Containerfile.bytecode . -t quay.io/$USER/go-xdp-counter-bytecode:latest +``` + +and + +```console +cd bpfman/examples/go-tc-counter/ +go generate + +docker build \ + --build-arg PROGRAM_NAME=go-tc-counter \ + --build-arg BPF_FUNCTION_NAME=stats \ + --build-arg PROGRAM_TYPE=tc \ + --build-arg BYTECODE_FILENAME=bpf_bpfel.o \ + --build-arg KERNEL_COMPILE_VER=$(uname -r) \ + -f ../../Containerfile.bytecode . -t quay.io/$USER/go-tc-counter-bytecode:latest +``` + +_Other program types are the same._ + +`bpfman` currently does not provide a method for pre-loading bytecode images +(see [issue #603](https://github.com/bpfman/bpfman/issues/603)), so push the bytecode image to a remote +repository. +For example: + +```console +docker login quay.io +docker push quay.io/$USER/go-xdp-counter-bytecode:latest +docker push quay.io/$USER/go-tc-counter-bytecode:latest +``` + +Then run with the privately built bytecode container image: + +```console +sudo ./go-tc-counter -iface ens3 -direction ingress -image quay.io/$USER/go-tc-counter-bytecode:latest +2022/12/02 16:38:44 Using Input: Interface=ens3 Priority=50 Source=quay.io/$USER/go-tc-counter-bytecode:latest +2022/12/02 16:38:45 Program registered with id 6225 +2022/12/02 16:38:48 4 packets received +2022/12/02 16:38:48 580 bytes received + +2022/12/02 16:38:51 4 packets received +2022/12/02 16:38:51 580 bytes received + +^C2022/12/02 16:38:51 Exiting... +2022/12/02 16:38:51 Unloading Program: 6225 +``` + +## Running Examples + +```console +cd bpfman/examples/go-xdp-counter/ +sudo ./go-xdp-counter -iface +``` + +or (**NOTE:** TC programs also require a direction, ingress or egress) + +```console +cd bpfman/examples/go-tc-counter/ +sudo ./go-tc-counter -direction ingress -iface +``` + +or + +```console +cd bpfman/examples/go-tracepoint-counter/ +sudo ./go-tracepoint-counter +``` -There are two ways to deploy these example applications: +bpfman can load eBPF bytecode from a container image built following the spec described in +[eBPF Bytecode Image Specifications](../developer-guide/shipping-bytecode.md). -* Run locally on one machine: [Deploying Example eBPF Programs On Local Host](./example-bpf-local.md) -* Deploy to multiple nodes in a Kubernetes cluster: [Deploying Example eBPF Programs On Kubernetes](./example-bpf-k8s.md) +To use the container image, pass the URL to the userspace program: -## Notes +```console +sudo ./go-xdp-counter -iface ens3 -image quay.io/bpfman-bytecode/go-xdp-counter:latest +2022/12/02 16:28:32 Using Input: Interface=ens3 Priority=50 Source=quay.io/bpfman-bytecode/go-xdp-counter:latest +2022/12/02 16:28:34 Program registered with id 6223 +2022/12/02 16:28:37 4 packets received +2022/12/02 16:28:37 580 bytes received -Notes regarding this document: +2022/12/02 16:28:40 4 packets received +2022/12/02 16:28:40 580 bytes received -- Source of images used in the example documentation can be found in - [bpfman Upstream Images](https://docs.google.com/presentation/d/1wU4xu6xeyk9cB3G-Nn-dzkf90j1-EI4PB167G7v-Xl4/edit?usp=sharing). - Request access if required. +^C2022/12/02 16:28:42 Exiting... +2022/12/02 16:28:42 Unloading Program: 6223 +``` diff --git a/docs/getting-started/launching-bpfman.md b/docs/getting-started/launching-bpfman.md new file mode 100644 index 000000000..ddeae65cf --- /dev/null +++ b/docs/getting-started/launching-bpfman.md @@ -0,0 +1,140 @@ +# Launching bpfman + +The most basic way to deploy bpfman is to run it directly on a host system. +First `bpfman` needs to be built and then started. + +## Build bpfman + +Perform the following steps to build `bpfman`. +If this is your first time using bpfman, follow the instructions in +[Setup and Building bpfman](./building-bpfman.md) to setup the prerequisites for building. +To avoid installing the dependencies and having to build bpfman, consider running bpfman +from a packaged release (see [Run bpfman From Release Image](./running-release.md)) or +installing the bpfman RPM (see [Run bpfman From RPM](./running-rpm.md)). + +```console +cd bpfman/ +cargo build +``` + +## Start bpfman-rpc + +When running bpfman, the RPC Server `bpfman-rpc` can be run as a long running process or a +systemd service. +Examples run the same, independent of how bpfman is deployed. + +### Run as a Long Lived Process + +While learning and experimenting with `bpfman`, it may be useful to run `bpfman` in the foreground +(which requires a second terminal to run the `bpfman` CLI commands). +When run in this fashion, logs are dumped directly to the terminal. +For more details on how logging is handled in bpfman, see [Logging](../developer-guide/logging.md). + +```console +sudo RUST_LOG=info ./target/debug/bpfman-rpc --timeout=0 +[INFO bpfman::utils] Log using env_logger +[INFO bpfman::utils] Has CAP_BPF: true +[INFO bpfman::utils] Has CAP_SYS_ADMIN: true +[WARN bpfman::utils] Unable to read config file, using defaults +[INFO bpfman_rpc::serve] Using no inactivity timer +[INFO bpfman_rpc::serve] Using default Unix socket +[INFO bpfman_rpc::serve] Listening on /run/bpfman-sock/bpfman.sock +``` + +When a build is run for bpfman, built binaries can be found in `./target/debug/`. +So when launching `bpfman-rpc` and calling `bpfman` CLI commands, the binary must be in the $PATH +or referenced directly: + +```console +sudo ./target/debug/bpfman list +``` + +For readability, the remaining sample commands will assume the `bpfman` CLI binary is in the $PATH, +so `./target/debug/` will be dropped. + +### Run as a systemd Service + +Run the following command to copy the `bpfman` CLI and `bpfman-rpc` binaries to `/usr/sbin/` and +copy `bpfman.socket` and `bpfman.service` files to `/usr/lib/systemd/system/`. +This option will also enable and start the systemd services: + +```console +sudo ./scripts/setup.sh install +``` + +`bpfman` CLI is now in $PATH, so `./targer/debug/` is not needed: + +```console +sudo bpfman list +``` + +To view logs, use `journalctl`: + +```console +sudo journalctl -f -u bpfman.service -u bpfman.socket +Mar 27 09:13:54 server-calvin systemd[1]: Listening on bpfman.socket - bpfman API Socket. + +Mar 27 09:15:43 server-calvin systemd[1]: Started bpfman.service - Run bpfman as a service. +Mar 27 09:15:43 server-calvin bpfman-rpc[2548091]: Log using journald +Mar 27 09:15:43 server-calvin bpfman-rpc[2548091]: Has CAP_BPF: true +Mar 27 09:15:43 server-calvin bpfman-rpc[2548091]: Has CAP_SYS_ADMIN: true +Mar 27 09:15:43 server-calvin bpfman-rpc[2548091]: Unable to read config file, using defaults +Mar 27 09:15:43 server-calvin bpfman-rpc[2548091]: Using a Unix socket from systemd +Mar 27 09:15:43 server-calvin bpfman-rpc[2548091]: Using inactivity timer of 15 seconds +Mar 27 09:15:43 server-calvin bpfman-rpc[2548091]: Listening on /run/bpfman-sock/bpfman.sock +Mar 27 09:15:43 server-calvin bpfman-rpc[2548091]: Unable to read config file, using defaults +Mar 27 09:15:43 server-calvin bpfman-rpc[2548091]: Unable to read config file, using defaults +Mar 27 09:15:43 server-calvin bpfman-rpc[2548091]: Starting Cosign Verifier, downloading data from Sigstore TUF repository +Mar 27 09:15:45 server-calvin bpfman-rpc[2548091]: Loading program bytecode from file: /home//src/bpfman/examples/go-kprobe-counter/bpf_bpfel.o +Mar 27 09:15:45 server-calvin bpfman-rpc[2548091]: Added probe program with name: kprobe_counter and id: 7568 +Mar 27 09:15:48 server-calvin bpfman-rpc[2548091]: Unable to read config file, using defaults +Mar 27 09:15:48 server-calvin bpfman-rpc[2548091]: Removing program with id: 7568 +Mar 27 09:15:58 server-calvin bpfman-rpc[2548091]: Shutdown Unix Handler /run/bpfman-sock/bpfman.sock +Mar 27 09:15:58 server-calvin systemd[1]: bpfman.service: Deactivated successfully. +``` + +#### Additional Notes + +To update the configuration settings associated with running `bpfman` as a service, edit the +service configuration files: + +```console +sudo vi /usr/lib/systemd/system/bpfman.socket +sudo vi /usr/lib/systemd/system/bpfman.service +sudo systemctl daemon-reload +``` + +If `bpfman` CLI or `bpfman-rpc` is rebuilt, the following command can be run to install the update +binaries without tearing down `bpfman`. +The services are automatically restarted. + +```console +sudo ./scripts/setup.sh reinstall +``` + +To unwind all the changes, stop `bpfman` and remove all related files from the system, run the +following script: + +```console +sudo ./scripts/setup.sh uninstall +``` + +### Preferred Method to Start bpfman + +In order to call into the `bpfman` Library, the calling process must be privileged. +In order to load and unload eBPF, the kernel requires a set of powerful capabilities. +Long lived privileged processes are more vulnerable to attack than short lived processes. +When `bpfman-rpc` is run as a systemd service, it is leveraging +[socket activation](https://man7.org/linux/man-pages/man1/systemd-socket-activate.1.html). +This means that it loads a `bpfman.socket` and `bpfman.service` file. +The socket service is the long lived process, which doesn't have any special permissions. +The service that runs `bpfman-rpc` is only started when there is a request on the socket, +and then `bpfman-rpc` stops itself after an inactivity timeout. + +> For security reasons, it is recommended to run `bpfman-rpc` as a systemd service when running +on a local host. +For local development, some may find it useful to run `bpfman-rpc` as a long lived process. + +When run as a systemd service, the set of linux capabilities are limited to only the required set. +If permission errors are encountered, see [Linux Capabilities](../developer-guide/linux-capabilities.md) +for help debugging. diff --git a/docs/getting-started/overview.md b/docs/getting-started/overview.md new file mode 100644 index 000000000..b87588faa --- /dev/null +++ b/docs/getting-started/overview.md @@ -0,0 +1,35 @@ +# bpfman Overview + +Core bpfman is a library written in Rust and published as a Crate via crates.io. +The `bpfman` library leverages the `aya` library to manage eBPF programs. +Applications written in Rust can import the `bpfman` library and call the +bpfman APIs directly. +An example of a Rust based application leveraging the `bpfman` library is the +`bpfman` CLI, which is a Rust based binary used to provision bpfman from a +Linux command prompt (see [CLI Guide](./cli-guide.md)). + +For applications written in other languages, bpfman provides `bpfman-rpc`, a Rust +based bpfman RPC server binary. +Non-Rust applications can send a RPC message to the server, which translate the +RPC request into a bpfman library call. +The long term solution is to leverage the Rust Foreign Function Interface (FFI) +feature, which enables a different (foreign) programming language to call Rust +functions, but that is not supported at the moment. + +![bpfman library](../img/bpfman_library.png) + +The `bpfman-rpc` server can run in one of two modes. +It can be run as a long running process or as a systemd service that uses +[socket activation](https://man7.org/linux/man-pages/man1/systemd-socket-activate.1.html) +to start `bpfman-rpc` only when there is a RPC message to process. +More details are provided in [Deploying Example eBPF Programs On Local Host](./example-bpf-local.md). + +When deploying `bpfman` in a Kubernetes deployment, `bpfman-agent`, `bpfman-rpc`, and the +`bpfman` library are packaged in a container. +When the container starts, `bpfman-rpc` is started as a long running process. +`bpfman-agent` listens to the KubeAPI Server and send RPC requests to `bpfman-rpc`, which +in turn calls the `bpfman` library to manage eBPF programs on a given node. + +![bpfman library](../img/bpfman_container.png) + +More details provided in [Deploying Example eBPF Programs On Kubernetes](./example-bpf-k8s.md). diff --git a/docs/getting-started/running-release.md b/docs/getting-started/running-release.md index dd4e12d66..315b7f616 100644 --- a/docs/getting-started/running-release.md +++ b/docs/getting-started/running-release.md @@ -4,14 +4,20 @@ This section describes how to deploy `bpfman` from a given release. See [Releases](https://github.com/bpfman/bpfman/releases) for the set of bpfman releases. +> **Note:** Instructions for interacting with bpfman change from release to release, so reference +> release specific documentation. For example: +> +> [https://bpfman.io/v0.4.0/getting-started/running-release/](https://bpfman.io/v0.4.0/getting-started/running-release/) + Jump to the [Setup and Building bpfman](./building-bpfman.md) section for help building from the latest code or building from a release branch. -[Tutorial](./tutorial.md) contains more details on the different -modes to run `bpfman` in on the host and how to test. -Use [Local Host](#local-host) or [Systemd Service](#systemd-service) -below for deploying released version of `bpfman` and then use [Tutorial](./tutorial.md) -for further information on how to test and interact with `bpfman`. +[Start bpfman-rpc](./launching-bpfman.md/#start-bpfman-rpc) contains more details on the different +modes to run `bpfman` in on the host. +Use [Run using an rpm](./running-rpm.md) +for deploying a released version of `bpfman` from an rpm as a systemd service and then use +[Deploying Example eBPF Programs On Local Host](./example-bpf-local.md) +for further information on how to test and interact with `bpfman`. [Deploying the bpfman-operator](../developer-guide/operator-quick-start.md) contains more details on deploying `bpfman` in a Kubernetes deployment and @@ -19,79 +25,47 @@ more details on deploying `bpfman` in a Kubernetes deployment and more details on interacting with `bpfman` running in a Kubernetes deployment. Use [Deploying Release Version of the bpfman-operator](#deploying-release-version-of-the-bpfman-operator) below for deploying released version of `bpfman` in Kubernetes and then use the -links above for further information on how to test and interact with `bpfman`. - -## Local Host +links above for further information on how to test and interact with `bpfman`. -To run `bpfman` in the foreground using `sudo`, download the release binary tar -files and unpack them. +## Run as a Long Lived Process ```console -export BPFMAN_REL=0.3.1 +export BPFMAN_REL=0.4.0 mkdir -p $HOME/src/bpfman-${BPFMAN_REL}/; cd $HOME/src/bpfman-${BPFMAN_REL}/ wget https://github.com/bpfman/bpfman/releases/download/v${BPFMAN_REL}/bpfman-linux-x86_64.tar.gz tar -xzvf bpfman-linux-x86_64.tar.gz; rm bpfman-linux-x86_64.tar.gz $ tree . -├── bpfman-linux-x86_64.tar.gz -└── target - └── x86_64-unknown-linux-musl - └── release - └── bpfman +├── bpf-log-exporter +├── bpfman +├── bpfman-ns +├── bpfman-rpc +└── bpf-metrics-exporter ``` -To deploy `bpfman`: +To deploy `bpfman-rpc`: ```console -sudo RUST_LOG=info ./target/x86_64-unknown-linux-musl/release/bpfman -[2023-10-13T15:53:25Z INFO bpfman] Log using env_logger -[2023-10-13T15:53:25Z INFO bpfman] Has CAP_BPF: true -[2023-10-13T15:53:25Z INFO bpfman] Has CAP_SYS_ADMIN: true +sudo RUST_LOG=info ./bpfman-rpc --timeout=0 +[INFO bpfman::utils] Log using env_logger +[INFO bpfman::utils] Has CAP_BPF: true +[INFO bpfman::utils] Has CAP_SYS_ADMIN: true +[WARN bpfman::utils] Unable to read config file, using defaults +[INFO bpfman_rpc::serve] Using no inactivity timer +[INFO bpfman_rpc::serve] Using default Unix socket +[INFO bpfman_rpc::serve] Listening on /run/bpfman-sock/bpfman.sock : ``` To use the CLI: ```console -sudo ./target/x86_64-unknown-linux-musl/release/bpfman list - Program ID Name Type Load Time -``` - -Continue in [Tutorial](./tutorial.md) if desired. - -## Systemd Service - -To run `bpfman` as a systemd service, the binaries will be placed in a well known location -(`/usr/sbin/.`) and a service configuration file will be added -(`/usr/lib/systemd/system/bpfman.service`). -There is a script that is used to install the service properly, so the source code needs -to be downloaded to retrieve the script. -Download and unpack the source code, then download and unpack the binaries. - -```console -export BPFMAN_REL=0.3.1 -mkdir -p $HOME/src/; cd $HOME/src/ -wget https://github.com/bpfman/bpfman/archive/refs/tags/v${BPFMAN_REL}.tar.gz -tar -xzvf v${BPFMAN_REL}.tar.gz; rm v${BPFMAN_REL}.tar.gz -cd bpfman-${BPFMAN_REL} - -wget https://github.com/bpfman/bpfman/releases/download/v${BPFMAN_REL}/bpfman-linux-x86_64.tar.gz -tar -xzvf bpfman-linux-x86_64.tar.gz; rm bpfman-linux-x86_64.tar.gz -``` - -Run the following command to copy the `bpfman` binaries to `/usr/sbin/` and copy a -default `bpfman.service` file to `/usr/lib/systemd/system/`. -This option will also start the systemd service `bpfman.service` by default. - -```console -sudo ./scripts/setup.sh install +sudo ./bpfman list + Program ID Name Type Load Time ``` -> **NOTE:** If running a release older than `v0.3.1`, the install script is not coded to copy -binaries from the release directory, so the binaries will need to be manually copied. - -Continue in [Tutorial](./tutorial.md) if desired. +Continue in [Deploying Example eBPF Programs On Local Host](./example-bpf-local.md) if desired. ## Deploying Release Version of the bpfman-operator @@ -105,11 +79,12 @@ kind create cluster --name=test-bpfman Next, deploy the bpfman CRDs: ```console -export BPFMAN_REL=0.3.1 -kubectl apply -f https://github.com/bpfman/bpfman/releases/download/v${BPFMAN_REL}/bpfman-crds-install-v${BPFMAN_REL}.yaml +export BPFMAN_REL=0.4.0 +kubectl apply -f https://github.com/bpfman/bpfman/releases/download/v${BPFMAN_REL}/bpfman-crds-install.yaml ``` -Next, deploy the `bpfman-operator`, which will also deploy the `bpfman-daemon`, which contains `bpfman` and `bpfman-agent`: +Next, deploy the `bpfman-operator`, which will also deploy the `bpfman-daemon`, which contains +`bpfman-rpc`, `bpfman` Library and `bpfman-agent`: ```console kubectl apply -f https://github.com/bpfman/bpfman/releases/download/v${BPFMAN_REL}/bpfman-operator-install-v${BPFMAN_REL}.yaml @@ -126,3 +101,11 @@ page. Continue in [Deploying the bpfman-operator](../developer-guide/operator-quick-start.md) or [Deploying Example eBPF Programs On Kubernetes](./example-bpf-k8s.md) if desired. +Keep in mind that prior to v0.4.0, `bpfman` was released as `bpfd`. +So follow the release specific documentation. + +Use the following command to teardown the cluster: + +```console +kind delete cluster -n test-bpfman +``` diff --git a/docs/getting-started/running-rpm.md b/docs/getting-started/running-rpm.md new file mode 100644 index 000000000..2c1dfef4e --- /dev/null +++ b/docs/getting-started/running-rpm.md @@ -0,0 +1,185 @@ +# Run bpfman From RPM + +This section describes how to deploy `bpfman` from an RPM. +RPMs are generated each time a Pull Request is merged in github for Fedora 38, 39 and +Rawhide (see [Install Prebuilt RPM](#install-prebuilt-rpm) below). +RPMs can also be built locally from a Fedora server +(see [Build RPM Locally](#build-rpm-locally) below). + +## Install Prebuilt RPM + +This section describes how to install an RPM built automatically by the +[Packit Service](https://dashboard.packit.dev/projects/github.com/bpfman/bpfman). +The Packit Service builds RPMs for each Pull Request merged. + +### Packit Service Prerequisites + +To install an RPM generated by the Packit Service, the following packages need +to be installed: + +`dnf` based OS: + +```console +sudo dnf install -y dnf-plugins-core +sudo dnf copr enable @ebpf-sig/bpfman-next +``` + +### Install RPM From Packit Service + +To load an RPM from a specific commit, find the commit from +[bpfman commits](https://github.com/bpfman/bpfman/commits/main/), and click on +the green check showing a given Pull Request was verified. +At the bottom of the list of checks are the RPM builds, click on the `details`, +and follow the Packit Dashboard link to the `Copr Build Results`. +Then install the given RPM: + +```console +sudo dnf install -y bpfman-0.4.0~dev-1.20240117143006587102.main.191.gda44a71.fc38.x86_64 +``` + +`bpfman` is now installed but not running. +To start `bpfman`: + +```console +sudo systemctl daemon-reload +sudo systemctl enable bpfman.socket +sudo systemctl start bpfman.socket +``` + +Verify `bpfman` is installed and running: + +```console +$ sudo systemctl status bpfman.socket +● bpfman.socket - bpfman API Socket + Loaded: loaded (/usr/lib/systemd/system/bpfman.socket; enabled; preset: disabled) + Active: active (listening) since Thu 2024-01-18 21:19:29 EST; 5s ago + Triggers: ● bpfman.service + Listen: /run/bpfman-sock/bpfman.sock (Stream) + CGroup: /system.slice/bpfman.socket +: + +$ sudo systemctl status bpfman.service +○ bpfman.service - Run bpfman as a service + Loaded: loaded (/usr/lib/systemd/system/bpfman.service; static) + Drop-In: /usr/lib/systemd/system/service.d + └─10-timeout-abort.conf + Active: inactive (dead) +TriggeredBy: ● bpfman.socket +: + +$ sudo bpfman list + Program ID Name Type Load Time + +``` + +### Uninstall Given RPM + +To determine the RPM that is currently loaded: + +```console +$ sudo rpm -qa | grep bpfman +bpfman-0.4.0~dev-1.20240117143006587102.main.191.gda44a71.fc39.x86_64 +``` + +To uninstall the RPM: + +```console +sudo dnf erase -y bpfman-0.4.0~dev-1.20240117143006587102.main.191.gda44a71.fc39.x86_64 + +sudo systemctl daemon-reload +``` + +## Build RPM Locally + +This section describes how to build and install an RPM locally. + +### Local Build Prerequisites + +To build locally, the following packages need to be installed: + +`dnf` based OS: + +```console +sudo dnf install packit +sudo dnf install cargo-rpm-macros +``` + +> NOTE: `cargo-rpm-macros` needs to be version 25 or higher. +> It appears this is only available on Fedora 37, 38, 39 and Rawhide at the moment. + +### Build Locally + +To build locally, run the following command: + +```console +packit build locally +``` + +This will generate several RPMs in a `x86_64/` directory: + +```console +$ ls x86_64/ +bpfman-0.4.0~dev-1.20240118212420167308..rpm.socket.192.gb2ea1b9.fc39.x86_64.rpm +bpfman-debuginfo-0.4.0~dev-1.20240118212420167308..rpm.socket.192.gb2ea1b9.fc39.x86_64.rpm +bpfman-debugsource-0.4.0~dev-1.20240118212420167308..rpm.socket.192.gb2ea1b9.fc39.x86_64.rpm +``` + +### Install Local Build + +Install the RPM: + +```console +sudo rpm -i x86_64/bpfman-0.4.0~dev-1.20240118212420167308..rpm.socket.192.gb2ea1b9.fc39.x86_64.rpm +``` + +`bpfman` is now installed but not running. +To start `bpfman`: + +```console +sudo systemctl daemon-reload +sudo systemctl enable bpfman.socket +sudo systemctl start bpfman.socket +``` + +Verify `bpfman` is installed and running: + +```console +$ sudo systemctl status bpfman.socket +● bpfman.socket - bpfman API Socket + Loaded: loaded (/usr/lib/systemd/system/bpfman.socket; enabled; preset: disabled) + Active: active (listening) since Thu 2024-01-18 21:19:29 EST; 5s ago + Triggers: ● bpfman.service + Listen: /run/bpfman-sock/bpfman.sock (Stream) + CGroup: /system.slice/bpfman.socket +: + +$ sudo systemctl status bpfman.service +○ bpfman.service - Run bpfman as a service + Loaded: loaded (/usr/lib/systemd/system/bpfman.service; static) + Drop-In: /usr/lib/systemd/system/service.d + └─10-timeout-abort.conf + Active: inactive (dead) +TriggeredBy: ● bpfman.socket +: + +$ sudo bpfman list + Program ID Name Type Load Time + +``` + +### Uninstall Local Build + +To determine the RPM that is currently loaded: + +```console +$ sudo rpm -qa | grep bpfman +bpfman-0.4.0~dev-1.20240118212420167308..rpm.socket.192.gb2ea1b9.fc39.x86_64 +``` + +To uninstall the RPM: + +```console +sudo rpm -e bpfman-0.4.0~dev-1.20240118212420167308..rpm.socket.192.gb2ea1b9.fc39.x86_64 + +sudo systemctl daemon-reload +``` diff --git a/docs/img/bpfman-on-k8s.png b/docs/img/bpfman-on-k8s.png index 7061fd337..696586ff7 100644 Binary files a/docs/img/bpfman-on-k8s.png and b/docs/img/bpfman-on-k8s.png differ diff --git a/docs/img/bpfman.png b/docs/img/bpfman.png deleted file mode 100644 index b42044338..000000000 Binary files a/docs/img/bpfman.png and /dev/null differ diff --git a/docs/img/bpfman.svg b/docs/img/bpfman.svg deleted file mode 100644 index 75d454c62..000000000 --- a/docs/img/bpfman.svg +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/img/bpfman_banner.png b/docs/img/bpfman_banner.png new file mode 100644 index 000000000..1f69fb891 Binary files /dev/null and b/docs/img/bpfman_banner.png differ diff --git a/docs/img/bpfman_banner.svg b/docs/img/bpfman_banner.svg new file mode 100644 index 000000000..e0272a20d --- /dev/null +++ b/docs/img/bpfman_banner.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/img/bpfman_container.png b/docs/img/bpfman_container.png new file mode 100644 index 000000000..cdf796384 Binary files /dev/null and b/docs/img/bpfman_container.png differ diff --git a/docs/img/bpfman_icon.svg b/docs/img/bpfman_icon.svg deleted file mode 100644 index 8b6d705bf..000000000 --- a/docs/img/bpfman_icon.svg +++ /dev/null @@ -1,233 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/img/bpfman_library.png b/docs/img/bpfman_library.png new file mode 100644 index 000000000..a4109ab86 Binary files /dev/null and b/docs/img/bpfman_library.png differ diff --git a/docs/img/bpfman_logo.png b/docs/img/bpfman_logo.png new file mode 100644 index 000000000..da1810833 Binary files /dev/null and b/docs/img/bpfman_logo.png differ diff --git a/docs/img/bpfman_logo.svg b/docs/img/bpfman_logo.svg new file mode 100644 index 000000000..5403d28f5 --- /dev/null +++ b/docs/img/bpfman_logo.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/img/bpfman_logo_256.png b/docs/img/bpfman_logo_256.png new file mode 100644 index 000000000..d2c65ff1e Binary files /dev/null and b/docs/img/bpfman_logo_256.png differ diff --git a/docs/img/bpfman_logo_512.png b/docs/img/bpfman_logo_512.png new file mode 100644 index 000000000..4556c0369 Binary files /dev/null and b/docs/img/bpfman_logo_512.png differ diff --git a/docs/img/favicon.ico b/docs/img/favicon.ico deleted file mode 100644 index d7a269397..000000000 Binary files a/docs/img/favicon.ico and /dev/null differ diff --git a/docs/img/favicon.png b/docs/img/favicon.png new file mode 100644 index 000000000..0c562c333 Binary files /dev/null and b/docs/img/favicon.png differ diff --git a/docs/img/favicon.svg b/docs/img/favicon.svg new file mode 100644 index 000000000..4e69b9284 --- /dev/null +++ b/docs/img/favicon.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/img/gocounter-on-host.png b/docs/img/gocounter-on-host.png index 1dc957882..d8e107df9 100644 Binary files a/docs/img/gocounter-on-host.png and b/docs/img/gocounter-on-host.png differ diff --git a/docs/img/gocounter-on-k8s.png b/docs/img/gocounter-on-k8s.png index e544bb7ac..94ca65ea8 100644 Binary files a/docs/img/gocounter-on-k8s.png and b/docs/img/gocounter-on-k8s.png differ diff --git a/docs/index.md b/docs/index.md index 1dacb15b8..d7270d400 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,16 +1,22 @@ -# [![Welcome to bpfman](./img/bpfman.svg)](https://bpfman.netlify.app/) +![bpfman logo](./img/bpfman_logo_256.png) -bpfman is a system daemon aimed at simplifying the deployment and management of -eBPF programs. It's goal is to enhance the developer-experience as well as -provide features to improve security, visibility and program-cooperation. bpfman -includes a Kubernetes operator to bring those same features to Kubernetes, -allowing users to safely deploy eBPF via custom resources across nodes in a -cluster. +_Formerly know as `bpfd`_ + +# bpfman: An eBPF Manager + +bpfman operates as an eBPF manager, focusing on simplifying the deployment and administration of eBPF programs. Its notable features encompass: + +- **System Overview**: Provides insights into how eBPF is utilized in your system. +- **eBPF Program Loader**: Includes a built-in program loader that supports program cooperation for XDP and TC programs, as well as deployment of eBPF programs from OCI images. +- **eBPF Filesystem Management**: Manages the eBPF filesystem, facilitating the deployment of eBPF applications without requiring additional privileges. + +Our program loader and eBPF filesystem manager ensure the secure deployment of eBPF applications. +Furthermore, bpfman includes a Kubernetes operator, extending these capabilities to Kubernetes. This allows users to confidently deploy eBPF through custom resource definitions across nodes in a cluster. ## Why eBPF? eBPF is a powerful general-purpose framework that allows running sandboxed -programs in the kernel. It can be used for many purposes, including networking, +programs in the kernel. It can be used for many purposes, including networking, monitoring, tracing and security. ## Why eBPF in Kubernetes? @@ -32,36 +38,36 @@ in Kubernetes include: - Requires privileged pods. - eBPF-enabled apps require at least CAP_BPF permissions and potentially - more depending on the type of program that is being attached. + more depending on the type of program that is being attached. - Since the Linux capabilities are very broad it is challenging to constrain - a pod to the minimum set of privileges required. This can allow them to do - damage (either unintentionally or intentionally). + a pod to the minimum set of privileges required. This can allow them to do + damage (either unintentionally or intentionally). - Handling multiple eBPF programs on the same eBPF hooks. - Not all eBPF hooks are designed to support multiple programs. - Some software using eBPF assumes exclusive use of an eBPF hook and can - unintentionally eject existing programs when being attached. This can - result in silent failures and non-deterministic failures. + unintentionally eject existing programs when being attached. This can + result in silent failures and non-deterministic failures. - Debugging problems with deployments is hard. - The cluster administrator may not be aware that eBPF programs are being - used in a cluster. + used in a cluster. - It is possible for some eBPF programs to interfere with others in - unpredictable ways. + unpredictable ways. - SSH access or a privileged pod is necessary to determine the state of eBPF - programs on each node in the cluster. + programs on each node in the cluster. - Lifecycle management of eBPF programs. - While there are libraries for the basic loading and unloading of eBPF - programs, a lot of code is often needed around them for lifecycle - management. + programs, a lot of code is often needed around them for lifecycle + management. - Deployment on Kubernetes is not simple. - It is an involved process that requires first writing a daemon that loads - your eBPF bytecode and deploying it using a DaemonSet. + your eBPF bytecode and deploying it using a DaemonSet. - This requires careful design and intricate knowledge of the eBPF program - lifecycle to ensure your program stays loaded and that you can easily - tolerate pod restarts and upgrades. + lifecycle to ensure your program stays loaded and that you can easily + tolerate pod restarts and upgrades. - In eBPF enabled K8s deployments today, the eBPF Program is often embedded - into the userspace binary that loads and interacts with it. This means - there's no easy way to have fine-grained versioning control of the - bpfProgram in relation to it's accompanying userspace counterpart. + into the userspace binary that loads and interacts with it. This means + there's no easy way to have fine-grained versioning control of the + bpfProgram in relation to it's accompanying userspace counterpart. ## What is bpfman? @@ -87,47 +93,51 @@ The benefits of this solution include the following: - Security - Improved security because only the bpfman daemon, which can be tightly - controlled, has the privileges needed to load eBPF programs, while access - to the API can be controlled via standard RBAC methods. Within bpfman, only - a single thread keeps these capabilities while the other threads (serving - RPCs) do not. + controlled, has the privileges needed to load eBPF programs, while access + to the API can be controlled via standard RBAC methods. Within bpfman, only + a single thread keeps these capabilities while the other threads (serving + RPCs) do not. - Gives the administrators control over who can load programs. - Allows administrators to define rules for the ordering of networking eBPF - programs. (ROADMAP) + programs. (ROADMAP) - Visibility/Debuggability - Improved visibility into what eBPF programs are running on a system, which - enhances the debuggability for developers, administrators, and customer - support. + enhances the debuggability for developers, administrators, and customer + support. - The greatest benefit is achieved when all apps use bpfman, but even if they - don't, bpfman can provide visibility into all the eBPF programs loaded on - the nodes in a cluster. + don't, bpfman can provide visibility into all the eBPF programs loaded on + the nodes in a cluster. - Multi-program Support - Support for the coexistence of multiple eBPF programs from multiple users. - Uses the [libxdp multiprog - protocol](https://github.com/xdp-project/xdp-tools/blob/master/lib/libxdp/protocol.org) - to allow multiple XDP programs on single interface + protocol](https://github.com/xdp-project/xdp-tools/blob/master/lib/libxdp/protocol.org) + to allow multiple XDP programs on single interface - This same protocol is also supported for TC programs to provide a common - multi-program user experience across both TC and XDP. + multi-program user experience across both TC and XDP. - Productivity - Simplifies the deployment and lifecycle management of eBPF programs in a - Kubernetes cluster. + Kubernetes cluster. - developers can stop worrying about program lifecycle (loading, attaching, - pin management, etc.) and use existing eBPF libraries to interact with - their program maps using well defined pin points which are managed by - bpfman. + pin management, etc.) and use existing eBPF libraries to interact with + their program maps using well defined pin points which are managed by + bpfman. - Developers can still use Cilium/libbpf/Aya/etc libraries for eBPF - development, and load/unload with bpfman. + development, and load/unload with bpfman. - Provides eBPF Bytecode Image Specifications that allows fine-grained - separate versioning control for userspace and kernelspace programs. This - also allows for signing these container images to verify bytecode - ownership. + separate versioning control for userspace and kernelspace programs. This + also allows for signing these container images to verify bytecode + ownership. For more details, please see the following: +- [bpfman Overview](./getting-started/overview.md) for an overview of bpfman. +- [Deploying Example eBPF Programs On Local Host](./getting-started/example-bpf-local.md) + for some examples of running `bpfman` on local host and using the CLI to install + eBPF programs on the host. +- [Deploying Example eBPF Programs On Kubernetes](./getting-started/example-bpf-k8s.md) + for some examples of deploying eBPF programs through `bpfman` in a Kubernetes deployment. - [Setup and Building bpfman](./getting-started/building-bpfman.md) for instructions on setting up your development environment and building bpfman. -- [Tutorial](./getting-started/tutorial.md) for some examples of starting - `bpfman`, managing logs, and using the CLI. - [Example eBPF Programs](./getting-started/example-bpf.md) for some examples of eBPF programs written in Go, interacting with `bpfman`. - [Deploying the bpfman-operator](./developer-guide/operator-quick-start.md) for diff --git a/docs/overrides/partials/copyright.html b/docs/overrides/partials/copyright.html new file mode 100644 index 000000000..430669e32 --- /dev/null +++ b/docs/overrides/partials/copyright.html @@ -0,0 +1,42 @@ + + + + + diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 000000000..a5014d81d --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,9 @@ +:root { + --md-primary-fg-color: #F28F22; + --md-primary-fg-color--light: #F3C622; + --md-primary-fg-color--dark: #CF791D; +} + +.md-copyright__netlify { + float: right; +} diff --git a/examples/Makefile b/examples/Makefile index bad21fe44..7192cbcd6 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -97,6 +97,9 @@ build: fmt ## Build all the userspace example code. go build -o go-tc-counter/go-tc-counter go-tc-counter/main.go go build -o go-tracepoint-counter/go-tracepoint-counter go-tracepoint-counter/main.go go build -o go-xdp-counter/go-xdp-counter go-xdp-counter/main.go + go build -o go-kprobe-counter/go-kprobe-counter go-kprobe-counter/main.go + go build -o go-uprobe-counter/go-uprobe-counter go-uprobe-counter/main.go + go build -o go-target/go-target go-target/main.go .PHONY: generate generate: ## Run `go generate` to build the bytecode for each of the examples. @@ -107,27 +110,36 @@ build-us-images: ## Build all example userspace images docker build -t ${IMAGE_TC_US} -f ./go-tc-counter/container-deployment/Containerfile.go-tc-counter ../ docker build -t ${IMAGE_TP_US} -f ./go-tracepoint-counter/container-deployment/Containerfile.go-tracepoint-counter ../ docker build -t ${IMAGE_XDP_US} -f ./go-xdp-counter/container-deployment/Containerfile.go-xdp-counter ../ + docker build -t ${IMAGE_KP_US} -f ./go-kprobe-counter/container-deployment/Containerfile.go-kprobe-counter ../ + docker build -t ${IMAGE_UP_US} -f ./go-uprobe-counter/container-deployment/Containerfile.go-uprobe-counter ../ + docker build -t ${IMAGE_GT_US} -f ./go-target/container-deployment/Containerfile.go-target ../ + .PHONY: build-bc-images build-bc-images: generate ## Build bytecode example userspace images - IMAGE_TC_BC=${IMAGE_TC_BC} IMAGE_TP_BC=${IMAGE_TP_BC} IMAGE_XDP_BC=${IMAGE_XDP_BC} ./build-bytecode-images.sh + IMAGE_TC_BC=${IMAGE_TC_BC} IMAGE_TP_BC=${IMAGE_TP_BC} IMAGE_XDP_BC=${IMAGE_XDP_BC} IMAGE_KP_BC=${IMAGE_KP_BC} IMAGE_UP_BC=${IMAGE_UP_BC} ./build-bytecode-images.sh .PHONY: push-us-images push-us-images: ## Push all example userspace images docker push ${IMAGE_TC_US} docker push ${IMAGE_TP_US} docker push ${IMAGE_XDP_US} + docker push ${IMAGE_KP_US} + docker push ${IMAGE_UP_US} + docker push ${IMAGE_GT_US} .PHONY: push-bc-images -push-bc-images: ## Push all example userspace images +push-bc-images: ## Push all example bytecode images docker push ${IMAGE_TC_BC} docker push ${IMAGE_TP_BC} docker push ${IMAGE_XDP_BC} + docker push ${IMAGE_KP_BC} + docker push ${IMAGE_UP_BC} .PHONY: load-us-images-kind load-us-images-kind: build-us-images ## Build and load all example userspace images into kind - kind load docker-image ${IMAGE_TC_US} ${IMAGE_TP_US} ${IMAGE_XDP_US} --name ${KIND_CLUSTER_NAME} + kind load docker-image ${IMAGE_TC_US} ${IMAGE_TP_US} ${IMAGE_XDP_US} ${IMAGE_KP_US} ${IMAGE_UP_US} ${IMAGE_GT_US} --name ${KIND_CLUSTER_NAME} ##@ Deployment Variables (not commands) TAG: ## Used to set all images to a fixed tag. Example: make deploy TAG=v0.2.0 @@ -137,6 +149,11 @@ IMAGE_TP_BC: ## Tracepoint Bytecode image. Example: make deploy-tracepoint IMAGE IMAGE_TP_US: ## Tracepoint Userspace image. Example: make deploy-tracepoint IMAGE_TP_US=quay.io/user1/go-tracepoint-counter-userspace:test IMAGE_XDP_BC: ## XDP Bytecode image. Example: make deploy-xdp IMAGE_XDP_BC=quay.io/user1/go-xdp-counter-bytecode:test IMAGE_XDP_US: ## XDP Userspace image. Example: make deploy-xdp IMAGE_XDP_US=quay.io/user1/go-xdp-counter-userspace:test +IMAGE_KP_BC: ## Kprobe Bytecode image. Example: make deploy-kprobe IMAGE_KP_BC=quay.io/user1/go-kprobe-counter-bytecode:test +IMAGE_KP_US: ## Kprobe Userspace image. Example: make deploy-kprobe IMAGE_KP_US=quay.io/user1/go-kprobe-counter-userspace:test +IMAGE_UP_BC: ## Uprobe Bytecode image. Example: make deploy-uprobe IMAGE_UP_BC=quay.io/user1/go-uprobe-counter-bytecode:test +IMAGE_UP_US: ## Uprobe Userspace image. Example: make deploy-uprobe IMAGE_UP_US=quay.io/user1/go-uprobe-counter-userspace:test +IMAGE_GT_US: ## Uprobe Userspace target. Example: make deploy-target IMAGE_GT_US=quay.io/user1/go-target-userspace:test KIND_CLUSTER_NAME: ## Name of the deployed cluster to load example images to, defaults to `bpfman-deployment` ignore-not-found: ## For any undeploy command, set to true to ignore resource not found errors during deletion. Example: make undeploy ignore-not-found=true @@ -147,95 +164,162 @@ IMAGE_TP_BC ?= quay.io/bpfman-bytecode/go-tracepoint-counter:latest IMAGE_TP_US ?= quay.io/bpfman-userspace/go-tracepoint-counter:latest IMAGE_XDP_BC ?= quay.io/bpfman-bytecode/go-xdp-counter:latest IMAGE_XDP_US ?= quay.io/bpfman-userspace/go-xdp-counter:latest +IMAGE_KP_BC ?= quay.io/bpfman-bytecode/go-kprobe-counter:latest +IMAGE_KP_US ?= quay.io/bpfman-userspace/go-kprobe-counter:latest +IMAGE_UP_BC ?= quay.io/bpfman-bytecode/go-uprobe-counter:latest +IMAGE_UP_US ?= quay.io/bpfman-userspace/go-uprobe-counter:latest +IMAGE_GT_US ?= quay.io/bpfman-userspace/go-target:latest KUST_DIR=default KIND_CLUSTER_NAME ?= bpfman-deployment -.PHONY: deploy-tc -deploy-tc: kustomize ## Deploy go-tc-counter to the cluster specified in ~/.kube/config. + +.PHONY: deploy-prog +deploy-prog: kustomize ifndef TAG - sed 's@URL_BC@$(IMAGE_TC_BC)@' config/default/go-tc-counter/patch.yaml.env > config/default/go-tc-counter/patch.yaml - cd config/default/go-tc-counter && $(KUSTOMIZE) edit set image quay.io/bpfman-userspace/go-tc-counter=${IMAGE_TC_US} + sed 's@URL_BC@$(IMAGE_BC)@' config/default/$(CONFIG_DIR)/patch.yaml.env > config/default/$(CONFIG_DIR)/patch.yaml + cd config/default/$(CONFIG_DIR) && $(KUSTOMIZE) edit set image quay.io/bpfman-userspace/$(PROG_NAME)=${IMAGE_US} else $(eval KUST_DIR=$(TAG)) endif - $(KUSTOMIZE) build config/$(KUST_DIR)/go-tc-counter | kubectl apply -f - + @if [ -f config/$(KUST_DIR)/$(CONFIG_DIR)/kustomization.yaml ]; then \ + $(KUSTOMIZE) build config/$(KUST_DIR)/$(CONFIG_DIR) | kubectl apply -f - ; \ + else \ + echo "Version $(KUST_DIR) not supported for program $(PROG_NAME)" ; \ + exit 1 ; \ + fi -.PHONY: undeploy-tc -undeploy-tc: ## Undeploy go-tc-counter from the cluster specified in ~/.kube/config. + +.PHONY: undeploy-prog +undeploy-prog: ifndef TAG - sed 's@URL_BC@$(IMAGE_TC_BC)@' config/default/go-tc-counter/patch.yaml.env > config/default/go-tc-counter/patch.yaml - cd config/default/go-tc-counter && $(KUSTOMIZE) edit set image quay.io/bpfman-userspace/go-tc-counter=${IMAGE_TC_US} + sed 's@URL_BC@$(IMAGE_BC)@' config/default/$(CONFIG_DIR)/patch.yaml.env > config/default/$(CONFIG_DIR)/patch.yaml + cd config/default/$(CONFIG_DIR) && $(KUSTOMIZE) edit set image quay.io/bpfman-userspace/$(PROG_NAME)=${IMAGE_US} else $(eval KUST_DIR=$(TAG)) endif - $(KUSTOMIZE) build config/$(KUST_DIR)/go-tc-counter | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + @if [ -f config/$(KUST_DIR)/$(CONFIG_DIR)/kustomization.yaml ]; then \ + $(KUSTOMIZE) build config/$(KUST_DIR)/$(CONFIG_DIR) | kubectl delete --ignore-not-found=$(ignore-not-found) -f -; \ + else \ + echo "Version $(KUST_DIR) not supported for program $(PROG_NAME)" ; \ + exit 1 ; \ + fi + +.PHONY: deploy-tc +deploy-tc: PROG_NAME=go-tc-counter +deploy-tc: CONFIG_DIR=$(PROG_NAME) +deploy-tc: IMAGE_BC=$(IMAGE_TC_BC) +deploy-tc: IMAGE_US=$(IMAGE_TC_US) +deploy-tc: deploy-prog ## Deploy go-tc-counter to the cluster specified in ~/.kube/config. + +.PHONY: undeploy-tc +undeploy-tc: PROG_NAME=go-tc-counter +undeploy-tc: CONFIG_DIR=$(PROG_NAME) +undeploy-tc: IMAGE_BC=$(IMAGE_TC_BC) +undeploy-tc: IMAGE_US=$(IMAGE_TC_US) +undeploy-tc: undeploy-prog ## Undeploy go-tc-counter from the cluster specified in ~/.kube/config. .PHONY: deploy-tracepoint -deploy-tracepoint: kustomize ## Deploy go-tracepoint-counter to the cluster specified in ~/.kube/config. -ifndef TAG - sed 's@URL_BC@$(IMAGE_TP_BC)@' config/default/go-tracepoint-counter/patch.yaml.env > config/default/go-tracepoint-counter/patch.yaml - cd config/default/go-tracepoint-counter && $(KUSTOMIZE) edit set image quay.io/bpfman-userspace/go-tracepoint-counter=${IMAGE_TP_US} -else - $(eval KUST_DIR=$(TAG)) -endif - $(KUSTOMIZE) build config/$(KUST_DIR)/go-tracepoint-counter | kubectl apply -f - +deploy-tracepoint: PROG_NAME=go-tracepoint-counter +deploy-tracepoint: CONFIG_DIR=$(PROG_NAME) +deploy-tracepoint: IMAGE_BC=$(IMAGE_TP_BC) +deploy-tracepoint: IMAGE_US=$(IMAGE_TP_US) +deploy-tracepoint: deploy-prog ## Deploy go-tracepoint-counter to the cluster specified in ~/.kube/config. .PHONY: undeploy-tracepoint -undeploy-tracepoint: ## Undeploy go-tracepoint-counter from the cluster specified in ~/.kube/config. -ifndef TAG - sed 's@URL_BC@$(IMAGE_TP_BC)@' config/default/go-tracepoint-counter/patch.yaml.env > config/default/go-tracepoint-counter/patch.yaml - cd config/default/go-tracepoint-counter && $(KUSTOMIZE) edit set image quay.io/bpfman-userspace/go-tracepoint-counter=${IMAGE_TP_US} -else - $(eval KUST_DIR=$(TAG)) -endif - $(KUSTOMIZE) build config/$(KUST_DIR)/go-tracepoint-counter | kubectl delete --ignore-not-found=$(ignore-not-found) -f - +undeploy-tracepoint: PROG_NAME=go-tracepoint-counter +undeploy-tracepoint: CONFIG_DIR=$(PROG_NAME) +undeploy-tracepoint: IMAGE_BC=$(IMAGE_TP_BC) +undeploy-tracepoint: IMAGE_US=$(IMAGE_TP_US) +undeploy-tracepoint: undeploy-prog ## Undeploy go-tracepoint-counter from the cluster specified in ~/.kube/config. .PHONY: deploy-xdp -deploy-xdp: kustomize ## Deploy go-xdp-counter to the cluster specified in ~/.kube/config. -ifndef TAG - sed 's@URL_BC@$(IMAGE_XDP_BC)@' config/default/go-xdp-counter/patch.yaml.env > config/default/go-xdp-counter/patch.yaml - cd config/default/go-xdp-counter && $(KUSTOMIZE) edit set image quay.io/bpfman-userspace/go-xdp-counter=${IMAGE_XDP_US} -else - $(eval KUST_DIR=$(TAG)) -endif - $(KUSTOMIZE) build config/$(KUST_DIR)/go-xdp-counter | kubectl apply -f - +deploy-xdp: PROG_NAME=go-xdp-counter +deploy-xdp: CONFIG_DIR=$(PROG_NAME) +deploy-xdp: IMAGE_BC=$(IMAGE_XDP_BC) +deploy-xdp: IMAGE_US=$(IMAGE_XDP_US) +deploy-xdp: deploy-prog ## Deploy go-xdp-counter to the cluster specified in ~/.kube/config. .PHONY: undeploy-xdp -undeploy-xdp: ## Undeploy go-xdp-counter from the cluster specified in ~/.kube/config. -ifndef TAG - sed 's@URL_BC@$(IMAGE_XDP_BC)@' config/default/go-xdp-counter/patch.yaml.env > config/default/go-xdp-counter/patch.yaml - cd config/default/go-xdp-counter && $(KUSTOMIZE) edit set image quay.io/bpfman-userspace/go-xdp-counter=${IMAGE_XDP_US} -else - $(eval KUST_DIR=$(TAG)) -endif - $(KUSTOMIZE) build config/$(KUST_DIR)/go-xdp-counter | kubectl delete --ignore-not-found=$(ignore-not-found) -f - +undeploy-xdp: PROG_NAME=go-xdp-counter +undeploy-xdp: CONFIG_DIR=$(PROG_NAME) +undeploy-xdp: IMAGE_BC=$(IMAGE_XDP_BC) +undeploy-xdp: IMAGE_US=$(IMAGE_XDP_US) +undeploy-xdp: undeploy-prog ## Undeploy go-xdp-counter from the cluster specified in ~/.kube/config. .PHONY: deploy-xdp-ms -deploy-xdp-ms: kustomize ## Deploy go-xdp-counter-sharing-map (shares map with go-xdp-counter) to the cluster specified in ~/.kube/config. -ifndef TAG - sed 's@URL_BC@$(IMAGE_XDP_BC)@' config/default/go-xdp-counter-sharing-map/patch.yaml.env > config/default/go-xdp-counter-sharing-map/patch.yaml - cd config/default/go-xdp-counter-sharing-map && $(KUSTOMIZE) edit set image quay.io/bpfman-userspace/go-xdp-counter=${IMAGE_XDP_US} -else - $(eval KUST_DIR=$(TAG)) -endif - $(KUSTOMIZE) build config/$(KUST_DIR)/go-xdp-counter-sharing-map | kubectl apply -f - +deploy-xdp-ms: PROG_NAME=go-xdp-counter +deploy-xdp-ms: CONFIG_DIR=go-xdp-counter-sharing-map +deploy-xdp-ms: IMAGE_BC=$(IMAGE_XDP_BC) +deploy-xdp-ms: IMAGE_US=$(IMAGE_XDP_US) +deploy-xdp-ms: deploy-prog ## Deploy go-xdp-counter-sharing-map (shares map with go-xdp-counter) to the cluster specified in ~/.kube/config. .PHONY: undeploy-xdp-ms -undeploy-xdp-ms: ## Undeploy go-xdp-counter-sharing-map from the cluster specified in ~/.kube/config. -ifndef TAG - sed 's@URL_BC@$(IMAGE_XDP_BC)@' config/default/go-xdp-counter-sharing-map/patch.yaml.env > config/default/go-xdp-counter-sharing-map/patch.yaml - cd config/default/go-xdp-counter-sharing-map && $(KUSTOMIZE) edit set image quay.io/bpfman-userspace/go-xdp-counter=${IMAGE_XDP_US} -else - $(eval KUST_DIR=$(TAG)) -endif - $(KUSTOMIZE) build config/$(KUST_DIR)/go-xdp-counter-sharing-map | kubectl delete --ignore-not-found=$(ignore-not-found) -f - +undeploy-xdp-ms: PROG_NAME=go-xdp-counter +undeploy-xdp-ms: CONFIG_DIR=go-xdp-counter-sharing-map +undeploy-xdp-ms: IMAGE_BC=$(IMAGE_XDP_BC) +undeploy-xdp-ms: IMAGE_US=$(IMAGE_XDP_US) +undeploy-xdp-ms: undeploy-prog ## Undeploy go-xdp-counter-sharing-map from the cluster specified in ~/.kube/config. + +.PHONY: deploy-kprobe +deploy-kprobe: PROG_NAME=go-kprobe-counter +deploy-kprobe: CONFIG_DIR=$(PROG_NAME) +deploy-kprobe: IMAGE_BC=$(IMAGE_KP_BC) +deploy-kprobe: IMAGE_US=$(IMAGE_KP_US) +deploy-kprobe: deploy-prog ## Deploy go-kprobe-counter to the cluster specified in ~/.kube/config. + +.PHONY: undeploy-kprobe +undeploy-kprobe: PROG_NAME=go-kprobe-counter +undeploy-kprobe: CONFIG_DIR=$(PROG_NAME) +undeploy-kprobe: IMAGE_BC=$(IMAGE_KP_BC) +undeploy-kprobe: IMAGE_US=$(IMAGE_KP_US) +undeploy-kprobe: undeploy-prog ## Undeploy go-kprobe-counter from the cluster specified in ~/.kube/config. + + +.PHONY: deploy-uprobe +deploy-uprobe: PROG_NAME=go-uprobe-counter +deploy-uprobe: CONFIG_DIR=$(PROG_NAME) +deploy-uprobe: IMAGE_BC=$(IMAGE_UP_BC) +deploy-uprobe: IMAGE_US=$(IMAGE_UP_US) +deploy-uprobe: deploy-prog ## Deploy go-uprobe-counter to the cluster specified in ~/.kube/config. + +.PHONY: undeploy-uprobe +undeploy-uprobe: PROG_NAME=go-uprobe-counter +undeploy-uprobe: CONFIG_DIR=$(PROG_NAME) +undeploy-uprobe: IMAGE_BC=$(IMAGE_UP_BC) +undeploy-uprobe: IMAGE_US=$(IMAGE_UP_US) +undeploy-uprobe: undeploy-prog ## Undeploy go-uprobe-counter from the cluster specified in ~/.kube/config. + + +.PHONY: deploy-target +deploy-target: ## Deploy go-target to the cluster specified in ~/.kube/config. + kubectl apply -f config/base/go-target/deployment.yaml + +.PHONY: undeploy-target +undeploy-target: ## Undeploy go-target from the cluster specified in ~/.kube/config. + kubectl delete -f config/base/go-target/deployment.yaml + +DEPLOY_TARGETS = deploy-tc deploy-tracepoint deploy-xdp deploy-xdp-ms deploy-kprobe deploy-target deploy-uprobe .PHONY: deploy -deploy: deploy-tc deploy-tracepoint deploy-xdp deploy-xdp-ms ## Deploy all examples to the cluster specified in ~/.kube/config. +deploy: ## Deploy all examples to the cluster specified in ~/.kube/config. +ifdef TAG + $(eval TAG_COMMAND="TAG=$(TAG)") +endif + for target in $(DEPLOY_TARGETS) ; do \ + $(MAKE) $$target $(TAG_COMMAND) || true; \ + done + +UNDEPLOY_TARGETS = undeploy-tc undeploy-tracepoint undeploy-xdp undeploy-xdp-ms undeploy-kprobe undeploy-uprobe undeploy-target .PHONY: undeploy -undeploy: undeploy-tc undeploy-tracepoint undeploy-xdp undeploy-xdp-ms ## Undeploy all examples to the cluster specified in ~/.kube/config. +undeploy: ## Undeploy all examples to the cluster specified in ~/.kube/config. +ifdef TAG + $(eval TAG_COMMAND="TAG=$(TAG)") +endif + for target in $(UNDEPLOY_TARGETS) ; do \ + $(MAKE) $$target $(TAG_COMMAND) || true; \ + done diff --git a/examples/build-bytecode-images.sh b/examples/build-bytecode-images.sh index de84f808b..6ee55c17d 100755 --- a/examples/build-bytecode-images.sh +++ b/examples/build-bytecode-images.sh @@ -23,3 +23,19 @@ docker build \ --build-arg BYTECODE_FILENAME=bpf_bpfel.o \ -f ../Containerfile.bytecode \ ./go-tracepoint-counter -t $IMAGE_TP_BC + +docker build \ + --build-arg PROGRAM_NAME=kprobe_counter \ + --build-arg BPF_FUNCTION_NAME=kprobe_counter \ + --build-arg PROGRAM_TYPE=kprobe \ + --build-arg BYTECODE_FILENAME=bpf_bpfel.o \ + -f ../Containerfile.bytecode \ + ./go-kprobe-counter -t $IMAGE_KP_BC + +docker build \ + --build-arg PROGRAM_NAME=uprobe_counter \ + --build-arg BPF_FUNCTION_NAME=uprobe_counter \ + --build-arg PROGRAM_TYPE=uprobe \ + --build-arg BYTECODE_FILENAME=bpf_bpfel.o \ + -f ../Containerfile.bytecode \ + ./go-uprobe-counter -t $IMAGE_UP_BC diff --git a/examples/config/base/go-kprobe-counter/bytecode.yaml b/examples/config/base/go-kprobe-counter/bytecode.yaml new file mode 100644 index 000000000..7e69b0f73 --- /dev/null +++ b/examples/config/base/go-kprobe-counter/bytecode.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: bpfman.io/v1alpha1 +kind: KprobeProgram +metadata: + labels: + app.kubernetes.io/name: kprobeprogram + name: go-kprobe-counter-example +spec: + bpffunctionname: kprobe_counter + # Select all nodes + nodeselector: {} + func_name: try_to_wake_up + offset: 0 + retprobe: false + bytecode: + image: + url: quay.io/bpfman-bytecode/go-kprobe-counter:latest + imagepullpolicy: IfNotPresent diff --git a/examples/config/base/go-kprobe-counter/deployment.yaml b/examples/config/base/go-kprobe-counter/deployment.yaml new file mode 100644 index 000000000..8757454fe --- /dev/null +++ b/examples/config/base/go-kprobe-counter/deployment.yaml @@ -0,0 +1,63 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: go-kprobe-counter +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bpfman-app-go-kprobe-counter + namespace: go-kprobe-counter +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: go-kprobe-counter-ds + namespace: go-kprobe-counter + labels: + k8s-app: go-kprobe-counter +spec: + selector: + matchLabels: + name: go-kprobe-counter + template: + metadata: + labels: + name: go-kprobe-counter + spec: + nodeSelector: {} + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + serviceAccountName: bpfman-app-go-kprobe-counter + tolerations: + # these tolerations are to have the daemonset runnable on control plane nodes + # remove them if your control plane nodes should not run pods + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule + - key: node-role.kubernetes.io/master + operator: Exists + effect: NoSchedule + containers: + - name: go-kprobe-counter + image: quay.io/bpfman-userspace/go-kprobe-counter:latest + imagePullPolicy: IfNotPresent + securityContext: + privileged: false + env: + - name: NODENAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + volumeMounts: + - name: go-kprobe-counter-maps + mountPath: /run/kprobe/maps + readOnly: true + volumes: + - name: go-kprobe-counter-maps + csi: + driver: csi.bpfman.io + volumeAttributes: + csi.bpfman.io/program: go-kprobe-counter-example + csi.bpfman.io/maps: kprobe_stats_map diff --git a/examples/config/base/go-kprobe-counter/kustomization.yaml b/examples/config/base/go-kprobe-counter/kustomization.yaml new file mode 100644 index 000000000..517d6b4a9 --- /dev/null +++ b/examples/config/base/go-kprobe-counter/kustomization.yaml @@ -0,0 +1,4 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: [bytecode.yaml, deployment.yaml] diff --git a/examples/config/base/go-target/deployment.yaml b/examples/config/base/go-target/deployment.yaml new file mode 100644 index 000000000..fdaf33bfe --- /dev/null +++ b/examples/config/base/go-target/deployment.yaml @@ -0,0 +1,52 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: go-target +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bpfman-app-go-target + namespace: go-target +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: go-target-ds + namespace: go-target + labels: + k8s-app: go-target +spec: + selector: + matchLabels: + name: go-target + template: + metadata: + labels: + name: go-target + spec: + nodeSelector: {} + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + serviceAccountName: bpfman-app-go-target + tolerations: + # these tolerations are to have the daemonset runnable on control plane nodes + # remove them if your control plane nodes should not run pods + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule + - key: node-role.kubernetes.io/master + operator: Exists + effect: NoSchedule + containers: + - name: go-target + image: quay.io/bpfman-userspace/go-target:latest + imagePullPolicy: IfNotPresent + securityContext: + privileged: false + env: + - name: NODENAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName diff --git a/examples/config/base/go-target/kustomization.yaml b/examples/config/base/go-target/kustomization.yaml new file mode 100644 index 000000000..b69b034e7 --- /dev/null +++ b/examples/config/base/go-target/kustomization.yaml @@ -0,0 +1,4 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: [deployment.yaml] diff --git a/examples/config/base/go-tracepoint-counter/bytecode.yaml b/examples/config/base/go-tracepoint-counter/bytecode.yaml index 4efc1eb76..dc65910dd 100644 --- a/examples/config/base/go-tracepoint-counter/bytecode.yaml +++ b/examples/config/base/go-tracepoint-counter/bytecode.yaml @@ -13,4 +13,4 @@ spec: bytecode: image: url: quay.io/bpfman-bytecode/go-tracepoint-counter:latest - imagepullpolicy: Always + imagepullpolicy: IfNotPresent diff --git a/examples/config/base/go-uprobe-counter/bytecode.yaml b/examples/config/base/go-uprobe-counter/bytecode.yaml new file mode 100644 index 000000000..65aacaffe --- /dev/null +++ b/examples/config/base/go-uprobe-counter/bytecode.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: bpfman.io/v1alpha1 +kind: UprobeProgram +metadata: + labels: + app.kubernetes.io/name: uprobeprogram + name: go-uprobe-counter-example +spec: + bpffunctionname: uprobe_counter + # Select all nodes + nodeselector: {} + func_name: main.getCount + target: /go-target + retprobe: false + bytecode: + image: + url: quay.io/bpfman-bytecode/go-uprobe-counter:latest + imagepullpolicy: IfNotPresent + containers: + namespace: go-target + pods: {} + containernames: + - go-target diff --git a/examples/config/base/go-uprobe-counter/deployment.yaml b/examples/config/base/go-uprobe-counter/deployment.yaml new file mode 100644 index 000000000..0227823ec --- /dev/null +++ b/examples/config/base/go-uprobe-counter/deployment.yaml @@ -0,0 +1,63 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: go-uprobe-counter +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bpfman-app-go-uprobe-counter + namespace: go-uprobe-counter +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: go-uprobe-counter-ds + namespace: go-uprobe-counter + labels: + k8s-app: go-uprobe-counter +spec: + selector: + matchLabels: + name: go-uprobe-counter + template: + metadata: + labels: + name: go-uprobe-counter + spec: + nodeSelector: {} + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + serviceAccountName: bpfman-app-go-uprobe-counter + tolerations: + # these tolerations are to have the daemonset runnable on control plane nodes + # remove them if your control plane nodes should not run pods + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule + - key: node-role.kubernetes.io/master + operator: Exists + effect: NoSchedule + containers: + - name: go-uprobe-counter + image: quay.io/bpfman-userspace/go-uprobe-counter:latest + imagePullPolicy: IfNotPresent + securityContext: + privileged: false + env: + - name: NODENAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + volumeMounts: + - name: go-uprobe-counter-maps + mountPath: /run/uprobe/maps + readOnly: true + volumes: + - name: go-uprobe-counter-maps + csi: + driver: csi.bpfman.io + volumeAttributes: + csi.bpfman.io/program: go-uprobe-counter-example + csi.bpfman.io/maps: uprobe_stats_map diff --git a/examples/config/base/go-uprobe-counter/kustomization.yaml b/examples/config/base/go-uprobe-counter/kustomization.yaml new file mode 100644 index 000000000..517d6b4a9 --- /dev/null +++ b/examples/config/base/go-uprobe-counter/kustomization.yaml @@ -0,0 +1,4 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: [bytecode.yaml, deployment.yaml] diff --git a/examples/config/default/go-kprobe-counter/kustomization.yaml b/examples/config/default/go-kprobe-counter/kustomization.yaml new file mode 100644 index 000000000..4b1642f5b --- /dev/null +++ b/examples/config/default/go-kprobe-counter/kustomization.yaml @@ -0,0 +1,19 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-kprobe-counter + newName: quay.io/bpfman-userspace/go-kprobe-counter + newTag: latest +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. Since this is being done with an environment +# variable and using sed to replace, moved to patch.yaml.env which is +# converted to patch.yaml using the sed command in Makefile. +patches: + - path: patch.yaml + target: + kind: kprobeProgram + name: go-kprobe-counter-example +resources: + - ../../base/go-kprobe-counter diff --git a/examples/config/default/go-kprobe-counter/patch.yaml b/examples/config/default/go-kprobe-counter/patch.yaml new file mode 100644 index 000000000..e95df1d12 --- /dev/null +++ b/examples/config/default/go-kprobe-counter/patch.yaml @@ -0,0 +1,5 @@ +# Patch the bytecode.yaml to change image and tag on the "url" field +# (which is an image) to new value. +- op: replace + path: /spec/bytecode/image/url + value: quay.io/bpfman-bytecode/go-kprobe-counter:latest diff --git a/examples/config/default/go-kprobe-counter/patch.yaml.env b/examples/config/default/go-kprobe-counter/patch.yaml.env new file mode 100644 index 000000000..9d4e92085 --- /dev/null +++ b/examples/config/default/go-kprobe-counter/patch.yaml.env @@ -0,0 +1,5 @@ +# Patch the bytecode.yaml to change image and tag on the "url" field +# (which is an image) to new value. +- op: replace + path: "/spec/bytecode/image/url" + value: URL_BC diff --git a/examples/config/default/go-target/kustomization.yaml b/examples/config/default/go-target/kustomization.yaml new file mode 100644 index 000000000..54553785b --- /dev/null +++ b/examples/config/default/go-target/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-target + newName: quay.io/bpfman-userspace/go-target + newTag: latest +resources: + - ../../base/go-target diff --git a/examples/config/default/go-uprobe-counter/kustomization.yaml b/examples/config/default/go-uprobe-counter/kustomization.yaml new file mode 100644 index 000000000..0ec3d3c90 --- /dev/null +++ b/examples/config/default/go-uprobe-counter/kustomization.yaml @@ -0,0 +1,19 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-uprobe-counter + newName: quay.io/bpfman-userspace/go-uprobe-counter + newTag: latest +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. Since this is being done with an environment +# variable and using sed to replace, moved to patch.yaml.env which is +# converted to patch.yaml using the sed command in Makefile. +patches: + - path: patch.yaml + target: + kind: uprobeProgram + name: go-uprobe-counter-example +resources: + - ../../base/go-uprobe-counter diff --git a/examples/config/default/go-uprobe-counter/patch.yaml b/examples/config/default/go-uprobe-counter/patch.yaml new file mode 100644 index 000000000..95ce20538 --- /dev/null +++ b/examples/config/default/go-uprobe-counter/patch.yaml @@ -0,0 +1,5 @@ +# Patch the bytecode.yaml to change image and tag on the "url" field +# (which is an image) to new value. +- op: replace + path: /spec/bytecode/image/url + value: quay.io/bpfman-bytecode/go-uprobe-counter:latest diff --git a/examples/config/default/go-uprobe-counter/patch.yaml.env b/examples/config/default/go-uprobe-counter/patch.yaml.env new file mode 100644 index 000000000..9d4e92085 --- /dev/null +++ b/examples/config/default/go-uprobe-counter/patch.yaml.env @@ -0,0 +1,5 @@ +# Patch the bytecode.yaml to change image and tag on the "url" field +# (which is an image) to new value. +- op: replace + path: "/spec/bytecode/image/url" + value: URL_BC diff --git a/examples/config/default/patch.yaml b/examples/config/default/patch.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/examples/config/v0.2.0/go-tc-counter/kustomization.yaml b/examples/config/v0.2.0/go-tc-counter/kustomization.yaml index d1ccc8b27..13df8e1f2 100644 --- a/examples/config/v0.2.0/go-tc-counter/kustomization.yaml +++ b/examples/config/v0.2.0/go-tc-counter/kustomization.yaml @@ -6,7 +6,7 @@ kind: Kustomization patches: - target: kind: TcProgram - name: "go-tc-counter-example" + name: go-tc-counter-example patch: |- - op: replace path: "/spec/bytecode/image/url" diff --git a/examples/config/v0.2.0/go-tracepoint-counter/kustomization.yaml b/examples/config/v0.2.0/go-tracepoint-counter/kustomization.yaml index c72a3857d..f29c14547 100644 --- a/examples/config/v0.2.0/go-tracepoint-counter/kustomization.yaml +++ b/examples/config/v0.2.0/go-tracepoint-counter/kustomization.yaml @@ -6,7 +6,7 @@ kind: Kustomization patches: - target: kind: TracepointProgram - name: "go-tracepoint-counter-example" + name: go-tracepoint-counter-example patch: |- - op: replace path: "/spec/bytecode/image/url" diff --git a/examples/config/v0.2.0/go-xdp-counter/kustomization.yaml b/examples/config/v0.2.0/go-xdp-counter/kustomization.yaml index cfb1ee815..3a22eeab9 100644 --- a/examples/config/v0.2.0/go-xdp-counter/kustomization.yaml +++ b/examples/config/v0.2.0/go-xdp-counter/kustomization.yaml @@ -6,7 +6,7 @@ kind: Kustomization patches: - target: kind: XdpProgram - name: "go-xdp-counter-example" + name: go-xdp-counter-example patch: |- - op: replace path: "/spec/bytecode/image/url" diff --git a/examples/config/v0.3.0/go-tc-counter/kustomization.yaml b/examples/config/v0.3.0/go-tc-counter/kustomization.yaml index fe509b54f..c4375fe98 100644 --- a/examples/config/v0.3.0/go-tc-counter/kustomization.yaml +++ b/examples/config/v0.3.0/go-tc-counter/kustomization.yaml @@ -6,7 +6,7 @@ kind: Kustomization patches: - target: kind: TcProgram - name: "go-tc-counter-example" + name: go-tc-counter-example patch: |- - op: replace path: "/spec/bytecode/image/url" diff --git a/examples/config/v0.3.0/go-tracepoint-counter/kustomization.yaml b/examples/config/v0.3.0/go-tracepoint-counter/kustomization.yaml index 36bfd8da3..61b9eda25 100644 --- a/examples/config/v0.3.0/go-tracepoint-counter/kustomization.yaml +++ b/examples/config/v0.3.0/go-tracepoint-counter/kustomization.yaml @@ -6,7 +6,7 @@ kind: Kustomization patches: - target: kind: TracepointProgram - name: "go-tracepoint-counter-example" + name: go-tracepoint-counter-example patch: |- - op: replace path: "/spec/bytecode/image/url" diff --git a/examples/config/v0.3.0/go-xdp-counter-sharing-map/kustomization.yaml b/examples/config/v0.3.0/go-xdp-counter-sharing-map/kustomization.yaml index 635865103..2b5370fa0 100644 --- a/examples/config/v0.3.0/go-xdp-counter-sharing-map/kustomization.yaml +++ b/examples/config/v0.3.0/go-xdp-counter-sharing-map/kustomization.yaml @@ -6,7 +6,7 @@ kind: Kustomization patches: - target: kind: XdpProgram - name: "go-xdp-counter-example" + name: go-xdp-counter-example patch: |- - op: replace path: "/spec/bytecode/image/url" diff --git a/examples/config/v0.3.0/go-xdp-counter/kustomization.yaml b/examples/config/v0.3.0/go-xdp-counter/kustomization.yaml index be6beb0f3..beb39fa68 100644 --- a/examples/config/v0.3.0/go-xdp-counter/kustomization.yaml +++ b/examples/config/v0.3.0/go-xdp-counter/kustomization.yaml @@ -6,7 +6,7 @@ kind: Kustomization patches: - target: kind: XdpProgram - name: "go-xdp-counter-example" + name: go-xdp-counter-example patch: |- - op: replace path: "/spec/bytecode/image/url" diff --git a/examples/config/v0.3.1-ocp/go-tc-counter/patch.yaml b/examples/config/v0.3.1-ocp/go-tc-counter/patch.yaml index 4345fa113..e129d00a6 100644 --- a/examples/config/v0.3.1-ocp/go-tc-counter/patch.yaml +++ b/examples/config/v0.3.1-ocp/go-tc-counter/patch.yaml @@ -1,5 +1,5 @@ # Patch the bytecode.yaml to change image and tag on the "url" field # (which is an image) to new value. - op: replace - path: "/spec/bytecode/image/url" + path: /spec/bytecode/image/url value: quay.io/bpfman-bytecode/go-tc-counter:latest diff --git a/examples/config/v0.4.0-ocp/go-kprobe-counter/kustomization.yaml b/examples/config/v0.4.0-ocp/go-kprobe-counter/kustomization.yaml new file mode 100644 index 000000000..cb8676410 --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-kprobe-counter/kustomization.yaml @@ -0,0 +1,35 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-kprobe-counter:v0.4.0 + target: + kind: TcProgram + name: go-kprobe-counter-example + - patch: |- + - op: replace + path: "/spec/template/spec/containers/0/securityContext/privileged" + value: true + target: + kind: DaemonSet + name: go-kprobe-counter-ds + namespace: go-kprobe-counter + - patch: |- + - op: add + path: "/metadata/labels" + value: {"pod-security.kubernetes.io/enforce":"privileged","pod-security.kubernetes.io/audit":"privileged","pod-security.kubernetes.io/warn":"privileged"} + target: + kind: Namespace + name: go-kprobe-counter +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-kprobe-counter + newName: quay.io/bpfman-userspace/go-kprobe-counter + newTag: v0.4.0 +resources: [../../base/go-kprobe-counter, ocp-scc.yaml] diff --git a/examples/config/v0.4.0-ocp/go-kprobe-counter/ocp-scc.yaml b/examples/config/v0.4.0-ocp/go-kprobe-counter/ocp-scc.yaml new file mode 100644 index 000000000..2fc165478 --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-kprobe-counter/ocp-scc.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: privileged-scc-kprobe +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:privileged +subjects: + - kind: ServiceAccount + name: bpfman-app-go-kprobe-counter + namespace: go-kprobe-counter diff --git a/examples/config/v0.4.0-ocp/go-tc-counter/kustomization.yaml b/examples/config/v0.4.0-ocp/go-tc-counter/kustomization.yaml new file mode 100644 index 000000000..fd0c05956 --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-tc-counter/kustomization.yaml @@ -0,0 +1,35 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-tc-counter:v0.4.0 + target: + kind: TcProgram + name: go-tc-counter-example + - patch: |- + - op: replace + path: "/spec/template/spec/containers/0/securityContext/privileged" + value: true + target: + kind: DaemonSet + name: go-tc-counter-ds + namespace: go-tc-counter + - patch: |- + - op: add + path: "/metadata/labels" + value: {"pod-security.kubernetes.io/enforce":"privileged","pod-security.kubernetes.io/audit":"privileged","pod-security.kubernetes.io/warn":"privileged"} + target: + kind: Namespace + name: go-tc-counter +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-tc-counter + newName: quay.io/bpfman-userspace/go-tc-counter + newTag: v0.4.0 +resources: [../../base/go-tc-counter, ocp-scc.yaml] diff --git a/examples/config/v0.4.0-ocp/go-tc-counter/ocp-scc.yaml b/examples/config/v0.4.0-ocp/go-tc-counter/ocp-scc.yaml new file mode 100644 index 000000000..88165e9a5 --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-tc-counter/ocp-scc.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: privileged-scc-tc +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:privileged +subjects: + - kind: ServiceAccount + name: bpfman-app-go-tc-counter + namespace: go-tc-counter diff --git a/examples/config/v0.4.0-ocp/go-tc-counter/patch.yaml b/examples/config/v0.4.0-ocp/go-tc-counter/patch.yaml new file mode 100644 index 000000000..e129d00a6 --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-tc-counter/patch.yaml @@ -0,0 +1,5 @@ +# Patch the bytecode.yaml to change image and tag on the "url" field +# (which is an image) to new value. +- op: replace + path: /spec/bytecode/image/url + value: quay.io/bpfman-bytecode/go-tc-counter:latest diff --git a/examples/config/v0.4.0-ocp/go-tc-counter/patch.yaml.env b/examples/config/v0.4.0-ocp/go-tc-counter/patch.yaml.env new file mode 100644 index 000000000..9d4e92085 --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-tc-counter/patch.yaml.env @@ -0,0 +1,5 @@ +# Patch the bytecode.yaml to change image and tag on the "url" field +# (which is an image) to new value. +- op: replace + path: "/spec/bytecode/image/url" + value: URL_BC diff --git a/examples/config/v0.4.0-ocp/go-tracepoint-counter/kustomization.yaml b/examples/config/v0.4.0-ocp/go-tracepoint-counter/kustomization.yaml new file mode 100644 index 000000000..79b806092 --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-tracepoint-counter/kustomization.yaml @@ -0,0 +1,35 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-tracepoint-counter:v0.4.0 + target: + kind: TcProgram + name: go-tracepoint-counter-example + - patch: |- + - op: replace + path: "/spec/template/spec/containers/0/securityContext/privileged" + value: true + target: + kind: DaemonSet + name: go-tracepoint-counter-ds + namespace: go-tracepoint-counter + - patch: |- + - op: add + path: "/metadata/labels" + value: {"pod-security.kubernetes.io/enforce":"privileged","pod-security.kubernetes.io/audit":"privileged","pod-security.kubernetes.io/warn":"privileged"} + target: + kind: Namespace + name: go-tracepoint-counter +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-tracepoint-counter + newName: quay.io/bpfman-userspace/go-tracepoint-counter + newTag: v0.4.0 +resources: [../../base/go-tracepoint-counter, ocp-scc.yaml] diff --git a/examples/config/v0.4.0-ocp/go-tracepoint-counter/ocp-scc.yaml b/examples/config/v0.4.0-ocp/go-tracepoint-counter/ocp-scc.yaml new file mode 100644 index 000000000..0d367460c --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-tracepoint-counter/ocp-scc.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: privileged-scc-tracepoint +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:privileged +subjects: + - kind: ServiceAccount + name: bpfman-app-go-tracepoint-counter + namespace: go-tracepoint-counter diff --git a/examples/config/v0.4.0-ocp/go-uprobe-counter/kustomization.yaml b/examples/config/v0.4.0-ocp/go-uprobe-counter/kustomization.yaml new file mode 100644 index 000000000..b5e68789f --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-uprobe-counter/kustomization.yaml @@ -0,0 +1,35 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-uprobe-counter:v0.4.0 + target: + kind: UprobeProgram + name: go-uprobe-counter-example + - patch: |- + - op: replace + path: "/spec/template/spec/containers/0/securityContext/privileged" + value: true + target: + kind: DaemonSet + name: go-uprobe-counter-ds + namespace: go-uprobe-counter + - patch: |- + - op: add + path: "/metadata/labels" + value: {"pod-security.kubernetes.io/enforce":"privileged","pod-security.kubernetes.io/audit":"privileged","pod-security.kubernetes.io/warn":"privileged"} + target: + kind: Namespace + name: go-uprobe-counter +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-uprobe-counter + newName: quay.io/bpfman-userspace/go-uprobe-counter + newTag: v0.4.0 +resources: [../../base/go-uprobe-counter, ocp-scc.yaml] diff --git a/examples/config/v0.4.0-ocp/go-uprobe-counter/ocp-scc.yaml b/examples/config/v0.4.0-ocp/go-uprobe-counter/ocp-scc.yaml new file mode 100644 index 000000000..4ecaa30df --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-uprobe-counter/ocp-scc.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: privileged-scc-uprobe +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:privileged +subjects: + - kind: ServiceAccount + name: bpfman-app-go-uprobe-counter + namespace: go-uprobe-counter diff --git a/examples/config/v0.4.0-ocp/go-xdp-counter-sharing-map/kustomization.yaml b/examples/config/v0.4.0-ocp/go-xdp-counter-sharing-map/kustomization.yaml new file mode 100644 index 000000000..e8e087faa --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-xdp-counter-sharing-map/kustomization.yaml @@ -0,0 +1,35 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-xdp-counter:v0.4.0 + target: + kind: TcProgram + name: go-xdp-counter-example + - patch: |- + - op: replace + path: "/spec/template/spec/containers/0/securityContext/privileged" + value: true + target: + kind: DaemonSet + name: go-xdp-counter-ds + namespace: go-xdp-counter + - patch: |- + - op: add + path: "/metadata/labels" + value: {"pod-security.kubernetes.io/enforce":"privileged","pod-security.kubernetes.io/audit":"privileged","pod-security.kubernetes.io/warn":"privileged"} + target: + kind: Namespace + name: go-xdp-counter +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-xdp-counter + newName: quay.io/bpfman-userspace/go-xdp-counter + newTag: v0.4.0 +resources: [../../base/go-xdp-counter-sharing-map, ocp-scc.yaml] diff --git a/examples/config/v0.4.0-ocp/go-xdp-counter-sharing-map/ocp-scc.yaml b/examples/config/v0.4.0-ocp/go-xdp-counter-sharing-map/ocp-scc.yaml new file mode 100644 index 000000000..aaa0b55b0 --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-xdp-counter-sharing-map/ocp-scc.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: privileged-scc-xdp +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:privileged +subjects: + - kind: ServiceAccount + name: bpfman-app-go-xdp-counter + namespace: go-xdp-counter diff --git a/examples/config/v0.4.0-ocp/go-xdp-counter/kustomization.yaml b/examples/config/v0.4.0-ocp/go-xdp-counter/kustomization.yaml new file mode 100644 index 000000000..d80bb9f7a --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-xdp-counter/kustomization.yaml @@ -0,0 +1,35 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-xdp-counter:0.4.0 + target: + kind: XdpProgram + name: go-xdp-counter-example + - patch: |- + - op: replace + path: "/spec/template/spec/containers/0/securityContext/privileged" + value: true + target: + kind: DaemonSet + name: go-xdp-counter-ds + namespace: go-xdp-counter + - patch: |- + - op: add + path: "/metadata/labels" + value: {"pod-security.kubernetes.io/enforce":"privileged","pod-security.kubernetes.io/audit":"privileged","pod-security.kubernetes.io/warn":"privileged"} + target: + kind: Namespace + name: go-xdp-counter +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-xdp-counter + newName: quay.io/bpfman-userspace/go-xdp-counter + newTag: 0.4.0 +resources: [../../base/go-xdp-counter, ocp-scc.yaml] diff --git a/examples/config/v0.4.0-ocp/go-xdp-counter/ocp-scc.yaml b/examples/config/v0.4.0-ocp/go-xdp-counter/ocp-scc.yaml new file mode 100644 index 000000000..aaa0b55b0 --- /dev/null +++ b/examples/config/v0.4.0-ocp/go-xdp-counter/ocp-scc.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: privileged-scc-xdp +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:privileged +subjects: + - kind: ServiceAccount + name: bpfman-app-go-xdp-counter + namespace: go-xdp-counter diff --git a/examples/config/v0.4.0/go-kprobe-counter/kustomization.yaml b/examples/config/v0.4.0/go-kprobe-counter/kustomization.yaml new file mode 100644 index 000000000..da637072b --- /dev/null +++ b/examples/config/v0.4.0/go-kprobe-counter/kustomization.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - target: + kind: KprobeProgram + name: go-kprobe-counter-example + patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-kprobe-counter:v0.4.0 +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-kprobe-counter + newName: quay.io/bpfman-userspace/go-kprobe-counter + newTag: v0.4.0 +resources: [../../base/go-kprobe-counter] diff --git a/examples/config/v0.4.0/go-tc-counter/kustomization.yaml b/examples/config/v0.4.0/go-tc-counter/kustomization.yaml new file mode 100644 index 000000000..7d3d3a4e4 --- /dev/null +++ b/examples/config/v0.4.0/go-tc-counter/kustomization.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - target: + kind: TcProgram + name: go-tc-counter-example + patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-tc-counter:v0.4.0 +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-tc-counter + newName: quay.io/bpfman-userspace/go-tc-counter + newTag: v0.4.0 +resources: [../../base/go-tc-counter] diff --git a/examples/config/v0.4.0/go-tracepoint-counter/kustomization.yaml b/examples/config/v0.4.0/go-tracepoint-counter/kustomization.yaml new file mode 100644 index 000000000..18b8c6844 --- /dev/null +++ b/examples/config/v0.4.0/go-tracepoint-counter/kustomization.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - target: + kind: TracepointProgram + name: go-tracepoint-counter-example + patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-tracepoint-counter:v0.4.0 +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-tracepoint-counter + newName: quay.io/bpfman-userspace/go-tracepoint-counter + newTag: v0.4.0 +resources: [../../base/go-tracepoint-counter] diff --git a/examples/config/v0.4.0/go-uprobe-counter/kustomization.yaml b/examples/config/v0.4.0/go-uprobe-counter/kustomization.yaml new file mode 100644 index 000000000..d89f85bd6 --- /dev/null +++ b/examples/config/v0.4.0/go-uprobe-counter/kustomization.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - target: + kind: UprobeProgram + name: go-uprobe-counter-example + patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-uprobe-counter:v0.4.0 +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-uprobe-counter + newName: quay.io/bpfman-userspace/go-uprobe-counter + newTag: v0.4.0 +resources: [../../base/go-uprobe-counter] diff --git a/examples/config/v0.4.0/go-xdp-counter-sharing-map/kustomization.yaml b/examples/config/v0.4.0/go-xdp-counter-sharing-map/kustomization.yaml new file mode 100644 index 000000000..1e86fb4b0 --- /dev/null +++ b/examples/config/v0.4.0/go-xdp-counter-sharing-map/kustomization.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - target: + kind: XdpProgram + name: go-xdp-counter-example + patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-xdp-counter:v0.4.0 +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-xdp-counter + newName: quay.io/bpfman-userspace/go-xdp-counter + newTag: v0.4.0 +resources: [../../base/go-xdp-counter-sharing-map] diff --git a/examples/config/v0.4.0/go-xdp-counter/kustomization.yaml b/examples/config/v0.4.0/go-xdp-counter/kustomization.yaml new file mode 100644 index 000000000..bacce0234 --- /dev/null +++ b/examples/config/v0.4.0/go-xdp-counter/kustomization.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Patch the bytecode.yaml to change tag on the "url" field (which is an +# image) to new value. This actually overwrites the image with the same value. +patches: + - target: + kind: XdpProgram + name: go-xdp-counter-example + patch: |- + - op: replace + path: "/spec/bytecode/image/url" + value: quay.io/bpfman-bytecode/go-xdp-counter:v0.4.0 +# Patch the deployment.yaml to change container image in Daemonset +# to new tag on the image. +images: + - name: quay.io/bpfman-userspace/go-xdp-counter + newName: quay.io/bpfman-userspace/go-xdp-counter + newTag: v0.4.0 +resources: [../../base/go-xdp-counter] diff --git a/examples/go-kprobe-counter/.gitignore b/examples/go-kprobe-counter/.gitignore new file mode 100644 index 000000000..e8c08bb1b --- /dev/null +++ b/examples/go-kprobe-counter/.gitignore @@ -0,0 +1 @@ +go-kprobe-counter diff --git a/examples/go-kprobe-counter/bpf/kprobe_counter.c b/examples/go-kprobe-counter/bpf/kprobe_counter.c new file mode 100644 index 000000000..1a57438f6 --- /dev/null +++ b/examples/go-kprobe-counter/bpf/kprobe_counter.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +// Copyright Authors of bpfman + +#include +#include +#include +#include + +#include + +struct datarec { + __u64 counter; +} datarec; + +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __type(key, __u32); + __type(value, datarec); + __uint(max_entries, 1); + __uint(pinning, LIBBPF_PIN_BY_NAME); +} kprobe_stats_map SEC(".maps"); + +SEC("kprobe/kprobe_counter") +static __u32 kprobe_counter(struct pt_regs *ctx) { + + __u32 index = 0; + struct datarec *rec = bpf_map_lookup_elem(&kprobe_stats_map, &index); + if (!rec) + return 1; + + rec->counter++; + bpf_printk("kprobe called"); + + return 0; +} + +char _license[] SEC("license") = "Dual BSD/GPL"; diff --git a/examples/go-kprobe-counter/container-deployment/Containerfile.go-kprobe-counter b/examples/go-kprobe-counter/container-deployment/Containerfile.go-kprobe-counter new file mode 100644 index 000000000..a1b25f6f9 --- /dev/null +++ b/examples/go-kprobe-counter/container-deployment/Containerfile.go-kprobe-counter @@ -0,0 +1,22 @@ +FROM golang:1.19 as go-kprobe-counter-build + +RUN apt-get update && apt-get install -y \ + clang \ + gcc-multilib \ + libbpf-dev + +RUN go install github.com/cilium/ebpf/cmd/bpf2go@master + +WORKDIR /usr/src/bpfman/ +COPY ./ /usr/src/bpfman/ + +WORKDIR /usr/src/bpfman/examples/go-kprobe-counter + +# Compile go-kprobe-counter +RUN go build + +FROM registry.fedoraproject.org/fedora-minimal:latest + +COPY --from=go-kprobe-counter-build /usr/src/bpfman/examples/go-kprobe-counter/go-kprobe-counter . + +ENTRYPOINT ["./go-kprobe-counter", "--crd"] diff --git a/examples/go-kprobe-counter/main.go b/examples/go-kprobe-counter/main.go new file mode 100644 index 000000000..40b5d93ce --- /dev/null +++ b/examples/go-kprobe-counter/main.go @@ -0,0 +1,191 @@ +//go:build linux +// +build linux + +package main + +import ( + "context" + "fmt" + "log" + "os" + "os/signal" + "syscall" + "time" + + bpfmanHelpers "github.com/bpfman/bpfman/bpfman-operator/pkg/helpers" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + configMgmt "github.com/bpfman/bpfman/examples/pkg/config-mgmt" + "github.com/cilium/ebpf" +) + +const ( + KprobeProgramName = "go-kprobe-counter-example" + BpfProgramMapIndex = "kprobe_stats_map" + DefaultByteCodeFile = "bpf_bpfel.o" + + // MapsMountPoint is the "go-kprobe-counter-maps" volumeMount "mountPath" from "deployment.yaml" + MapsMountPoint = "/run/kprobe/maps" +) + +type Stats struct { + Counter uint64 +} + +//go:generate bpf2go -cc clang -no-strip -cflags "-O2 -g -Wall" bpf ./bpf/kprobe_counter.c -- -I.:/usr/include/bpf:/usr/include/linux +func main() { + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + // pull the BPFMAN config management data to determine if we're running on a + // system with BPFMAN available. + paramData, err := configMgmt.ParseParamData(configMgmt.ProgTypeKprobe, DefaultByteCodeFile) + if err != nil { + log.Printf("error processing parameters: %v\n", err) + return + } + + // determine the path to the kprobe_stats_map, whether provided via CRD + // or BPFMAN or otherwise. + var mapPath string + // If running in a Kubernetes deployment, the eBPF program is already loaded. + // Only need the map path, which is at a known location in the pod using VolumeMounts + // and the CSI Driver. + if paramData.CrdFlag { + // 3. Get access to our map + mapPath = fmt.Sprintf("%s/%s", MapsMountPoint, BpfProgramMapIndex) + } else { // if not on k8s, find the map path from the system + ctx := context.Background() + + // connect to the BPFMAN server + conn, err := configMgmt.CreateConnection(ctx) + if err != nil { + log.Printf("failed to create client connection: %v", err) + return + } + + c := gobpfman.NewBpfmanClient(conn) + + // If the bytecode src is a Program ID, skip the loading and unloading of the bytecode. + if paramData.BytecodeSrc != configMgmt.SrcProgId { + var loadRequest *gobpfman.LoadRequest + if paramData.MapOwnerId != 0 { + mapOwnerId := uint32(paramData.MapOwnerId) + loadRequest = &gobpfman.LoadRequest{ + Bytecode: paramData.BytecodeSource, + Name: "kprobe_counter", + ProgramType: *bpfmanHelpers.Kprobe.Uint32(), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_KprobeAttachInfo{ + KprobeAttachInfo: &gobpfman.KprobeAttachInfo{ + FnName: "try_to_wake_up", + }, + }, + }, + MapOwnerId: &mapOwnerId, + } + } else { + loadRequest = &gobpfman.LoadRequest{ + Bytecode: paramData.BytecodeSource, + Name: "kprobe_counter", + ProgramType: *bpfmanHelpers.Kprobe.Uint32(), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_KprobeAttachInfo{ + KprobeAttachInfo: &gobpfman.KprobeAttachInfo{ + FnName: "try_to_wake_up", + }, + }, + }, + } + } + + // 1. Load Program using bpfman + var res *gobpfman.LoadResponse + res, err = c.Load(ctx, loadRequest) + if err != nil { + conn.Close() + log.Print(err) + return + } + + kernelInfo := res.GetKernelInfo() + if kernelInfo != nil { + paramData.ProgId = uint(kernelInfo.GetId()) + } else { + conn.Close() + log.Printf("kernelInfo not returned in LoadResponse") + return + } + log.Printf("Program registered with id %d\n", paramData.ProgId) + + // 2. Set up defer to unload program when this is closed + defer func(id uint) { + log.Printf("unloading program: %d\n", id) + _, err = c.Unload(ctx, &gobpfman.UnloadRequest{Id: uint32(id)}) + if err != nil { + conn.Close() + log.Print(err) + return + } + conn.Close() + }(paramData.ProgId) + + // 3. Get access to our map + mapPath, err = configMgmt.CalcMapPinPath(res.GetInfo(), "kprobe_stats_map") + if err != nil { + log.Print(err) + return + } + } else { + // 2. Set up defer to close connection + defer func(id uint) { + log.Printf("Closing Connection for Program: %d\n", id) + conn.Close() + }(paramData.ProgId) + + // 3. Get access to our map + mapPath, err = configMgmt.RetrieveMapPinPath(ctx, c, paramData.ProgId, "kprobe_stats_map") + if err != nil { + log.Print(err) + return + } + } + } + + // load the pinned stats map which is keeping count of kill -SIGUSR1 calls + opts := &ebpf.LoadPinOptions{ + ReadOnly: false, + WriteOnly: false, + Flags: 0, + } + statsMap, err := ebpf.LoadPinnedMap(mapPath, opts) + if err != nil { + log.Printf("Failed to load pinned Map: %s\n", mapPath) + log.Print(err) + return + } + + // retrieve and report on the number of times the kprobe is executed. + index := uint32(0) + ticker := time.NewTicker(1 * time.Second) + go func() { + for range ticker.C { + var stats []Stats + var totalCount uint64 + + if err := statsMap.Lookup(&index, &stats); err != nil { + log.Printf("map lookup failed: %v", err) + return + } + + for _, stat := range stats { + totalCount += stat.Counter + } + + log.Printf("Kprobe count: %d\n", totalCount) + } + }() + + <-stop + + log.Printf("Exiting...\n") +} diff --git a/examples/go-target/.gitignore b/examples/go-target/.gitignore new file mode 100644 index 000000000..35f1392bb --- /dev/null +++ b/examples/go-target/.gitignore @@ -0,0 +1 @@ +go-target diff --git a/examples/go-target/container-deployment/Containerfile.go-target b/examples/go-target/container-deployment/Containerfile.go-target new file mode 100644 index 000000000..dba8517cc --- /dev/null +++ b/examples/go-target/container-deployment/Containerfile.go-target @@ -0,0 +1,20 @@ +FROM golang:1.19 as go-target-build + +RUN apt-get update && apt-get install -y \ + clang \ + gcc-multilib \ + libbpf-dev + +WORKDIR /usr/src/bpfman/ +COPY ./ /usr/src/bpfman/ + +WORKDIR /usr/src/bpfman/examples/go-target + +# Compile go-target +RUN go build + +FROM registry.fedoraproject.org/fedora-minimal:latest + +COPY --from=go-target-build /usr/src/bpfman/examples/go-target/go-target . + +ENTRYPOINT ["./go-target", "--crd"] diff --git a/examples/go-target/main.go b/examples/go-target/main.go new file mode 100644 index 000000000..f629b8100 --- /dev/null +++ b/examples/go-target/main.go @@ -0,0 +1,26 @@ +// This is the target program that we will attach the uprobe to. It is a simple +// program that runs the `ls` command every 5 seconds and logs the output. + +package main + +import ( + "log" + "time" +) + +var iteration int = 0 + +func main() { + for { + log.Printf("Count: %d\n", getCount()) + time.Sleep(1 * time.Second) + } +} + +// Don't inline this function so that we can attach a uprobe to it. +// +//go:noinline +func getCount() int { + iteration++ + return iteration +} diff --git a/examples/go-tc-counter/main.go b/examples/go-tc-counter/main.go index 77e6f1a81..8686c7f13 100644 --- a/examples/go-tc-counter/main.go +++ b/examples/go-tc-counter/main.go @@ -24,7 +24,6 @@ type Stats struct { } const ( - DefaultConfigPath = "/etc/bpfman/bpfman.toml" DefaultByteCodeFile = "bpf_bpfel.o" TcProgramName = "go-tc-counter-example" BpfProgramMapIndex = "tc_stats_map" @@ -43,7 +42,7 @@ func main() { signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) // Parse Input Parameters (CmdLine and Config File) - paramData, err := configMgmt.ParseParamData(configMgmt.ProgTypeTc, DefaultConfigPath, DefaultByteCodeFile) + paramData, err := configMgmt.ParseParamData(configMgmt.ProgTypeTc, DefaultByteCodeFile) if err != nil { log.Printf("error processing parameters: %v\n", err) return @@ -70,9 +69,8 @@ func main() { } else { ctx := context.Background() - configFileData := configMgmt.LoadConfig(DefaultConfigPath) // Set up a connection to the server. - conn, err := configMgmt.CreateConnection(configFileData.Grpc.Endpoints, ctx) + conn, err := configMgmt.CreateConnection(ctx) if err != nil { log.Printf("failed to create client connection: %v", err) return diff --git a/examples/go-tracepoint-counter/main.go b/examples/go-tracepoint-counter/main.go index d3b8ff9c1..9d6618c91 100644 --- a/examples/go-tracepoint-counter/main.go +++ b/examples/go-tracepoint-counter/main.go @@ -22,7 +22,6 @@ const ( TracepointProgramName = "go-tracepoint-counter-example" BpfProgramMapIndex = "tracepoint_stats_map" DefaultByteCodeFile = "bpf_bpfel.o" - DefaultConfigPath = "/etc/bpfman/bpfman.toml" // MapsMountPoint is the "go-tracepoint-counter-maps" volumeMount "mountPath" from "deployment.yaml" MapsMountPoint = "/run/tracepoint/maps" @@ -39,7 +38,7 @@ func main() { // pull the BPFMAN config management data to determine if we're running on a // system with BPFMAN available. - paramData, err := configMgmt.ParseParamData(configMgmt.ProgTypeTracepoint, DefaultConfigPath, DefaultByteCodeFile) + paramData, err := configMgmt.ParseParamData(configMgmt.ProgTypeTracepoint, DefaultByteCodeFile) if err != nil { log.Printf("error processing parameters: %v\n", err) return @@ -57,11 +56,8 @@ func main() { } else { // if not on k8s, find the map path from the system ctx := context.Background() - // get the BPFMAN config - configFileData := configMgmt.LoadConfig(DefaultConfigPath) - // connect to the BPFMAN server - conn, err := configMgmt.CreateConnection(configFileData.Grpc.Endpoints, ctx) + conn, err := configMgmt.CreateConnection(ctx) if err != nil { log.Printf("failed to create client connection: %v", err) return diff --git a/examples/go-uprobe-counter/.gitignore b/examples/go-uprobe-counter/.gitignore new file mode 100644 index 000000000..f64fb3cf3 --- /dev/null +++ b/examples/go-uprobe-counter/.gitignore @@ -0,0 +1 @@ +go-uprobe-counter diff --git a/examples/go-uprobe-counter/bpf/uprobe_counter.c b/examples/go-uprobe-counter/bpf/uprobe_counter.c new file mode 100644 index 000000000..87fc75232 --- /dev/null +++ b/examples/go-uprobe-counter/bpf/uprobe_counter.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +// Copyright Authors of bpfman + +#include +#include +#include +#include + +#include + +struct datarec { + __u64 counter; +} datarec; + +struct { + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __type(key, __u32); + __type(value, datarec); + __uint(max_entries, 1); + __uint(pinning, LIBBPF_PIN_BY_NAME); +} uprobe_stats_map SEC(".maps"); + +SEC("uprobe/uprobe_counter") +static __u32 uprobe_counter(struct pt_regs *ctx) { + + __u32 index = 0; + struct datarec *rec = bpf_map_lookup_elem(&uprobe_stats_map, &index); + if (!rec) + return 1; + + rec->counter++; + bpf_printk("uprobe called"); + + return 0; +} + +char _license[] SEC("license") = "Dual BSD/GPL"; diff --git a/examples/go-uprobe-counter/container-deployment/Containerfile.go-uprobe-counter b/examples/go-uprobe-counter/container-deployment/Containerfile.go-uprobe-counter new file mode 100644 index 000000000..83ff9ff4d --- /dev/null +++ b/examples/go-uprobe-counter/container-deployment/Containerfile.go-uprobe-counter @@ -0,0 +1,22 @@ +FROM golang:1.19 as go-uprobe-counter-build + +RUN apt-get update && apt-get install -y \ + clang \ + gcc-multilib \ + libbpf-dev + +RUN go install github.com/cilium/ebpf/cmd/bpf2go@master + +WORKDIR /usr/src/bpfman/ +COPY ./ /usr/src/bpfman/ + +WORKDIR /usr/src/bpfman/examples/go-uprobe-counter + +# Compile go-uprobe-counter +RUN go build + +FROM registry.fedoraproject.org/fedora-minimal:latest + +COPY --from=go-uprobe-counter-build /usr/src/bpfman/examples/go-uprobe-counter/go-uprobe-counter . + +ENTRYPOINT ["./go-uprobe-counter", "--crd"] diff --git a/examples/go-uprobe-counter/main.go b/examples/go-uprobe-counter/main.go new file mode 100644 index 000000000..b3473fdf2 --- /dev/null +++ b/examples/go-uprobe-counter/main.go @@ -0,0 +1,195 @@ +//go:build linux +// +build linux + +package main + +import ( + "context" + "fmt" + "log" + "os" + "os/signal" + "syscall" + "time" + + bpfmanHelpers "github.com/bpfman/bpfman/bpfman-operator/pkg/helpers" + gobpfman "github.com/bpfman/bpfman/clients/gobpfman/v1" + configMgmt "github.com/bpfman/bpfman/examples/pkg/config-mgmt" + "github.com/cilium/ebpf" +) + +const ( + UprobeProgramName = "go-uprobe-counter-example" + BpfProgramMapIndex = "uprobe_stats_map" + DefaultByteCodeFile = "bpf_bpfel.o" + + // MapsMountPoint is the "go-uprobe-counter-maps" volumeMount "mountPath" from "deployment.yaml" + MapsMountPoint = "/run/uprobe/maps" +) + +type Stats struct { + Counter uint64 +} + +//go:generate bpf2go -cc clang -no-strip -cflags "-O2 -g -Wall" bpf ./bpf/uprobe_counter.c -- -I.:/usr/include/bpf:/usr/include/linux +func main() { + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + // pull the BPFMAN config management data to determine if we're running on a + // system with BPFMAN available. + paramData, err := configMgmt.ParseParamData(configMgmt.ProgTypeUprobe, DefaultByteCodeFile) + if err != nil { + log.Printf("error processing parameters: %v\n", err) + return + } + + // determine the path to the uprobe_stats_map, whether provided via CRD + // or BPFMAN or otherwise. + var mapPath string + // If running in a Kubernetes deployment, the eBPF program is already loaded. + // Only need the map path, which is at a known location in the pod using VolumeMounts + // and the CSI Driver. + if paramData.CrdFlag { + // 3. Get access to our map + mapPath = fmt.Sprintf("%s/%s", MapsMountPoint, BpfProgramMapIndex) + } else { // if not on k8s, find the map path from the system + ctx := context.Background() + + // connect to the BPFMAN server + conn, err := configMgmt.CreateConnection(ctx) + if err != nil { + log.Printf("failed to create client connection: %v", err) + return + } + + c := gobpfman.NewBpfmanClient(conn) + + fnName := "malloc" + + // If the bytecode src is a Program ID, skip the loading and unloading of the bytecode. + if paramData.BytecodeSrc != configMgmt.SrcProgId { + var loadRequest *gobpfman.LoadRequest + if paramData.MapOwnerId != 0 { + mapOwnerId := uint32(paramData.MapOwnerId) + loadRequest = &gobpfman.LoadRequest{ + Bytecode: paramData.BytecodeSource, + Name: "uprobe_counter", + ProgramType: *bpfmanHelpers.Kprobe.Uint32(), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_UprobeAttachInfo{ + UprobeAttachInfo: &gobpfman.UprobeAttachInfo{ + FnName: &fnName, + Target: "libc", + }, + }, + }, + MapOwnerId: &mapOwnerId, + } + } else { + loadRequest = &gobpfman.LoadRequest{ + Bytecode: paramData.BytecodeSource, + Name: "uprobe_counter", + ProgramType: *bpfmanHelpers.Kprobe.Uint32(), + Attach: &gobpfman.AttachInfo{ + Info: &gobpfman.AttachInfo_UprobeAttachInfo{ + UprobeAttachInfo: &gobpfman.UprobeAttachInfo{ + FnName: &fnName, + Target: "libc", + }, + }, + }, + } + } + + // 1. Load Program using bpfman + var res *gobpfman.LoadResponse + res, err = c.Load(ctx, loadRequest) + if err != nil { + conn.Close() + log.Print(err) + return + } + + kernelInfo := res.GetKernelInfo() + if kernelInfo != nil { + paramData.ProgId = uint(kernelInfo.GetId()) + } else { + conn.Close() + log.Printf("kernelInfo not returned in LoadResponse") + return + } + log.Printf("Program registered with id %d\n", paramData.ProgId) + + // 2. Set up defer to unload program when this is closed + defer func(id uint) { + log.Printf("unloading program: %d\n", id) + _, err = c.Unload(ctx, &gobpfman.UnloadRequest{Id: uint32(id)}) + if err != nil { + conn.Close() + log.Print(err) + return + } + conn.Close() + }(paramData.ProgId) + + // 3. Get access to our map + mapPath, err = configMgmt.CalcMapPinPath(res.GetInfo(), "uprobe_stats_map") + if err != nil { + log.Print(err) + return + } + } else { + // 2. Set up defer to close connection + defer func(id uint) { + log.Printf("Closing Connection for Program: %d\n", id) + conn.Close() + }(paramData.ProgId) + + // 3. Get access to our map + mapPath, err = configMgmt.RetrieveMapPinPath(ctx, c, paramData.ProgId, "uprobe_stats_map") + if err != nil { + log.Print(err) + return + } + } + } + + // load the pinned stats map which is keeping count of uprobe hits + opts := &ebpf.LoadPinOptions{ + ReadOnly: false, + WriteOnly: false, + Flags: 0, + } + statsMap, err := ebpf.LoadPinnedMap(mapPath, opts) + if err != nil { + log.Printf("Failed to load pinned Map: %s\n", mapPath) + log.Print(err) + return + } + + // retrieve and report on the number of times the uprobe is executed. + index := uint32(0) + ticker := time.NewTicker(1 * time.Second) + go func() { + for range ticker.C { + var stats []Stats + var totalCount uint64 + + if err := statsMap.Lookup(&index, &stats); err != nil { + log.Printf("map lookup failed: %v", err) + return + } + + for _, stat := range stats { + totalCount += stat.Counter + } + + log.Printf("Uprobe count: %d\n", totalCount) + } + }() + + <-stop + + log.Printf("Exiting...\n") +} diff --git a/examples/go-xdp-counter/main.go b/examples/go-xdp-counter/main.go index 10933cbc7..4208217c4 100644 --- a/examples/go-xdp-counter/main.go +++ b/examples/go-xdp-counter/main.go @@ -24,7 +24,6 @@ type Stats struct { } const ( - DefaultConfigPath = "/etc/bpfman/bpfman.toml" DefaultByteCodeFile = "bpf_bpfel.o" XdpProgramName = "go-xdp-counter-example" BpfProgramMapIndex = "xdp_stats_map" @@ -43,7 +42,7 @@ func main() { signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) // Parse Input Parameters (CmdLine and Config File) - paramData, err := configMgmt.ParseParamData(configMgmt.ProgTypeXdp, DefaultConfigPath, DefaultByteCodeFile) + paramData, err := configMgmt.ParseParamData(configMgmt.ProgTypeXdp, DefaultByteCodeFile) if err != nil { log.Printf("error processing parameters: %v\n", err) return @@ -60,9 +59,7 @@ func main() { } else { ctx := context.Background() - configFileData := configMgmt.LoadConfig(DefaultConfigPath) - - conn, err := configMgmt.CreateConnection(configFileData.Grpc.Endpoints, ctx) + conn, err := configMgmt.CreateConnection(ctx) if err != nil { log.Printf("failed to create client connection: %v", err) return diff --git a/examples/pkg/config-mgmt/configfile.go b/examples/pkg/config-mgmt/configfile.go index 7e716e72f..bef65afaa 100644 --- a/examples/pkg/config-mgmt/configfile.go +++ b/examples/pkg/config-mgmt/configfile.go @@ -20,85 +20,30 @@ import ( "context" "fmt" "log" - "os" - toml "github.com/pelletier/go-toml" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" ) -type Endpoint struct { - Type string `toml:"type"` - Path string `toml:"path"` - Port uint16 `toml:"port"` - Enabled bool `toml:"enabled"` -} - -type Grpc struct { - Endpoints []Endpoint `toml:"endpoints"` -} - -type ConfigFileData struct { - Grpc Grpc `toml:"grpc"` -} - const ( - DefaultType = "unix" - DefaultPath = "/run/bpfman/bpfman.sock" - DefaultEnabled = true + DefaultPath = "/run/bpfman-sock/bpfman.sock" ) -func LoadConfig(configFilePath string) ConfigFileData { - config := ConfigFileData{ - Grpc: Grpc{ - Endpoints: []Endpoint{ - { - Type: DefaultType, - Path: DefaultPath, - Enabled: DefaultEnabled, - }, - }, - }, - } - - file, err := os.ReadFile(configFilePath) - if err == nil { - err = toml.Unmarshal(file, &config) - if err != nil { - log.Printf("Unable to parse %s, using default configuration values.\n", configFilePath) - } else { - log.Printf("Using configuration values from %s\n", configFilePath) - } - } else { - log.Printf("Unable to read %s, using default configuration values.\n", configFilePath) - } - - return config -} - -func CreateConnection(endpoints []Endpoint, ctx context.Context) (*grpc.ClientConn, error) { +func CreateConnection(ctx context.Context) (*grpc.ClientConn, error) { var ( addr string local_creds credentials.TransportCredentials ) - for _, e := range endpoints { - if !e.Enabled { - continue - } - - if e.Type == "unix" { - addr = fmt.Sprintf("unix://%s", e.Path) - local_creds = insecure.NewCredentials() - } + addr = fmt.Sprintf("unix://%s", DefaultPath) + local_creds = insecure.NewCredentials() - conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(local_creds)) - if err == nil { - return conn, nil - } - log.Printf("did not connect: %v", err) + conn, err := grpc.DialContext(ctx, addr, grpc.WithTransportCredentials(local_creds)) + if err == nil { + return conn, nil } + log.Printf("did not connect: %v", err) - return nil, fmt.Errorf("unable to stablish connection") + return nil, fmt.Errorf("unable to establish connection") } diff --git a/examples/pkg/config-mgmt/param.go b/examples/pkg/config-mgmt/param.go index eebc7af31..fb28c975c 100644 --- a/examples/pkg/config-mgmt/param.go +++ b/examples/pkg/config-mgmt/param.go @@ -44,10 +44,12 @@ const ( ProgTypeXdp ProgType = iota ProgTypeTc ProgTypeTracepoint + ProgTypeKprobe + ProgTypeUprobe ) func (s ProgType) String() string { - return [...]string{"xdp", "tc", "tracepoint"}[s] + return [...]string{"xdp", "tc", "tracepoint", "kprobe", "uprobe"}[s] } const ( @@ -68,7 +70,7 @@ type ParameterData struct { BytecodeSrc int } -func ParseParamData(progType ProgType, configFilePath string, bytecodeFile string) (ParameterData, error) { +func ParseParamData(progType ProgType, bytecodeFile string) (ParameterData, error) { var paramData ParameterData paramData.BytecodeSrc = SrcNone @@ -141,7 +143,7 @@ func ParseParamData(progType ProgType, configFilePath string, bytecodeFile strin // defaults to 50 from the commandline. // ./go-xdp-counter -iface eth0 -priority 45 - // "-id" and "-location" are mutually exclusive and "-id" takes precedence. + // "-id" and "-image" are mutually exclusive and "-id" takes precedence. // Parse Commandline first. // "-id" is a ProgramID for the bytecode that has already loaded into bpfman. If not @@ -151,7 +153,7 @@ func ParseParamData(progType ProgType, configFilePath string, bytecodeFile strin // "-path" is a file path for the bytecode source. If not provided, check toml file. // ./go-xdp-counter -iface eth0 -path /var/bpfman/bytecode/bpf_bpfel.o if len(cmdlineFile) != 0 { - // "-location" was entered so it is a URL + // "-image" was entered so it is a URL paramData.BytecodeSource = &gobpfman.BytecodeLocation{ Location: &gobpfman.BytecodeLocation_File{File: cmdlineFile}, } @@ -162,7 +164,7 @@ func ParseParamData(progType ProgType, configFilePath string, bytecodeFile strin // "-image" is a container registry url for the bytecode source. If not provided, check toml file. // ./go-xdp-counter -p eth0 -image quay.io/bpfman-bytecode/go-xdp-counter:latest if len(cmdlineImage) != 0 { - // "-location" was entered so it is a URL + // "-image" was entered so it is a URL paramData.BytecodeSource = &gobpfman.BytecodeLocation{ Location: &gobpfman.BytecodeLocation_Image{Image: &gobpfman.BytecodeImage{ Url: cmdlineImage, diff --git a/go.mod b/go.mod index 6a0c34f8a..447aedeb9 100644 --- a/go.mod +++ b/go.mod @@ -8,21 +8,22 @@ require ( github.com/go-logr/logr v1.2.3 github.com/google/go-cmp v0.5.9 github.com/kong/kubernetes-testing-framework v0.24.1 - github.com/pelletier/go-toml v1.9.5 github.com/stretchr/testify v1.8.1 go.uber.org/zap v1.24.0 - google.golang.org/grpc v1.56.3 + google.golang.org/grpc v1.54.0 google.golang.org/protobuf v1.30.0 k8s.io/api v0.26.0 k8s.io/apimachinery v0.26.0 k8s.io/client-go v0.26.0 k8s.io/code-generator v0.26.0 sigs.k8s.io/controller-runtime v0.14.1 + sigs.k8s.io/yaml v1.3.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect + github.com/buger/jsonparser v1.1.1 github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect @@ -81,5 +82,4 @@ require ( sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/kind v0.17.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 213ca851a..ed97cae12 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -233,8 +235,6 @@ github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -584,8 +584,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/mkdocs.yml b/mkdocs.yml index 11918a506..31e02c020 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,19 +7,20 @@ site_dir: site theme: name: material + custom_dir: docs/overrides language: en - logo: img/bpfman_icon.svg - favicon: img/favicon.ico + logo: img/favicon.svg + favicon: img/favicon.svg palette: - media: "(prefers-color-scheme: light)" scheme: default - primary: red + primary: custom toggle: icon: material/weather-night name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate - primary: red + primary: custom toggle: icon: material/weather-sunny name: Switch to light mode @@ -50,13 +51,15 @@ markdown_extensions: nav: - Introduction: index.md - Getting Started: + - bpfman Overview: getting-started/overview.md + - Launching bpfman: getting-started/launching-bpfman.md + - Deploying Example eBPF Programs On Local Host: getting-started/example-bpf-local.md + - Deploying Example eBPF Programs On Kubernetes: getting-started/example-bpf-k8s.md - Setup and Building: getting-started/building-bpfman.md - Run bpfman From Release Image: getting-started/running-release.md - - Bpfman on Linux Tutorial: getting-started/tutorial.md + - Run bpfman From RPM: getting-started/running-rpm.md - CLI Guide: getting-started/cli-guide.md - Example eBPF Programs: getting-started/example-bpf.md - - Deploying Example eBPF Programs On Local Host: getting-started/example-bpf-local.md - - Deploying Example eBPF Programs On Kubernetes: getting-started/example-bpf-k8s.md - Troubleshooting: getting-started/troubleshooting.md - Developer Guide: - Contributing: governance/CONTRIBUTING.md @@ -73,6 +76,9 @@ nav: - Testing: developer-guide/testing.md - Debugging: developer-guide/debugging.md - Releasing: developer-guide/release.md + - XDP Tutorial: developer-guide/xdp-overview.md + - Design: + - Daemonless: design/daemonless.md - Blog: - blog/index.md - Community: @@ -90,6 +96,11 @@ plugins: blog_toc: true post_excerpt: required post_excerpt_max_authors: 2 + categories_allowed: + - Community Meeting + - "2023" + - "2024" - search + - mike copyright: Copyright © 2021-2023 The bpfman contributors diff --git a/netlify.toml b/netlify.toml deleted file mode 100644 index 783dbd255..000000000 --- a/netlify.toml +++ /dev/null @@ -1,7 +0,0 @@ -[build] -command = """ -pip3 install -r requirements.txt -./scripts/make-docs.sh -""" -environment = { PYTHON_VERSION = "3.8" } -publish = "site" diff --git a/packaging/rust-bpfman.spec b/packaging/rust-bpfman.spec deleted file mode 100644 index 16696a62d..000000000 --- a/packaging/rust-bpfman.spec +++ /dev/null @@ -1,55 +0,0 @@ -# Generated by rust2rpm 20 -%bcond_without check - -%global crate bpfman - -Name: rust-%{crate} -Version: 0.1.0 -Release: %autorelease -Summary: # FIXME - -# Upstream license specification: None -License: # FIXME - -URL: https://crates.io/crates/bpfman -Source: %{crates_source} - -ExclusiveArch: %{rust_arches} - -BuildRequires: rust-packaging - -%global _description %{expand: -%{summary}.} - -%description %{_description} - -%package -n %{crate} -Summary: %{summary} - -%description -n %{crate} %{_description} - -%files -n %{crate} -%license LICENSE-APACHE LICENSE-GPL2 LICENSE-MIT libbpf/LICENSE libbpf/LICENSE.BSD-2-Clause libbpf/LICENSE.LGPL-2.1 -%doc README.md -%{_bindir}/bpfman - -%prep -%autosetup -n %{crate}-%{version_no_tilde} -p1 -%cargo_prep - -%generate_buildrequires -%cargo_generate_buildrequires - -%build -%cargo_build - -%install -%cargo_install - -%if %{with check} -%check -%cargo_test -%endif - -%changelog -%autochangelog diff --git a/packaging/vm-deployment/provision.yaml b/packaging/vm-deployment/provision.yaml index e1dfdede7..318d3f2c6 100644 --- a/packaging/vm-deployment/provision.yaml +++ b/packaging/vm-deployment/provision.yaml @@ -6,7 +6,7 @@ tasks: - name: Set Variables set_fact: - vagrant_user: "vagrant" + vagrant_user: vagrant - name: Make directories for bpfman binaries and scripts shell: | @@ -37,7 +37,7 @@ - name: Install packages package: - name: ["openssl", "acl"] + name: [openssl, acl] - name: Change the working directory to bpfman/scripts and run become: true diff --git a/plans/example.fmf b/plans/example.fmf new file mode 100644 index 000000000..b440b7e38 --- /dev/null +++ b/plans/example.fmf @@ -0,0 +1,3 @@ +summary: Basic smoke test for bpfman +execute: + script: bpfman --help diff --git a/proto/bpfman.proto b/proto/bpfman.proto index 3f17f6fdb..66062150f 100644 --- a/proto/bpfman.proto +++ b/proto/bpfman.proto @@ -110,7 +110,7 @@ message KprobeAttachInfo { string fn_name = 1; uint64 offset = 2; bool retprobe = 3; - optional string namespace = 4; + optional int32 container_pid = 4; } /* UprobeAttachInfo represents the program specific metadata which bpfman @@ -123,7 +123,23 @@ message UprobeAttachInfo { string target = 3; bool retprobe = 4; optional int32 pid = 5; - optional string namespace = 6; + optional int32 container_pid = 6; +} + +/* FentryAttachInfo represents the program specific metadata which bpfman + * needs to attach and observe a Fentry program for a given kernel probe. + */ + +message FentryAttachInfo { + string fn_name = 1; +} + +/* FexitAttachInfo represents the program specific metadata which bpfman + * needs to attach and observe a Fexit program for a given kernel probe. + */ + +message FexitAttachInfo { + string fn_name = 1; } /* Program specific parameters, mostly concerning where and how to attach @@ -137,6 +153,8 @@ message AttachInfo { TracepointAttachInfo tracepoint_attach_info = 4; KprobeAttachInfo kprobe_attach_info = 5; UprobeAttachInfo uprobe_attach_info = 6; + FentryAttachInfo fentry_attach_info = 7; + FexitAttachInfo fexit_attach_info = 8; } }; diff --git a/release.toml b/release.toml index da8b334f0..7c97426f4 100644 --- a/release.toml +++ b/release.toml @@ -1,7 +1 @@ consolidate-commits = true -consolidate-pushes = true -dev-version = true -dev-version-ext = "dev" -post-release-commit-message = "bpfman, bpfman-common: start next development iteration {{next_version}}" -pre-release-commit-message = "bpfman, bpfman-common: release version {{version}}" -shared-version = true diff --git a/requirements.txt b/requirements.txt index e606c3897..19efde57c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,7 +27,7 @@ pymdown-extensions==10.4 pyparsing==3.0.9 pyquery==2.0.0 python-dateutil==2.8.2 -PyYAML==6.0 +PyYAML==6.0.1 pyyaml_env_tag==0.1 readtime==3.0.0 regex==2023.10.3 diff --git a/scripts/bpfman.service b/scripts/bpfman.service index 7f4679880..a4021830c 100644 --- a/scripts/bpfman.service +++ b/scripts/bpfman.service @@ -1,10 +1,10 @@ [Unit] Description=Run bpfman as a service DefaultDependencies=no -After=network.target +Requires=bpfman.socket [Service] Environment="RUST_LOG=Info" -ExecStart=/usr/sbin/bpfman system service -AmbientCapabilities=CAP_BPF CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_RESOURCE -CapabilityBoundingSet=CAP_BPF CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_RESOURCE +ExecStart=/usr/sbin/bpfman-rpc +#AmbientCapabilities=CAP_BPF CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_RESOURCE +#CapabilityBoundingSet=CAP_BPF CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_RESOURCE diff --git a/scripts/bpfman.socket b/scripts/bpfman.socket new file mode 100644 index 000000000..ee6445be6 --- /dev/null +++ b/scripts/bpfman.socket @@ -0,0 +1,9 @@ +[Unit] +Description=bpfman API Socket + +[Socket] +ListenStream=/run/bpfman-sock/bpfman.sock +SocketMode=0660 + +[Install] +WantedBy=sockets.target diff --git a/scripts/bpfman.toml b/scripts/bpfman.toml index f7b3eb7c0..657c56c89 100644 --- a/scripts/bpfman.toml +++ b/scripts/bpfman.toml @@ -2,13 +2,9 @@ [interface.eth0] xdp_mode = "hw" # Valid xdp modes are "hw", "skb" and "drv". Default: "skb". -[[grpc.endpoints]] -address = "::1" -enabled = true -port = 50051 -type = "tcp" +[signing] +allow_unsigned = true -[[grpc.endpoints]] -enabled = false -path = "/run/bpfman/bpfman.sock" -type = "unix" +[database] +max_retries = 10 +millisec_delay = 1000 diff --git a/scripts/certificates.sh b/scripts/certificates.sh deleted file mode 100755 index 4fb8e9d4d..000000000 --- a/scripts/certificates.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/bash - -KEY_LEN=4096 - -cert_init() { - regen=$1 - if [ -z "${regen}" ]; then - regen=false - fi - - if [ "${regen}" == false ]; then - echo "Creating certs:" - else - echo "Regenerating certs:" - fi - - # generate ca cert - mkdir -p "${CFG_CA_CERT_DIR}" - if [ ! -f "${CFG_CA_CERT_DIR}"/ca.pem ] || [ "${regen}" == true ]; then - openssl genrsa -out "${CFG_CA_CERT_DIR}"/ca.key ${KEY_LEN} - openssl req -new -x509 -key "${CFG_CA_CERT_DIR}"/ca.key -subj "/CN=bpfman-ca/" -out "${CFG_CA_CERT_DIR}"/ca.pem - # Set the private key such that only members of the "bpfman" group can read - chmod -v 0440 "${CFG_CA_CERT_DIR}"/ca.key - # Set the public key such that any user can read - chmod -v 0444 "${CFG_CA_CERT_DIR}"/ca.pem - fi - - cert_client "${BIN_BPFMAN}" ${regen} - cert_client "${BIN_BPFMAN_CLIENT}" ${regen} -} - -cert_client() { - sub_directory=$1 - regen=$2 - if [ -z "${sub_directory}" ]; then - echo "Sub-directory name required" - exit 1 - fi - if [ -z "${regen}" ]; then - regen=false - fi - - CERT_PATH="${CONFIGURATION_DIR}/certs/${sub_directory}" - - # If $regen is true, only regenerate certs that already existed. - if [ ! -f "${CERT_PATH}/${sub_directory}.pem" ] && [ $regen == true ]; then - exit 0 - fi - - mkdir -p "${CERT_PATH}" - if [ ! -f "${CERT_PATH}/${sub_directory}.pem" ] || [ $regen == true ]; then - openssl genrsa -out "${CERT_PATH}/${sub_directory}.key" ${KEY_LEN} - openssl req -new -key "${CERT_PATH}/${sub_directory}.key" \ - -subj "/CN=${sub_directory}/" \ - -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" \ - -out "${CERT_PATH}/${sub_directory}.csr" - openssl x509 -req -in "${CERT_PATH}/${sub_directory}.csr" \ - -CA "${CFG_CA_CERT_DIR}/ca.pem" \ - -CAkey "${CFG_CA_CERT_DIR}/ca.key" \ - -CAcreateserial \ - -extfile <(printf "subjectAltName=DNS:localhost,IP:127.0.0.1") \ - -out "${CERT_PATH}/${sub_directory}.pem" - rm "${CERT_PATH}/${sub_directory}.csr" - # Set the private and public keys such that only members of the user group can read - chmod -v 0440 "${CERT_PATH}/${sub_directory}.pem" "${CERT_PATH}/${sub_directory}.key" - fi -} diff --git a/scripts/install.sh b/scripts/install.sh index 81e5a5e2b..11d1f35ec 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -35,35 +35,87 @@ del_bin() { exit 1 fi - echo " Removing \"${bin_name}\"" - rm -f "${DST_BIN_PATH}/${bin_name}" + if test -f ${DST_BIN_PATH}/${bin_name}; then + echo " Removing \"${bin_name}\"" + rm -f "${DST_BIN_PATH}/${bin_name}" + fi } copy_svc() { - svc_name=$1 - if [ -z "${svc_name}" ]; then - echo "service name required" + svc_file=$1 + if [ -z "${svc_file}" ]; then + echo "service file required" exit 1 fi - echo " Copying \"${svc_name}.service\"" - cp "${svc_name}.service" "${DST_SVC_PATH}/${svc_name}.service" + echo " Copying \"${svc_file}\"" + cp "${svc_file}" "${DST_SVC_PATH}/${svc_file}" systemctl daemon-reload } del_svc() { - svc_name=$1 - if [ -z "${svc_name}" ]; then - echo "service name required" + svc_file=$1 + if [ -z "${svc_file}" ]; then + echo "service file required" exit 1 fi - echo " Stopping \"${svc_name}.service\"" - systemctl disable ${svc_name}.service - systemctl stop ${svc_name}.service + systemctl status "${svc_file}" &>/dev/null + if [ $? -eq 0 ]; then + echo " Stopping \"${svc_file}\"" + systemctl disable ${svc_file} + systemctl stop ${svc_file} + fi + + if test -f ${DST_SVC_PATH}/${svc_file}; then + echo " Removing \"${svc_file}\"" + rm -f "${DST_SVC_PATH}/${svc_file}" + fi +} + +copy_cli_tab_completion() { + if [ -d ${SRC_CLI_TAB_COMPLETE_PATH} ] && [ "$(ls -A ${SRC_CLI_TAB_COMPLETE_PATH})" ]; then + #if [ -d ${SRC_CLI_TAB_COMPLETE_PATH} ] && [ "$(find ${SRC_CLI_TAB_COMPLETE_PATH} -mindepth 1 -maxdepth 1)" ]; then + case $SHELL in + "/bin/bash") + echo " Copying \"${SRC_CLI_TAB_COMPLETE_PATH}}/bpfman.bash\" to \"${DST_CLI_TAB_COMPLETE_PATH}/.\"" + cp ${SRC_CLI_TAB_COMPLETE_PATH}/bpfman.bash ${DST_CLI_TAB_COMPLETE_PATH}/. + ;; + + *) + echo "Currently only bash is supported by this script. For other shells, manually install." + ;; + esac + + + else + echo " CLI TAB Completion files not generated yet. Use \"cargo xtask build-completion\" to generate." + fi +} + +del_cli_tab_completion() { + if [ -d ${DST_CLI_TAB_COMPLETE_PATH} ] && [ -f ${DST_CLI_TAB_COMPLETE_PATH}/bpfman.bash ]; then + echo " Removing CLI TAB Completion files from \"${DST_CLI_TAB_COMPLETE_PATH}/bpfman.bash\"" + rm ${DST_CLI_TAB_COMPLETE_PATH}/bpfman.bash &>/dev/null + fi +} + +copy_manpages() { + if [ -d ${SRC_MANPAGE_PATH} ] && [ "$(ls -A ${SRC_MANPAGE_PATH})" ]; then + #if [ -d ${SRC_MANPAGE_PATH} ] && [ -z "$(find ${SRC_MANPAGE_PATH} -mindepth 1 -maxdepth 1)" ]; then + echo " Copying \"${SRC_MANPAGE_PATH}\" to \"${DST_MANPAGE_PATH}\"" + rm ${DST_MANPAGE_PATH}/bpfman*.1 &>/dev/null + cp ${SRC_MANPAGE_PATH}/bpfman*.1 ${DST_MANPAGE_PATH}/. + else + echo " CLI Manpage files not generated yet. Use \"cargo xtask build-man-page\" to generate." + fi +} - echo " Removing \"${svc_name}.service\"" - rm -f "${DST_SVC_PATH}/${svc_name}.service" +del_manpages() { + if [ -d ${DST_MANPAGE_PATH} ] && [ -f ${DST_MANPAGE_PATH}/bpfman.1 ]; then + echo " Removing Manpage files from \"${DST_MANPAGE_PATH}\"" + rm ${DST_MANPAGE_PATH}/bpfman*.1 &>/dev/null + fi } install() { @@ -80,49 +132,78 @@ install() { release=false fi + echo "Copy CLI TAB Completion files:" + copy_cli_tab_completion + + echo "Copy Manpage files:" + copy_manpages + echo "Copy binaries:" if [ "${reinstall}" == true ]; then systemctl status bpfman | grep "Active:" | grep running &>/dev/null if [ $? -eq 0 ]; then - echo " Stopping \"${BIN_BPFMAN}.service\"" - systemctl stop ${BIN_BPFMAN}.service + echo " Stopping \"${SVC_BPFMAN_SVC}\"" + systemctl stop ${SVC_BPFMAN_SVC} start_bpfman=true fi fi copy_bin "${BIN_BPFMAN}" ${release} + copy_bin "${BIN_BPFMAN_RPC}" ${release} + copy_bin "${BIN_BPFMAN_NS}" ${release} if [ "${reinstall}" == false ]; then - echo "Copy service file:" - copy_svc "${BIN_BPFMAN}" - systemctl daemon-reload + echo "Copy service files:" + copy_svc "${SVC_BPFMAN_SOCK}" + copy_svc "${SVC_BPFMAN_SVC}" fi if [ "${start_bpfman}" == true ]; then - echo " Starting \"${BIN_BPFMAN}.service\"" - systemctl start ${BIN_BPFMAN}.service + echo " Starting \"${SVC_BPFMAN_SOCK}\"" + systemctl enable --now ${SVC_BPFMAN_SOCK} fi } uninstall() { - echo "Remove service file:" - del_svc "${BIN_BPFMAN}" + echo "Remove CLI TAB Completion files:" + del_cli_tab_completion + + echo "Remove Manpage files:" + del_manpages + + echo "Remove service files:" + del_svc "${SVC_BPFMAN_SOCK}" + del_svc "${SVC_BPFMAN_SVC}" echo "Remove binaries:" del_bin "${BIN_BPFMAN}" + del_bin "${BIN_BPFMAN_RPC}" del_kubectl_plugin + + # TO BE REMOVED! + # Left around to cleanup deprecated `bpfd` binary + SVC_BPFD_SVC="bpfd.service" + BIN_BPFD="bpfd" + BIN_BPFCTL="bpfctl" + del_svc "${SVC_BPFD_SVC}" + del_bin "${BIN_BPFD}" + del_bin "${BIN_BPFCTL}" } # TO BE REMOVED! -# Left around to cleanup deprecated lubectl plugins +# Left around to cleanup deprecated kubectl plugins del_kubectl_plugin() { - echo "Remove kubectl plugins:" - - echo " Deleting \"kubectl-bpfprogramconfigs\"" - rm -f "${DST_KUBECTL_PLUGIN_PATH}/kubectl-bpfprogramconfigs" + if test -f "${DST_KUBECTL_PLUGIN_PATH}/kubectl-bpfprogramconfigs"; then + echo "Remove kubectl plugins:" + echo " Deleting \"kubectl-bpfprogramconfigs\"" + rm -f "${DST_KUBECTL_PLUGIN_PATH}/kubectl-bpfprogramconfigs" + fi - echo " Deleting \"kubectl-bpfprograms\"" - rm -f "${DST_KUBECTL_PLUGIN_PATH}/kubectl-bpfprograms" + if test -f "${DST_KUBECTL_PLUGIN_PATH}/kubectl-bpfprograms"; then + echo "Remove kubectl plugins:" + echo " Deleting \"kubectl-bpfprograms\"" + rm -f "${DST_KUBECTL_PLUGIN_PATH}/kubectl-bpfprograms" + fi } diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100755 index 000000000..0298e406d --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +lint_all() { + echo "### Linting yaml" + if command -v prettier &>/dev/null; then + find ../ -type f -name '*.yaml' -print0 | xargs -0 prettier -l + else + echo "### prettier could not be found, skipping Yaml lint" + fi + echo "### Linting toml" + if command -v taplo &>/dev/null; then + taplo fmt --check + else + echo "### taplo could not be found, skipping Toml lint" + fi + echo "### Linting bash scripts" + if command -v shellcheck &>/dev/null; then + shellcheck -e SC2046 -e SC2086 -e SC2034 -e SC2181 -e SC2207 -e SC2002 -e SC2155 -e SC2128 ./*.sh + else + echo "### shellcheck could not be found, skipping shell lint" + fi + echo "### Linting rust code" + cargo +nightly fmt --all -- --check + cargo +nightly clippy --all -- --deny warnings + echo "### Linting golang code" + golangci-lint run ../bpfman-operator/... ../examples/... +} + +lint_all + diff --git a/scripts/make-docs.sh b/scripts/make-docs.sh deleted file mode 100755 index 159a59e4c..000000000 --- a/scripts/make-docs.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# Copyright 2021 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -o errexit -set -o nounset -set -o pipefail - -readonly SCRIPT_ROOT="$(cd "$(dirname "${BASH_SOURCE}")"/.. && pwd)" -cd $SCRIPT_ROOT - -# Wrap sed to deal with GNU and BSD sed flags. -run::sed() { - local -r vers="$(sed --version < /dev/null 2>&1 | grep -q GNU && echo gnu || echo bsd)" - case "$vers" in - gnu) sed -i "$@" ;; - *) sed -i '' "$@" ;; -esac -} - -# Generate docs with mkdocs -mkdocs build -d site -# Generate v1alpha2 API docs -./bpfman-operator/hack/api-docs/generate.sh site/api-spec.html -# Add them to spec page originally generated by mkdocs -run::sed -e '/REPLACE_WITH_GENERATED_CONTENT/{r site/api-spec.html' -e 'd;}' site/developer-guide/api-spec/index.html -run::sed -e '/REPLACE_WITH_GENERATED_CONTENT/{r site/api-spec.html' -e 'd;}' site/developer-guide/api-spec/index.html diff --git a/scripts/setup.sh b/scripts/setup.sh index ab7bbefcd..59da44949 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -2,22 +2,30 @@ CALL_POPD=false if [[ "$PWD" != */scripts ]]; then - pushd scripts &>/dev/null + pushd scripts &>/dev/null || exit fi # Source the functions in other files -. certificates.sh . install.sh . user.sh BIN_BPFMAN="bpfman" -BIN_BPFMAN_CLIENT="bpfman-client" +BIN_BPFMAN_RPC="bpfman-rpc" +BIN_BPFMAN_NS="bpfman-ns" # Well known directories SRC_DEBUG_BIN_PATH="../target/debug" -SRC_RELEASE_BIN_PATH="../target/x86_64-unknown-linux-musl/release" +SRC_RELEASE_BIN_PATH="../target/x86_64-unknown-linux-gnu/release" DST_BIN_PATH="/usr/sbin" DST_SVC_PATH="/usr/lib/systemd/system" +SVC_BPFMAN_SOCK="${BIN_BPFMAN}.socket" +SVC_BPFMAN_SVC="${BIN_BPFMAN}.service" + +SRC_CLI_TAB_COMPLETE_PATH="../.output/completions" +DST_CLI_TAB_COMPLETE_PATH="/usr/share/bash-completion/completions" + +SRC_MANPAGE_PATH="../.output/manpage" +DST_MANPAGE_PATH="/usr/local/share/man/man1" # ConfigurationDirectory: /etc/bpfman/ CONFIGURATION_DIR="/etc/bpfman" @@ -26,6 +34,7 @@ CFG_CA_CERT_DIR="/etc/bpfman/certs/ca" # RuntimeDirectory: /run/bpfman/ RUNTIME_DIR="/run/bpfman" RTDIR_FS="/run/bpfman/fs" +RUNTIME_SOCKET_DIR="/run/bpfman-sock" # StateDirectory: /var/lib/bpfman/ STATE_DIR="/var/lib/bpfman" @@ -37,8 +46,12 @@ usage() { echo " Prepare system for running \"bpfman\" as a systemd service. Performs the" echo " following tasks:" echo " * Copy \"bpfman\" binaries to \"/usr/sbin/.\"." + echo " * Copy \"bpfman\" CLI TAB completeion files to" + echo " \"/usr/share/bash-completion/completions/.\", if they have been generated." + echo " * Copy \"bpfman\" manpages to \"/usr/local/share/man/man1/.\", if they have" + echo " been generated." echo " * Copy \"bpfman.service\" to \"/usr/lib/systemd/system/\"." - echo " * Run \"systemctl start bpfman.service\" to start the sevice." + echo " * Run \"systemctl start bpfman.socket\" to start the sevice." echo "sudo ./scripts/setup.sh setup [--release]" echo " Same as \"install\" above, but don't start the service." echo "sudo ./scripts/setup.sh reinstall [--release]" @@ -93,10 +106,6 @@ case "$1" in uninstall user_cleanup ;; - "certs") - regen_cert=true - cert_init ${regen_cert} - ;; "help"|"--help"|"?") usage ;; @@ -108,5 +117,5 @@ case "$1" in esac if [[ "$CALL_POPD" == true ]]; then - popd &>/dev/null + popd &>/dev/null || exit fi diff --git a/scripts/user.sh b/scripts/user.sh index f9a8b5719..61ba0ea8e 100755 --- a/scripts/user.sh +++ b/scripts/user.sh @@ -2,14 +2,49 @@ delete_directories() { # Remove directories - echo "Deleting \"bpfman\" specific directories" - echo " Deleting \"${CONFIGURATION_DIR}\"" - rm -rf "${CONFIGURATION_DIR}" - echo " Deleting \"${RUNTIME_DIR}\"" - umount "${RTDIR_FS}" - rm -rf "${RUNTIME_DIR}" - echo " Deleting \"${STATE_DIR}\"" - rm -rf "${STATE_DIR}" + if test -d "${CONFIGURATION_DIR}"; then + echo "Deleting \"bpfman\" specific directories" + echo " Deleting \"${CONFIGURATION_DIR}\"" + rm -rf "${CONFIGURATION_DIR}" + fi + if test -d "${RUNTIME_DIR}"; then + echo " Deleting \"${RUNTIME_DIR}\"" + umount "${RTDIR_FS}" + rm -rf "${RUNTIME_DIR}" + fi + if test -d "${RUNTIME_SOCKET_DIR}"; then + echo " Deleting \"${RUNTIME_SOCKET_DIR}\"" + rm -rf "${RUNTIME_SOCKET_DIR}" + fi + if test -d "${STATE_DIR}"; then + echo " Deleting \"${STATE_DIR}\"" + rm -rf "${STATE_DIR}" + fi +} + +# TO BE REMOVED! +# Left around to cleanup deprecated `bpfd` directories +delete_bpfd_directories() { + BPFD_CONFIGURATION_DIR="/etc/bpfd" + BPFD_RUNTIME_DIR="/run/bpfd" + BPFD_RTDIR_FS="/run/bpfd/fs" + BPFD_STATE_DIR="/var/lib/bpfd" + + # Remove directories + if test -d "${BPFD_CONFIGURATION_DIR}"; then + echo "Deleting \"bpfd\" specific directories" + echo " Deleting \"${BPFD_CONFIGURATION_DIR}\"" + rm -rf "${BPFD_CONFIGURATION_DIR}" + fi + if test -d "${BPFD_RUNTIME_DIR}"; then + echo " Deleting \"${BPFD_RUNTIME_DIR}\"" + umount "${BPFD_RTDIR_FS}" + rm -rf "${BPFD_RUNTIME_DIR}" + fi + if test -d "${BPFD_STATE_DIR}"; then + echo " Deleting \"${BPFD_STATE_DIR}\"" + rm -rf "${BPFD_STATE_DIR}" + fi } delete_user() { @@ -23,7 +58,12 @@ delete_user() { user_group=${user_name} fi - echo "Deleting \"${user_name}:${user_group}\" user/group:" + user_exists=false + getent passwd ${user_name} &>/dev/null + if [[ $? -eq 0 ]]; then + echo "Deleting \"${user_name}:${user_group}\" user/group:" + user_exists=true + fi # Remove group from all users TMP_USER_LIST=($(cat /etc/group | grep ${user_group} | awk -F':' '{print $4}')) @@ -34,12 +74,9 @@ delete_user() { done # Delete User - getent passwd ${user_name} &>/dev/null - if [[ $? -eq 0 ]]; then + if [ "${user_exists}" == true ]; then echo " Deleting user \"${user_name}\"" userdel -r ${user_name} &>/dev/null - else - echo " User \"${user_name}\" does not exist" fi } @@ -47,8 +84,9 @@ user_cleanup() { delete_directories # TO BE REMOVED! - # Left around to cleanup deprecated `bpfman` user and user group - USER_BPFMAN="bpfman" - USER_GROUP="bpfman" - delete_user "${USER_BPFMAN}" "${USER_GROUP}" + # Left around to cleanup deprecated `bpfd` user and user group + USER_BPFD="bpfd" + USER_GROUP="bpfd" + delete_user "${USER_BPFD}" "${USER_GROUP}" + delete_bpfd_directories } diff --git a/scripts/verify-golint.sh b/scripts/verify-golint.sh index db6960148..02420d13f 100755 --- a/scripts/verify-golint.sh +++ b/scripts/verify-golint.sh @@ -1,4 +1,3 @@ - #!/bin/bash # Copyright 2023 The Kubernetes Authors. diff --git a/tests/integration-test/Cargo.toml b/tests/integration-test/Cargo.toml index 7c9e1cb33..6b5c9cff5 100644 --- a/tests/integration-test/Cargo.toml +++ b/tests/integration-test/Cargo.toml @@ -7,7 +7,7 @@ version = "0.1.0" [dependencies] anyhow = { workspace = true, features = ["std"] } assert_cmd = { workspace = true } -bpfman-api = { workspace = true } +bpfman = { workspace = true } env_logger = { workspace = true } integration-test-macros = { workspace = true } inventory = { workspace = true } diff --git a/tests/integration-test/bpf/build_push_images.sh b/tests/integration-test/bpf/build_push_images.sh index 7bd50f7f7..e19e03c2c 100755 --- a/tests/integration-test/bpf/build_push_images.sh +++ b/tests/integration-test/bpf/build_push_images.sh @@ -82,4 +82,23 @@ docker build \ docker push quay.io/bpfman-bytecode/kretprobe +docker build \ + --build-arg PROGRAM_NAME=do_unlinkat \ + --build-arg BPF_FUNCTION_NAME=test_fentry \ + --build-arg PROGRAM_TYPE=fentry \ + --build-arg BYTECODE_FILENAME=fentry.bpf.o \ + -f ../../../Containerfile.bytecode \ + ./.output -t quay.io/bpfman-bytecode/fentry:latest + +docker push quay.io/bpfman-bytecode/fentry + +docker build \ + --build-arg PROGRAM_NAME=do_unlinkat \ + --build-arg BPF_FUNCTION_NAME=test_fexit \ + --build-arg PROGRAM_TYPE=fexit \ + --build-arg BYTECODE_FILENAME=fentry.bpf.o \ + -f ../../../Containerfile.bytecode \ + ./.output -t quay.io/bpfman-bytecode/fexit:latest + +docker push quay.io/bpfman-bytecode/fexit diff --git a/tests/integration-test/bpf/fentry.bpf.c b/tests/integration-test/bpf/fentry.bpf.c new file mode 100644 index 000000000..b3f7d1d38 --- /dev/null +++ b/tests/integration-test/bpf/fentry.bpf.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +// Copyright Authors of bpfman + +// Some fentry/fexit test code + +// clang-format off +#include +#include +#include +// clang-format on + +SEC("fentry/do_unlinkat") +int BPF_PROG(test_fentry) { + bpf_printk("fentry: do_unlinkat ENTER\n"); + return 0; +} + +SEC("fexit/do_unlinkat") +int BPF_PROG(test_fexit) { + bpf_printk("fexit: do_unlinkat EXIT\n"); + return 0; +} + +char _license[] SEC("license") = "Dual BSD/GPL"; diff --git a/tests/integration-test/src/tests/basic.rs b/tests/integration-test/src/tests/basic.rs index 3fc9b8eef..88b124d62 100644 --- a/tests/integration-test/src/tests/basic.rs +++ b/tests/integration-test/src/tests/basic.rs @@ -1,13 +1,10 @@ use std::process::Command; use assert_cmd::prelude::*; -use bpfman_api::util::directories::{ - RTDIR_FS_MAPS, RTDIR_FS_TC_INGRESS, RTDIR_FS_XDP, STDIR_BYTECODE_IMAGE_CONTENT_STORE, -}; use log::debug; use rand::Rng; -use super::{integration_test, IntegrationTest}; +use super::{integration_test, IntegrationTest, RTDIR_FS_MAPS, RTDIR_FS_TC_INGRESS, RTDIR_FS_XDP}; use crate::tests::utils::*; #[integration_test] @@ -23,65 +20,11 @@ fn test_bpfmanhelptext() { .is_empty()); } -#[integration_test] -fn test_unix_socket_load_unload_xdp() { - let _namespace_guard = create_namespace().unwrap(); - let _ping_guard = start_ping().unwrap(); - - cfgfile_append_unix_socket(); - - let _bpfman_guard = start_bpfman().unwrap(); - - assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); - - debug!("Installing xdp_pass programs"); - - let globals = vec!["GLOBAL_u8=61", "GLOBAL_u32=0D0C0B0A"]; - - let proceed_on = vec![ - "aborted", - "drop", - "pass", - "tx", - "redirect", - "dispatcher_return", - ]; - - let mut loaded_ids = vec![]; - let mut rng = rand::thread_rng(); - - // Install a few xdp programs - for lt in LOAD_TYPES { - for _ in 0..5 { - let priority = rng.gen_range(1..255); - let (prog_id, _) = add_xdp( - DEFAULT_BPFMAN_IFACE, - priority, - Some(globals.clone()), - Some(proceed_on.clone()), - lt, - XDP_PASS_IMAGE_LOC, - XDP_PASS_FILE_LOC, - None, // metadata - None, // map_owner_id - ); - loaded_ids.push(prog_id.unwrap()); - } - } - assert_eq!(loaded_ids.len(), 10); - - assert!(bpffs_has_entries(RTDIR_FS_XDP)); - - verify_and_delete_programs(loaded_ids); - - cfgfile_remove(); -} - #[integration_test] fn test_load_unload_xdp() { let _namespace_guard = create_namespace().unwrap(); let _ping_guard = start_ping().unwrap(); - let bpfman_guard = start_bpfman().unwrap(); + //let bpfman_guard = start_bpfman().unwrap(); assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); @@ -124,8 +67,8 @@ fn test_load_unload_xdp() { assert!(bpffs_has_entries(RTDIR_FS_XDP)); // Verify rule persistence between restarts - drop(bpfman_guard); - let _bpfman_guard = start_bpfman().unwrap(); + // drop(bpfman_guard); + // let _bpfman_guard = start_bpfman().unwrap(); verify_and_delete_programs(loaded_ids); @@ -136,7 +79,7 @@ fn test_load_unload_xdp() { fn test_map_sharing_load_unload_xdp() { let _namespace_guard = create_namespace().unwrap(); let _ping_guard = start_ping().unwrap(); - let _bpfman_guard = start_bpfman().unwrap(); + //let bpfman_guard = start_bpfman().unwrap(); let load_type = LoadType::Image; assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); @@ -219,10 +162,14 @@ fn test_map_sharing_load_unload_xdp() { // Unload the Map Owner Program bpfman_del_program(&(map_owner_id.unwrap())); + //drop(bpfman_guard); + // Retrive the Program sharing the map let stdout_3 = bpfman_get(shared_owner_id.as_ref().unwrap()); let binding_3 = stdout_3.unwrap(); + //let _bpfman_guard = start_bpfman().unwrap(); + // Verify "Map Used By:" field is set to only the // 2nd loaded program (one sharing the map). let map_used_by_3 = bpfman_output_xdp_map_used_by(&binding_3); @@ -237,7 +184,7 @@ fn test_map_sharing_load_unload_xdp() { fn test_load_unload_tc() { let _namespace_guard = create_namespace().unwrap(); let _ping_guard = start_ping().unwrap(); - let _bpfman_guard = start_bpfman().unwrap(); + //let _bpfman_guard = start_bpfman().unwrap(); assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); @@ -298,8 +245,6 @@ fn test_load_unload_tc() { #[integration_test] fn test_load_unload_tracepoint() { - let _bpfman_guard = start_bpfman().unwrap(); - debug!("Installing tracepoint programs"); let globals = vec!["GLOBAL_u8=61", "GLOBAL_u32=0D0C0B0A"]; @@ -321,8 +266,6 @@ fn test_load_unload_tracepoint() { #[integration_test] fn test_load_unload_uprobe() { - let _bpfman_guard = start_bpfman().unwrap(); - debug!("Installing uprobe program"); let globals = vec!["GLOBAL_u8=63", "GLOBAL_u32=0D0C0B0A"]; @@ -335,6 +278,7 @@ fn test_load_unload_uprobe() { lt, UPROBE_IMAGE_LOC, URETPROBE_FILE_LOC, + None, ) .unwrap(); loaded_ids.push(prog_id); @@ -345,8 +289,6 @@ fn test_load_unload_uprobe() { #[integration_test] fn test_load_unload_uretprobe() { - let _bpfman_guard = start_bpfman().unwrap(); - debug!("Installing uretprobe program"); let globals = vec!["GLOBAL_u8=63", "GLOBAL_u32=0D0C0B0A"]; @@ -369,8 +311,6 @@ fn test_load_unload_uretprobe() { #[integration_test] fn test_load_unload_kprobe() { - let _bpfman_guard = start_bpfman().unwrap(); - debug!("Installing kprobe program"); let globals = vec!["GLOBAL_u8=63", "GLOBAL_u32=0D0C0B0A"]; @@ -388,8 +328,6 @@ fn test_load_unload_kprobe() { #[integration_test] fn test_load_unload_kretprobe() { - let _bpfman_guard = start_bpfman().unwrap(); - debug!("Installing kretprobe program"); let globals = vec!["GLOBAL_u8=63", "GLOBAL_u32=0D0C0B0A"]; @@ -413,24 +351,15 @@ fn test_load_unload_kretprobe() { #[integration_test] fn test_pull_bytecode() { - if std::path::PathBuf::from(STDIR_BYTECODE_IMAGE_CONTENT_STORE).exists() { - std::fs::remove_dir_all(STDIR_BYTECODE_IMAGE_CONTENT_STORE).unwrap(); - } - - let _bpfman_guard = start_bpfman().unwrap(); - debug!("Pull bytecode image"); - let _result = bpfman_pull_bytecode().unwrap(); - - let path = get_image_path(); - assert!(path.exists()); + // Just ensure this doesn't panic + assert!(bpfman_pull_bytecode().is_ok()); } #[integration_test] fn test_list_with_metadata() { let _namespace_guard = create_namespace().unwrap(); - let bpfman_guard = start_bpfman().unwrap(); assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); @@ -496,11 +425,35 @@ fn test_list_with_metadata() { assert!(bpffs_has_entries(RTDIR_FS_XDP)); - // Verify rule persistence between restarts - drop(bpfman_guard); - let _bpfman_guard = start_bpfman().unwrap(); - verify_and_delete_programs(loaded_ids); assert!(!bpffs_has_entries(RTDIR_FS_XDP)); } + +#[integration_test] +fn test_load_unload_fentry() { + debug!("Installing fentry program"); + + let mut loaded_ids = vec![]; + + for lt in LOAD_TYPES { + let prog_id = add_fentry_or_fexit(lt, FENTRY_IMAGE_LOC, FENTRY_FILE_LOC, true).unwrap(); + loaded_ids.push(prog_id); + } + + verify_and_delete_programs(loaded_ids); +} + +#[integration_test] +fn test_load_unload_fexit() { + debug!("Installing fexit program"); + + let mut loaded_ids = vec![]; + + for lt in LOAD_TYPES { + let prog_id = add_fentry_or_fexit(lt, FEXIT_IMAGE_LOC, FEXIT_FILE_LOC, false).unwrap(); + loaded_ids.push(prog_id); + } + + verify_and_delete_programs(loaded_ids); +} diff --git a/tests/integration-test/src/tests/e2e.rs b/tests/integration-test/src/tests/e2e.rs index 9609ea57d..af645b52c 100644 --- a/tests/integration-test/src/tests/e2e.rs +++ b/tests/integration-test/src/tests/e2e.rs @@ -1,9 +1,10 @@ use std::{path::PathBuf, thread::sleep, time::Duration}; -use bpfman_api::util::directories::{RTDIR_FS_TC_EGRESS, RTDIR_FS_TC_INGRESS, RTDIR_FS_XDP}; use log::debug; -use super::{integration_test, IntegrationTest}; +use super::{ + integration_test, IntegrationTest, RTDIR_FS_TC_EGRESS, RTDIR_FS_TC_INGRESS, RTDIR_FS_XDP, +}; use crate::tests::utils::*; const GLOBAL_1: &str = "GLOBAL_u8=25"; @@ -33,7 +34,6 @@ fn test_proceed_on_xdp() { let _namespace_guard = create_namespace().unwrap(); let _ping_guard = start_ping().unwrap(); let trace_guard = start_trace_pipe().unwrap(); - let _bpfman_guard = start_bpfman().unwrap(); assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); @@ -141,7 +141,6 @@ fn test_unload_xdp() { let _namespace_guard = create_namespace().unwrap(); let _ping_guard = start_ping().unwrap(); let trace_guard = start_trace_pipe().unwrap(); - let _bpfman_guard = start_bpfman().unwrap(); assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); @@ -238,7 +237,6 @@ fn test_proceed_on_tc() { let _namespace_guard = create_namespace().unwrap(); let _ping_guard = start_ping().unwrap(); let trace_guard = start_trace_pipe().unwrap(); - let bpfman_guard = start_bpfman().unwrap(); assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); @@ -377,10 +375,6 @@ fn test_proceed_on_tc() { assert!(trace_pipe_log.contains(TC_EG_GLOBAL_6_LOG)); debug!("Successfully completed tc egress proceed-on test"); - // Verify that the programs still work after we stop and restart bpfman - drop(bpfman_guard); - let _bpfman_guard = start_bpfman().unwrap(); - // Make sure it still works like it did before we stopped and restarted bpfman debug!("Clear the trace_pipe_log"); drop(trace_guard); @@ -415,7 +409,6 @@ fn test_unload_tc() { let _namespace_guard = create_namespace().unwrap(); let _ping_guard = start_ping().unwrap(); let trace_guard = start_trace_pipe().unwrap(); - let _bpfman_guard = start_bpfman().unwrap(); assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); @@ -554,7 +547,6 @@ fn test_program_execution_with_global_variables() { let _namespace_guard = create_namespace().unwrap(); let _ping_guard = start_ping().unwrap(); let _trace_guard = start_trace_pipe().unwrap(); - let _bpfman_guard = start_bpfman().unwrap(); assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); @@ -625,6 +617,7 @@ fn test_program_execution_with_global_variables() { &LoadType::Image, UPROBE_IMAGE_LOC, UPROBE_FILE_LOC, + None, ); loaded_ids.push(prog_id.unwrap()); @@ -697,7 +690,6 @@ fn test_program_execution_with_global_variables() { fn test_load_unload_xdp_maps() { let _namespace_guard = create_namespace().unwrap(); let _ping_guard = start_ping().unwrap(); - let bpfman_guard = start_bpfman().unwrap(); assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); @@ -724,10 +716,6 @@ fn test_load_unload_xdp_maps() { let map_pin_path = bpfman_output_map_pin_path(&binding); assert!(PathBuf::from(map_pin_path).join("xdp_stats_map").exists()); - // Verify rule persistence between restarts - drop(bpfman_guard); - let _bpfman_guard = start_bpfman().unwrap(); - verify_and_delete_programs(vec![prog_id.unwrap()]); assert!(!bpffs_has_entries(RTDIR_FS_XDP)); @@ -737,7 +725,6 @@ fn test_load_unload_xdp_maps() { fn test_load_unload_tc_maps() { let _namespace_guard = create_namespace().unwrap(); let _ping_guard = start_ping().unwrap(); - let bpfman_guard = start_bpfman().unwrap(); assert!(iface_exists(DEFAULT_BPFMAN_IFACE)); @@ -763,10 +750,6 @@ fn test_load_unload_tc_maps() { let map_pin_path = bpfman_output_map_pin_path(&binding); assert!(PathBuf::from(map_pin_path).join("tc_stats_map").exists()); - // Verify rule persistence between restarts - drop(bpfman_guard); - let _bpfman_guard = start_bpfman().unwrap(); - verify_and_delete_programs(vec![prog_id.unwrap()]); assert!(!bpffs_has_entries(RTDIR_FS_TC_INGRESS)); @@ -776,7 +759,6 @@ fn test_load_unload_tc_maps() { fn test_load_unload_tracepoint_maps() { let _namespace_guard = create_namespace().unwrap(); let _ping_guard = start_ping().unwrap(); - let bpfman_guard = start_bpfman().unwrap(); debug!("Installing tracepoint_counter program"); @@ -791,9 +773,39 @@ fn test_load_unload_tracepoint_maps() { .join("tracepoint_stats_map") .exists()); - // Verify rule persistence between restarts - drop(bpfman_guard); - let _bpfman_guard = start_bpfman().unwrap(); - verify_and_delete_programs(vec![prog_id.unwrap()]); } + +#[integration_test] +fn test_uprobe_container() { + // Start docker container and verify we can attach a uprobe inside. + + let container = start_container().unwrap(); + let _trace_guard = start_trace_pipe().unwrap(); + + let mut loaded_ids = vec![]; + + let container_pid = container.container_pid().to_string(); + + debug!("Installing uprobe program"); + let prog_id = add_uprobe( + Some([GLOBAL_1, "GLOBAL_u32=0A0B0C0D"].to_vec()), + &LoadType::Image, + UPROBE_IMAGE_LOC, + UPROBE_FILE_LOC, + Some(&container_pid), + ); + + loaded_ids.push(prog_id.unwrap()); + + // generate some mallocs which should generate some logs + for _ in 0..5 { + container.ls(); + } + + let trace_pipe_log = read_trace_pipe_log().unwrap(); + assert!(trace_pipe_log.contains(UPROBE_GLOBAL_1_LOG)); + debug!("Successfully validated uprobe in a container"); + + verify_and_delete_programs(loaded_ids); +} diff --git a/tests/integration-test/src/tests/mod.rs b/tests/integration-test/src/tests/mod.rs index 272894cf1..1204d7a75 100644 --- a/tests/integration-test/src/tests/mod.rs +++ b/tests/integration-test/src/tests/mod.rs @@ -10,4 +10,9 @@ pub struct IntegrationTest { pub test_fn: fn(), } +pub(crate) const RTDIR_FS_MAPS: &str = "/run/bpfman/fs/maps"; +pub(crate) const RTDIR_FS_TC_INGRESS: &str = "/run/bpfman/fs/tc-ingress"; +pub(crate) const RTDIR_FS_XDP: &str = "/run/bpfman/fs/xdp"; +pub(crate) const RTDIR_FS_TC_EGRESS: &str = "/run/bpfman/fs/tc-egress"; + inventory::collect!(IntegrationTest); diff --git a/tests/integration-test/src/tests/utils.rs b/tests/integration-test/src/tests/utils.rs index c82b084ce..7fe269a85 100644 --- a/tests/integration-test/src/tests/utils.rs +++ b/tests/integration-test/src/tests/utils.rs @@ -1,16 +1,10 @@ use std::{ - fs, - fs::File, - io::{Read, Write}, - path::{Path, PathBuf}, - process::Command, - thread::sleep, + fs::File, io::Read, path::PathBuf, process::Command, str::FromStr, thread::sleep, time::Duration, }; use anyhow::Result; use assert_cmd::prelude::*; -use bpfman_api::util::directories::{CFGPATH_BPFMAN_CONFIG, STDIR_BYTECODE_IMAGE_CONTENT_STORE}; use log::debug; use predicates::str::is_empty; use regex::Regex; @@ -49,6 +43,8 @@ pub const KRETPROBE_IMAGE_LOC: &str = "quay.io/bpfman-bytecode/kretprobe:latest" pub const XDP_COUNTER_IMAGE_LOC: &str = "quay.io/bpfman-bytecode/go-xdp-counter"; pub const TC_COUNTER_IMAGE_LOC: &str = "quay.io/bpfman-bytecode/go-tc-counter"; pub const TRACEPOINT_COUNTER_IMAGE_LOC: &str = "quay.io/bpfman-bytecode/go-tracepoint-counter"; +pub const FENTRY_IMAGE_LOC: &str = "quay.io/bpfman-bytecode/fentry:latest"; +pub const FEXIT_IMAGE_LOC: &str = "quay.io/bpfman-bytecode/fexit:latest"; pub const XDP_PASS_FILE_LOC: &str = "tests/integration-test/bpf/.output/xdp_pass.bpf.o"; pub const TC_PASS_FILE_LOC: &str = "tests/integration-test/bpf/.output/tc_pass.bpf.o"; @@ -57,6 +53,8 @@ pub const UPROBE_FILE_LOC: &str = "tests/integration-test/bpf/.output/uprobe.bpf pub const URETPROBE_FILE_LOC: &str = "tests/integration-test/bpf/.output/uprobe.bpf.o"; pub const KPROBE_FILE_LOC: &str = "tests/integration-test/bpf/.output/kprobe.bpf.o"; pub const KRETPROBE_FILE_LOC: &str = "tests/integration-test/bpf/.output/kprobe.bpf.o"; +pub const FENTRY_FILE_LOC: &str = "tests/integration-test/bpf/.output/fentry.bpf.o"; +pub const FEXIT_FILE_LOC: &str = "tests/integration-test/bpf/.output/fentry.bpf.o"; /// Exit on panic as well as the passing of a test #[derive(Debug)] @@ -78,53 +76,36 @@ impl Drop for ChildGuard { } /// Spawn a bpfman process -pub fn start_bpfman() -> Result { - debug!("Starting bpfman"); - - let bpfman_process = Command::cargo_bin("bpfman")? - .args(["system", "service"]) - .env("RUST_LOG", "bpfman=debug") - .spawn() - .map(|c| ChildGuard { - name: "bpfman", - child: c, - })?; - - // Wait for up to 5 seconds for bpfman to be ready - sleep(Duration::from_millis(100)); - for i in 1..51 { - if let Err(e) = Command::cargo_bin("bpfman")?.args(["list"]).ok() { - if i == 50 { - panic!("bpfman not ready after {} ms. Error:\n{}", i * 100, e); - } else { - sleep(Duration::from_millis(100)); - } - } else { - break; - } - } - debug!("Successfully Started bpfman"); - - Ok(bpfman_process) -} - -/// Update bpfman.toml with Unix Socket -pub fn cfgfile_append_unix_socket() { - debug!("Setup bpfman.toml with Unix Socket"); - - let mut f = File::create(CFGPATH_BPFMAN_CONFIG).unwrap(); - f.write_all( - b"[[grpc.endpoints]]\ntype = \"unix\"\nenabled = true\npath = \"/run/bpfman/bpfman.sock\"", - ) - .expect("could not write unix socket to bpfman.toml file"); -} - -/// Update bpfman.toml with Unix Socket -pub fn cfgfile_remove() { - debug!("Remove bpfman.toml"); - - fs::remove_file(CFGPATH_BPFMAN_CONFIG).expect("could not remove bpfman.toml file"); -} +// pub fn start_bpfman() -> Result { +// debug!("Starting bpfman"); + +// let bpfman_process = Command::cargo_bin("bpfman")? +// .args(["system", "service", "--timeout=0"]) +// .env("RUST_LOG", "bpfman=debug") +// .spawn() +// .map(|c| ChildGuard { +// name: "bpfman", +// child: c, +// })?; + +// debug!("started process"); +// // Wait for up to 5 seconds for bpfman to be ready +// sleep(Duration::from_millis(100)); +// for i in 1..51 { +// if let Err(e) = Command::cargo_bin("bpfman")?.args(["list"]).ok() { +// if i == 50 { +// panic!("bpfman not ready after {} ms. Error:\n{}", i * 100, e); +// } else { +// sleep(Duration::from_millis(100)); +// } +// } else { +// break; +// } +// } +// debug!("Successfully Started bpfman"); + +// Ok(bpfman_process) +// } /// Install an xdp program with bpfman #[allow(clippy::too_many_arguments)] @@ -301,12 +282,16 @@ pub fn add_tracepoint( (Ok(prog_id), Ok(stdout)) } -/// Attach a uprobe program to bpfman with bpfman +/// Attach a uprobe program. +/// +/// If a container_pid is provided, attach it to malloc() in that namespace. +/// Otherwise, attach it to the main function in the bpfctl command. pub fn add_uprobe( globals: Option>, load_type: &LoadType, image_url: &str, file_path: &str, + container_pid: Option<&str>, ) -> Result { let bpfman_cmd = Command::cargo_bin("bpfman")?; let bpfman_path = bpfman_cmd.get_program().to_str().unwrap(); @@ -331,7 +316,19 @@ pub fn add_uprobe( LoadType::File => args.extend(["-n", "my_uprobe", "--path", file_path]), } - args.extend(["uprobe", "-f", "main", "-t", bpfman_path]); + if let Some(pid) = container_pid { + args.extend([ + "uprobe", + "-f", + "malloc", + "-t", + "libc", + "--container-pid", + pid, + ]); + } else { + args.extend(["uprobe", "-f", "main", "-t", bpfman_path]); + } let output = Command::cargo_bin("bpfman")?.args(args).ok(); let stdout = String::from_utf8(output.unwrap().stdout).unwrap(); @@ -467,6 +464,58 @@ pub fn add_kretprobe( Ok(prog_id) } +/// Install a fentry or fexit program with bpfman +pub fn add_fentry_or_fexit( + load_type: &LoadType, + image_url: &str, + file_path: &str, + fentry: bool, +) -> Result { + let mut args = vec!["load"]; + match load_type { + LoadType::Image => { + args.push("image"); + } + LoadType::File => { + args.push("file"); + } + } + + match load_type { + LoadType::Image => args.extend(["--image-url", image_url, "--pull-policy", "Always"]), + LoadType::File => { + if fentry { + args.extend(["-n", "test_fentry", "--path", file_path]); + } else { + args.extend(["-n", "test_fexit", "--path", file_path]); + } + } + } + + if fentry { + args.extend(["fentry", "-f", "do_unlinkat"]); + } else { + args.extend(["fexit", "-f", "do_unlinkat"]); + } + + let output = Command::cargo_bin("bpfman")?.args(args).ok(); + let stdout = String::from_utf8(output.unwrap().stdout).unwrap(); + let prog_id = bpfman_output_parse_id(&stdout); + assert!(!prog_id.is_empty()); + if fentry { + debug!( + "Successfully added fentry program: {:?} from: {:?}", + prog_id, load_type + ); + } else { + debug!( + "Successfully added fexit program: {:?} from: {:?}", + prog_id, load_type + ); + } + Ok(prog_id) +} + /// Delete a bpfman program using bpfman pub fn bpfman_del_program(prog_id: &str) { Command::cargo_bin("bpfman") @@ -524,11 +573,6 @@ pub fn bpfman_pull_bytecode() -> Result { Ok(stdout.unwrap()) } -pub fn get_image_path() -> PathBuf { - let relative_path = str::replace(TRACEPOINT_IMAGE_LOC, ":", "/"); - Path::new(STDIR_BYTECODE_IMAGE_CONTENT_STORE).join(relative_path) -} - /// Retrieve the output of bpfman list pub fn tc_filter_list(iface: &str) -> Result { let output = Command::new("tc") @@ -823,18 +867,18 @@ pub fn bpffs_has_entries(path: &str) -> bool { fn bpfman_output_parse_id(stdout: &str) -> String { // Regex: - // Match the string "\n ID: ". + // Match the string "\n Program ID: ". // The {2,} indicates to match the previous token (a space) between 2 and // unlimited times. // For the capture group (.*?), the . indicates to capture any character // (except for line terminators) and the *? indicates to capture "the previous // token between zero and unlimited times". // The \s indicates to match any whites space. - let re = Regex::new(r"\n ID: {2,}(.*?)\s").unwrap(); + let re = Regex::new(r"\n Program ID: {2,}(.*?)\s").unwrap(); match re.captures(stdout) { Some(caps) => caps[1].to_owned(), None => { - debug!("\"ID:\" not found",); + debug!("\"Program ID:\" not found",); "".to_string() } } @@ -939,3 +983,87 @@ pub fn bpfman_output_map_ids(stdout: &str) -> Vec { map_ids } + +#[derive(Debug)] +pub struct DockerContainer { + container_pid: i32, + container_id: String, +} + +impl Drop for DockerContainer { + fn drop(&mut self) { + let output = Command::new("docker") + .args(["rm", "-f", self.container_id.as_str()]) + .output() + .expect("failed to start docker"); + + if output.status.success() { + debug!("Docker container {} removed", self.container_id); + } else { + debug!("Error removing container {}", self.container_id); + } + } +} + +impl DockerContainer { + /// Return the container PID + pub fn container_pid(&self) -> i32 { + self.container_pid + } + + /// Runs the ls command in the container to generate some mallocs. + pub fn ls(&self) { + let output = Command::new("docker") + .args(["exec", &self.container_id, "ls"]) + .output() + .expect("failed run ls in container"); + assert!(output.status.success()); + } +} + +/// Starts a docker container from the nginx image +pub fn start_container() -> Result { + let status = Command::new("systemctl") + .args(["start", "docker"]) + .status() + .expect("failed to start docker"); + assert!(status.success()); + + let output = Command::new("docker") + .args(["run", "--name", "mynginx1", "-p", "80:80", "-d", "nginx"]) + .output() + .expect("failed to start nginx"); + + let mut container_id = String::from_utf8(output.stdout).unwrap(); + // Get rid of trailing '\n' + container_id.pop(); + + assert!(!container_id.is_empty()); + + let output = Command::new("lsns") + .args(["-t", "pid"]) + .output() + .expect("systemctl start docker"); + + let output = String::from_utf8(output.stdout).unwrap(); + + let mut container_pid: i32 = 0; + for line in output.lines() { + if line.contains("nginx") { + let pid_str: Vec<&str> = line.split_whitespace().collect(); + container_pid = FromStr::from_str(pid_str[3]).unwrap(); + break; + } + } + assert!(container_pid != 0); + + debug!( + "Docker container with ID {} and PID {} created", + container_id, container_pid + ); + + Ok(DockerContainer { + container_pid, + container_id, + }) +} diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index b76bce571..1e6f0c3b4 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -6,6 +6,9 @@ version = "0.1.0" [dependencies] anyhow = { workspace = true, features = ["std"] } +bpfman = { workspace = true } +bpfman-api = { workspace = true } +cargo_metadata = { workspace = true } clap = { workspace = true, features = [ "derive", "help", @@ -13,6 +16,14 @@ clap = { workspace = true, features = [ "suggestions", "usage", ] } +clap_complete = { workspace = true } +clap_mangen = { workspace = true } +dialoguer = { workspace = true } +diff = { workspace = true } +hex = { workspace = true, features = ["std"] } lazy_static = { workspace = true } +public-api = { workspace = true } +rustdoc-json = { workspace = true } +rustup-toolchain = { workspace = true } serde_json = { workspace = true, features = ["std"] } tonic-build = { workspace = true, features = ["prost"] } diff --git a/xtask/public-api/bpfman.txt b/xtask/public-api/bpfman.txt new file mode 100644 index 000000000..8b357c8a8 --- /dev/null +++ b/xtask/public-api/bpfman.txt @@ -0,0 +1,1470 @@ +pub mod bpfman +pub mod bpfman::errors +pub enum bpfman::errors::BpfmanError +pub bpfman::errors::BpfmanError::BpfBytecodeError(crate::oci_utils::ImageError) +pub bpfman::errors::BpfmanError::BpfFunctionNameNotValid(alloc::string::String) +pub bpfman::errors::BpfmanError::BpfIOError(std::io::error::Error) +pub bpfman::errors::BpfmanError::BpfLoadError(aya::bpf::BpfError) +pub bpfman::errors::BpfmanError::BpfProgramError(aya::programs::ProgramError) +pub bpfman::errors::BpfmanError::BpfmanProgramDeleteError(anyhow::Error) +pub bpfman::errors::BpfmanError::BtfError(aya_obj::btf::btf::BtfError) +pub bpfman::errors::BpfmanError::BytecodeMetaDataMismatch +pub bpfman::errors::BpfmanError::BytecodeMetaDataMismatch::image_prog_name: alloc::string::String +pub bpfman::errors::BpfmanError::BytecodeMetaDataMismatch::provided_prog_name: alloc::string::String +pub bpfman::errors::BpfmanError::ContainerAttachError +pub bpfman::errors::BpfmanError::ContainerAttachError::container_pid: i32 +pub bpfman::errors::BpfmanError::ContainerAttachError::program_type: alloc::string::String +pub bpfman::errors::BpfmanError::DatabaseError(alloc::string::String, alloc::string::String) +pub bpfman::errors::BpfmanError::DatabaseLockError +pub bpfman::errors::BpfmanError::DispatcherNotRequired +pub bpfman::errors::BpfmanError::Error(alloc::string::String) +pub bpfman::errors::BpfmanError::InternalError(alloc::string::String) +pub bpfman::errors::BpfmanError::InvalidAttach(alloc::string::String) +pub bpfman::errors::BpfmanError::InvalidInterface +pub bpfman::errors::BpfmanError::NotLoaded +pub bpfman::errors::BpfmanError::RpcRecvError(tokio::sync::oneshot::error::RecvError) +pub bpfman::errors::BpfmanError::RpcSendError(anyhow::Error) +pub bpfman::errors::BpfmanError::TooManyPrograms +pub bpfman::errors::BpfmanError::UnableToPinLink(aya::pin::PinError) +pub bpfman::errors::BpfmanError::UnableToPinMap(aya::pin::PinError) +pub bpfman::errors::BpfmanError::UnableToPinProgram(aya::pin::PinError) +impl core::convert::From for bpfman::errors::BpfmanError +pub fn bpfman::errors::BpfmanError::from(source: anyhow::Error) -> Self +impl core::convert::From for bpfman::errors::BpfmanError +pub fn bpfman::errors::BpfmanError::from(source: aya::bpf::BpfError) -> Self +impl core::convert::From for bpfman::errors::BpfmanError +pub fn bpfman::errors::BpfmanError::from(source: aya::programs::ProgramError) -> Self +impl core::convert::From for bpfman::errors::BpfmanError +pub fn bpfman::errors::BpfmanError::from(source: aya_obj::btf::btf::BtfError) -> Self +impl core::convert::From for bpfman::errors::BpfmanError +pub fn bpfman::errors::BpfmanError::from(source: std::io::error::Error) -> Self +impl core::convert::From for bpfman::errors::BpfmanError +pub fn bpfman::errors::BpfmanError::from(source: tokio::sync::oneshot::error::RecvError) -> Self +impl core::error::Error for bpfman::errors::BpfmanError +pub fn bpfman::errors::BpfmanError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)> +impl core::fmt::Debug for bpfman::errors::BpfmanError +pub fn bpfman::errors::BpfmanError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for bpfman::errors::BpfmanError +pub fn bpfman::errors::BpfmanError::fmt(&self, __formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::errors::BpfmanError +impl core::marker::Send for bpfman::errors::BpfmanError +impl core::marker::Sync for bpfman::errors::BpfmanError +impl core::marker::Unpin for bpfman::errors::BpfmanError +impl !core::panic::unwind_safe::RefUnwindSafe for bpfman::errors::BpfmanError +impl !core::panic::unwind_safe::UnwindSafe for bpfman::errors::BpfmanError +impl core::convert::Into for bpfman::errors::BpfmanError where U: core::convert::From +pub fn bpfman::errors::BpfmanError::into(self) -> U +impl core::convert::TryFrom for bpfman::errors::BpfmanError where U: core::convert::Into +pub type bpfman::errors::BpfmanError::Error = core::convert::Infallible +pub fn bpfman::errors::BpfmanError::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::errors::BpfmanError where U: core::convert::TryFrom +pub type bpfman::errors::BpfmanError::Error = >::Error +pub fn bpfman::errors::BpfmanError::try_into(self) -> core::result::Result>::Error> +impl alloc::string::ToString for bpfman::errors::BpfmanError where T: core::fmt::Display + core::marker::Sized +pub fn bpfman::errors::BpfmanError::to_string(&self) -> alloc::string::String +impl core::any::Any for bpfman::errors::BpfmanError where T: 'static + core::marker::Sized +pub fn bpfman::errors::BpfmanError::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::errors::BpfmanError where T: core::marker::Sized +pub fn bpfman::errors::BpfmanError::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::errors::BpfmanError where T: core::marker::Sized +pub fn bpfman::errors::BpfmanError::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::errors::BpfmanError +pub fn bpfman::errors::BpfmanError::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::errors::BpfmanError +pub type bpfman::errors::BpfmanError::Init = T +pub const bpfman::errors::BpfmanError::ALIGN: usize +pub unsafe fn bpfman::errors::BpfmanError::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::errors::BpfmanError::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::errors::BpfmanError::drop(ptr: usize) +pub unsafe fn bpfman::errors::BpfmanError::init(init: ::Init) -> usize +impl snafu::AsErrorSource for bpfman::errors::BpfmanError where T: core::error::Error + 'static +pub fn bpfman::errors::BpfmanError::as_error_source(&self) -> &(dyn core::error::Error + 'static) +impl tracing::instrument::Instrument for bpfman::errors::BpfmanError +impl tracing::instrument::WithSubscriber for bpfman::errors::BpfmanError +impl typenum::type_operators::Same for bpfman::errors::BpfmanError +pub type bpfman::errors::BpfmanError::Output = T +impl ppv_lite86::types::VZip for bpfman::errors::BpfmanError where V: ppv_lite86::types::MultiLane +pub fn bpfman::errors::BpfmanError::vzip(self) -> V +pub enum bpfman::errors::ParseError +pub bpfman::errors::ParseError::BytecodeLocationParseFailure(url::parser::ParseError) +pub bpfman::errors::ParseError::ConfigParseError(toml::de::Error) +pub bpfman::errors::ParseError::InvalidBytecodeImagePullPolicy +pub bpfman::errors::ParseError::InvalidBytecodeImagePullPolicy::pull_policy: alloc::string::String +pub bpfman::errors::ParseError::InvalidBytecodeLocation +pub bpfman::errors::ParseError::InvalidBytecodeLocation::location: alloc::string::String +pub bpfman::errors::ParseError::InvalidDirection +pub bpfman::errors::ParseError::InvalidDirection::direction: alloc::string::String +pub bpfman::errors::ParseError::InvalidProbeType +pub bpfman::errors::ParseError::InvalidProbeType::probe: alloc::string::String +pub bpfman::errors::ParseError::InvalidProceedOn +pub bpfman::errors::ParseError::InvalidProceedOn::proceedon: alloc::string::String +pub bpfman::errors::ParseError::InvalidProgramType +pub bpfman::errors::ParseError::InvalidProgramType::program: alloc::string::String +pub bpfman::errors::ParseError::InvalidXdpMode +pub bpfman::errors::ParseError::InvalidXdpMode::mode: alloc::string::String +impl core::convert::From for bpfman::errors::ParseError +pub fn bpfman::errors::ParseError::from(source: toml::de::Error) -> Self +impl core::error::Error for bpfman::errors::ParseError +pub fn bpfman::errors::ParseError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)> +impl core::fmt::Debug for bpfman::errors::ParseError +pub fn bpfman::errors::ParseError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for bpfman::errors::ParseError +pub fn bpfman::errors::ParseError::fmt(&self, __formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::errors::ParseError +impl core::marker::Send for bpfman::errors::ParseError +impl core::marker::Sync for bpfman::errors::ParseError +impl core::marker::Unpin for bpfman::errors::ParseError +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::errors::ParseError +impl core::panic::unwind_safe::UnwindSafe for bpfman::errors::ParseError +impl core::convert::Into for bpfman::errors::ParseError where U: core::convert::From +pub fn bpfman::errors::ParseError::into(self) -> U +impl core::convert::TryFrom for bpfman::errors::ParseError where U: core::convert::Into +pub type bpfman::errors::ParseError::Error = core::convert::Infallible +pub fn bpfman::errors::ParseError::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::errors::ParseError where U: core::convert::TryFrom +pub type bpfman::errors::ParseError::Error = >::Error +pub fn bpfman::errors::ParseError::try_into(self) -> core::result::Result>::Error> +impl alloc::string::ToString for bpfman::errors::ParseError where T: core::fmt::Display + core::marker::Sized +pub fn bpfman::errors::ParseError::to_string(&self) -> alloc::string::String +impl core::any::Any for bpfman::errors::ParseError where T: 'static + core::marker::Sized +pub fn bpfman::errors::ParseError::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::errors::ParseError where T: core::marker::Sized +pub fn bpfman::errors::ParseError::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::errors::ParseError where T: core::marker::Sized +pub fn bpfman::errors::ParseError::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::errors::ParseError +pub fn bpfman::errors::ParseError::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::errors::ParseError +pub type bpfman::errors::ParseError::Init = T +pub const bpfman::errors::ParseError::ALIGN: usize +pub unsafe fn bpfman::errors::ParseError::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::errors::ParseError::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::errors::ParseError::drop(ptr: usize) +pub unsafe fn bpfman::errors::ParseError::init(init: ::Init) -> usize +impl snafu::AsErrorSource for bpfman::errors::ParseError where T: core::error::Error + 'static +pub fn bpfman::errors::ParseError::as_error_source(&self) -> &(dyn core::error::Error + 'static) +impl tracing::instrument::Instrument for bpfman::errors::ParseError +impl tracing::instrument::WithSubscriber for bpfman::errors::ParseError +impl typenum::type_operators::Same for bpfman::errors::ParseError +pub type bpfman::errors::ParseError::Output = T +impl ppv_lite86::types::VZip for bpfman::errors::ParseError where V: ppv_lite86::types::MultiLane +pub fn bpfman::errors::ParseError::vzip(self) -> V +pub mod bpfman::types +pub enum bpfman::types::Direction +pub bpfman::types::Direction::Egress = 2 +pub bpfman::types::Direction::Ingress = 1 +impl core::clone::Clone for bpfman::types::Direction +pub fn bpfman::types::Direction::clone(&self) -> bpfman::types::Direction +impl core::cmp::Eq for bpfman::types::Direction +impl core::cmp::PartialEq for bpfman::types::Direction +pub fn bpfman::types::Direction::eq(&self, other: &bpfman::types::Direction) -> bool +impl core::convert::TryFrom for bpfman::types::Direction +pub type bpfman::types::Direction::Error = bpfman::errors::ParseError +pub fn bpfman::types::Direction::try_from(v: alloc::string::String) -> core::result::Result +impl core::convert::TryFrom for bpfman::types::Direction +pub type bpfman::types::Direction::Error = bpfman::errors::ParseError +pub fn bpfman::types::Direction::try_from(v: u32) -> core::result::Result +impl core::fmt::Debug for bpfman::types::Direction +pub fn bpfman::types::Direction::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for bpfman::types::Direction +pub fn bpfman::types::Direction::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::hash::Hash for bpfman::types::Direction +pub fn bpfman::types::Direction::hash<__H: core::hash::Hasher>(&self, state: &mut __H) +impl core::marker::Copy for bpfman::types::Direction +impl core::marker::StructuralPartialEq for bpfman::types::Direction +impl serde::ser::Serialize for bpfman::types::Direction +pub fn bpfman::types::Direction::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde::ser::Serializer>::Ok, <__S as serde::ser::Serializer>::Error> where __S: serde::ser::Serializer +impl<'de> serde::de::Deserialize<'de> for bpfman::types::Direction +pub fn bpfman::types::Direction::deserialize<__D>(__deserializer: __D) -> core::result::Result::Error> where __D: serde::de::Deserializer<'de> +impl core::marker::Freeze for bpfman::types::Direction +impl core::marker::Send for bpfman::types::Direction +impl core::marker::Sync for bpfman::types::Direction +impl core::marker::Unpin for bpfman::types::Direction +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::types::Direction +impl core::panic::unwind_safe::UnwindSafe for bpfman::types::Direction +impl jwt::token::signed::SignWithKey for bpfman::types::Direction where C: jwt::ToBase64 +pub fn bpfman::types::Direction::sign_with_key(self, key: &impl jwt::algorithm::SigningAlgorithm) -> core::result::Result +impl equivalent::Equivalent for bpfman::types::Direction where Q: core::cmp::Eq + core::marker::Sized, K: core::borrow::Borrow + core::marker::Sized +pub fn bpfman::types::Direction::equivalent(&self, key: &K) -> bool +impl hashbrown::Equivalent for bpfman::types::Direction where Q: core::cmp::Eq + core::marker::Sized, K: core::borrow::Borrow + core::marker::Sized +pub fn bpfman::types::Direction::equivalent(&self, key: &K) -> bool +impl core::convert::Into for bpfman::types::Direction where U: core::convert::From +pub fn bpfman::types::Direction::into(self) -> U +impl core::convert::TryFrom for bpfman::types::Direction where U: core::convert::Into +pub type bpfman::types::Direction::Error = core::convert::Infallible +pub fn bpfman::types::Direction::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::Direction where U: core::convert::TryFrom +pub type bpfman::types::Direction::Error = >::Error +pub fn bpfman::types::Direction::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::Direction where T: core::clone::Clone +pub type bpfman::types::Direction::Owned = T +pub fn bpfman::types::Direction::clone_into(&self, target: &mut T) +pub fn bpfman::types::Direction::to_owned(&self) -> T +impl alloc::string::ToString for bpfman::types::Direction where T: core::fmt::Display + core::marker::Sized +pub fn bpfman::types::Direction::to_string(&self) -> alloc::string::String +impl core::any::Any for bpfman::types::Direction where T: 'static + core::marker::Sized +pub fn bpfman::types::Direction::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::Direction where T: core::marker::Sized +pub fn bpfman::types::Direction::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::Direction where T: core::marker::Sized +pub fn bpfman::types::Direction::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::Direction +pub fn bpfman::types::Direction::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::Direction +pub type bpfman::types::Direction::Init = T +pub const bpfman::types::Direction::ALIGN: usize +pub unsafe fn bpfman::types::Direction::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::Direction::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::Direction::drop(ptr: usize) +pub unsafe fn bpfman::types::Direction::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::Direction where T: core::clone::Clone +pub fn bpfman::types::Direction::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl jwt::FromBase64 for bpfman::types::Direction where T: for<'de> serde::de::Deserialize<'de> +pub fn bpfman::types::Direction::from_base64(raw: &Input) -> core::result::Result where Input: core::convert::AsRef<[u8]> + core::marker::Sized +impl jwt::ToBase64 for bpfman::types::Direction where T: serde::ser::Serialize +pub fn bpfman::types::Direction::to_base64(&self) -> core::result::Result, jwt::error::Error> +impl serde::de::DeserializeOwned for bpfman::types::Direction where T: for<'de> serde::de::Deserialize<'de> +impl tracing::instrument::Instrument for bpfman::types::Direction +impl tracing::instrument::WithSubscriber for bpfman::types::Direction +impl typenum::type_operators::Same for bpfman::types::Direction +pub type bpfman::types::Direction::Output = T +impl ppv_lite86::types::VZip for bpfman::types::Direction where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::Direction::vzip(self) -> V +pub enum bpfman::types::ImagePullPolicy +pub bpfman::types::ImagePullPolicy::Always +pub bpfman::types::ImagePullPolicy::IfNotPresent +pub bpfman::types::ImagePullPolicy::Never +impl core::clone::Clone for bpfman::types::ImagePullPolicy +pub fn bpfman::types::ImagePullPolicy::clone(&self) -> bpfman::types::ImagePullPolicy +impl core::convert::From for i32 +pub fn i32::from(value: bpfman::types::ImagePullPolicy) -> Self +impl core::convert::TryFrom<&str> for bpfman::types::ImagePullPolicy +pub type bpfman::types::ImagePullPolicy::Error = bpfman::errors::ParseError +pub fn bpfman::types::ImagePullPolicy::try_from(value: &str) -> core::result::Result +impl core::convert::TryFrom for bpfman::types::ImagePullPolicy +pub type bpfman::types::ImagePullPolicy::Error = bpfman::errors::ParseError +pub fn bpfman::types::ImagePullPolicy::try_from(value: i32) -> core::result::Result +impl core::fmt::Debug for bpfman::types::ImagePullPolicy +pub fn bpfman::types::ImagePullPolicy::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for bpfman::types::ImagePullPolicy +pub fn bpfman::types::ImagePullPolicy::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl serde::ser::Serialize for bpfman::types::ImagePullPolicy +pub fn bpfman::types::ImagePullPolicy::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde::ser::Serializer>::Ok, <__S as serde::ser::Serializer>::Error> where __S: serde::ser::Serializer +impl<'de> serde::de::Deserialize<'de> for bpfman::types::ImagePullPolicy +pub fn bpfman::types::ImagePullPolicy::deserialize<__D>(__deserializer: __D) -> core::result::Result::Error> where __D: serde::de::Deserializer<'de> +impl core::marker::Freeze for bpfman::types::ImagePullPolicy +impl core::marker::Send for bpfman::types::ImagePullPolicy +impl core::marker::Sync for bpfman::types::ImagePullPolicy +impl core::marker::Unpin for bpfman::types::ImagePullPolicy +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::types::ImagePullPolicy +impl core::panic::unwind_safe::UnwindSafe for bpfman::types::ImagePullPolicy +impl jwt::token::signed::SignWithKey for bpfman::types::ImagePullPolicy where C: jwt::ToBase64 +pub fn bpfman::types::ImagePullPolicy::sign_with_key(self, key: &impl jwt::algorithm::SigningAlgorithm) -> core::result::Result +impl core::convert::Into for bpfman::types::ImagePullPolicy where U: core::convert::From +pub fn bpfman::types::ImagePullPolicy::into(self) -> U +impl core::convert::TryFrom for bpfman::types::ImagePullPolicy where U: core::convert::Into +pub type bpfman::types::ImagePullPolicy::Error = core::convert::Infallible +pub fn bpfman::types::ImagePullPolicy::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::ImagePullPolicy where U: core::convert::TryFrom +pub type bpfman::types::ImagePullPolicy::Error = >::Error +pub fn bpfman::types::ImagePullPolicy::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::ImagePullPolicy where T: core::clone::Clone +pub type bpfman::types::ImagePullPolicy::Owned = T +pub fn bpfman::types::ImagePullPolicy::clone_into(&self, target: &mut T) +pub fn bpfman::types::ImagePullPolicy::to_owned(&self) -> T +impl alloc::string::ToString for bpfman::types::ImagePullPolicy where T: core::fmt::Display + core::marker::Sized +pub fn bpfman::types::ImagePullPolicy::to_string(&self) -> alloc::string::String +impl core::any::Any for bpfman::types::ImagePullPolicy where T: 'static + core::marker::Sized +pub fn bpfman::types::ImagePullPolicy::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::ImagePullPolicy where T: core::marker::Sized +pub fn bpfman::types::ImagePullPolicy::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::ImagePullPolicy where T: core::marker::Sized +pub fn bpfman::types::ImagePullPolicy::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::ImagePullPolicy +pub fn bpfman::types::ImagePullPolicy::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::ImagePullPolicy +pub type bpfman::types::ImagePullPolicy::Init = T +pub const bpfman::types::ImagePullPolicy::ALIGN: usize +pub unsafe fn bpfman::types::ImagePullPolicy::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::ImagePullPolicy::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::ImagePullPolicy::drop(ptr: usize) +pub unsafe fn bpfman::types::ImagePullPolicy::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::ImagePullPolicy where T: core::clone::Clone +pub fn bpfman::types::ImagePullPolicy::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl jwt::FromBase64 for bpfman::types::ImagePullPolicy where T: for<'de> serde::de::Deserialize<'de> +pub fn bpfman::types::ImagePullPolicy::from_base64(raw: &Input) -> core::result::Result where Input: core::convert::AsRef<[u8]> + core::marker::Sized +impl jwt::ToBase64 for bpfman::types::ImagePullPolicy where T: serde::ser::Serialize +pub fn bpfman::types::ImagePullPolicy::to_base64(&self) -> core::result::Result, jwt::error::Error> +impl serde::de::DeserializeOwned for bpfman::types::ImagePullPolicy where T: for<'de> serde::de::Deserialize<'de> +impl tracing::instrument::Instrument for bpfman::types::ImagePullPolicy +impl tracing::instrument::WithSubscriber for bpfman::types::ImagePullPolicy +impl typenum::type_operators::Same for bpfman::types::ImagePullPolicy +pub type bpfman::types::ImagePullPolicy::Output = T +impl ppv_lite86::types::VZip for bpfman::types::ImagePullPolicy where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::ImagePullPolicy::vzip(self) -> V +pub enum bpfman::types::Location +pub bpfman::types::Location::File(alloc::string::String) +pub bpfman::types::Location::Image(bpfman::types::BytecodeImage) +impl core::clone::Clone for bpfman::types::Location +pub fn bpfman::types::Location::clone(&self) -> bpfman::types::Location +impl core::fmt::Debug for bpfman::types::Location +pub fn bpfman::types::Location::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for bpfman::types::Location +pub fn bpfman::types::Location::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl serde::ser::Serialize for bpfman::types::Location +pub fn bpfman::types::Location::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde::ser::Serializer>::Ok, <__S as serde::ser::Serializer>::Error> where __S: serde::ser::Serializer +impl<'de> serde::de::Deserialize<'de> for bpfman::types::Location +pub fn bpfman::types::Location::deserialize<__D>(__deserializer: __D) -> core::result::Result::Error> where __D: serde::de::Deserializer<'de> +impl core::marker::Freeze for bpfman::types::Location +impl core::marker::Send for bpfman::types::Location +impl core::marker::Sync for bpfman::types::Location +impl core::marker::Unpin for bpfman::types::Location +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::types::Location +impl core::panic::unwind_safe::UnwindSafe for bpfman::types::Location +impl jwt::token::signed::SignWithKey for bpfman::types::Location where C: jwt::ToBase64 +pub fn bpfman::types::Location::sign_with_key(self, key: &impl jwt::algorithm::SigningAlgorithm) -> core::result::Result +impl core::convert::Into for bpfman::types::Location where U: core::convert::From +pub fn bpfman::types::Location::into(self) -> U +impl core::convert::TryFrom for bpfman::types::Location where U: core::convert::Into +pub type bpfman::types::Location::Error = core::convert::Infallible +pub fn bpfman::types::Location::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::Location where U: core::convert::TryFrom +pub type bpfman::types::Location::Error = >::Error +pub fn bpfman::types::Location::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::Location where T: core::clone::Clone +pub type bpfman::types::Location::Owned = T +pub fn bpfman::types::Location::clone_into(&self, target: &mut T) +pub fn bpfman::types::Location::to_owned(&self) -> T +impl alloc::string::ToString for bpfman::types::Location where T: core::fmt::Display + core::marker::Sized +pub fn bpfman::types::Location::to_string(&self) -> alloc::string::String +impl core::any::Any for bpfman::types::Location where T: 'static + core::marker::Sized +pub fn bpfman::types::Location::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::Location where T: core::marker::Sized +pub fn bpfman::types::Location::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::Location where T: core::marker::Sized +pub fn bpfman::types::Location::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::Location +pub fn bpfman::types::Location::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::Location +pub type bpfman::types::Location::Init = T +pub const bpfman::types::Location::ALIGN: usize +pub unsafe fn bpfman::types::Location::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::Location::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::Location::drop(ptr: usize) +pub unsafe fn bpfman::types::Location::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::Location where T: core::clone::Clone +pub fn bpfman::types::Location::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl jwt::FromBase64 for bpfman::types::Location where T: for<'de> serde::de::Deserialize<'de> +pub fn bpfman::types::Location::from_base64(raw: &Input) -> core::result::Result where Input: core::convert::AsRef<[u8]> + core::marker::Sized +impl jwt::ToBase64 for bpfman::types::Location where T: serde::ser::Serialize +pub fn bpfman::types::Location::to_base64(&self) -> core::result::Result, jwt::error::Error> +impl serde::de::DeserializeOwned for bpfman::types::Location where T: for<'de> serde::de::Deserialize<'de> +impl tracing::instrument::Instrument for bpfman::types::Location +impl tracing::instrument::WithSubscriber for bpfman::types::Location +impl typenum::type_operators::Same for bpfman::types::Location +pub type bpfman::types::Location::Output = T +impl ppv_lite86::types::VZip for bpfman::types::Location where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::Location::vzip(self) -> V +pub enum bpfman::types::ProbeType +pub bpfman::types::ProbeType::Kprobe +pub bpfman::types::ProbeType::Kretprobe +pub bpfman::types::ProbeType::Uprobe +pub bpfman::types::ProbeType::Uretprobe +impl core::clone::Clone for bpfman::types::ProbeType +pub fn bpfman::types::ProbeType::clone(&self) -> bpfman::types::ProbeType +impl core::cmp::Eq for bpfman::types::ProbeType +impl core::cmp::PartialEq for bpfman::types::ProbeType +pub fn bpfman::types::ProbeType::eq(&self, other: &bpfman::types::ProbeType) -> bool +impl core::convert::From for bpfman::types::ProbeType +pub fn bpfman::types::ProbeType::from(value: aya::programs::probe::ProbeKind) -> Self +impl core::convert::TryFrom for bpfman::types::ProbeType +pub type bpfman::types::ProbeType::Error = bpfman::errors::ParseError +pub fn bpfman::types::ProbeType::try_from(value: i32) -> core::result::Result +impl core::fmt::Debug for bpfman::types::ProbeType +pub fn bpfman::types::ProbeType::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for bpfman::types::ProbeType +pub fn bpfman::types::ProbeType::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Copy for bpfman::types::ProbeType +impl core::marker::StructuralPartialEq for bpfman::types::ProbeType +impl serde::ser::Serialize for bpfman::types::ProbeType +pub fn bpfman::types::ProbeType::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde::ser::Serializer>::Ok, <__S as serde::ser::Serializer>::Error> where __S: serde::ser::Serializer +impl<'de> serde::de::Deserialize<'de> for bpfman::types::ProbeType +pub fn bpfman::types::ProbeType::deserialize<__D>(__deserializer: __D) -> core::result::Result::Error> where __D: serde::de::Deserializer<'de> +impl core::marker::Freeze for bpfman::types::ProbeType +impl core::marker::Send for bpfman::types::ProbeType +impl core::marker::Sync for bpfman::types::ProbeType +impl core::marker::Unpin for bpfman::types::ProbeType +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::types::ProbeType +impl core::panic::unwind_safe::UnwindSafe for bpfman::types::ProbeType +impl jwt::token::signed::SignWithKey for bpfman::types::ProbeType where C: jwt::ToBase64 +pub fn bpfman::types::ProbeType::sign_with_key(self, key: &impl jwt::algorithm::SigningAlgorithm) -> core::result::Result +impl equivalent::Equivalent for bpfman::types::ProbeType where Q: core::cmp::Eq + core::marker::Sized, K: core::borrow::Borrow + core::marker::Sized +pub fn bpfman::types::ProbeType::equivalent(&self, key: &K) -> bool +impl hashbrown::Equivalent for bpfman::types::ProbeType where Q: core::cmp::Eq + core::marker::Sized, K: core::borrow::Borrow + core::marker::Sized +pub fn bpfman::types::ProbeType::equivalent(&self, key: &K) -> bool +impl core::convert::Into for bpfman::types::ProbeType where U: core::convert::From +pub fn bpfman::types::ProbeType::into(self) -> U +impl core::convert::TryFrom for bpfman::types::ProbeType where U: core::convert::Into +pub type bpfman::types::ProbeType::Error = core::convert::Infallible +pub fn bpfman::types::ProbeType::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::ProbeType where U: core::convert::TryFrom +pub type bpfman::types::ProbeType::Error = >::Error +pub fn bpfman::types::ProbeType::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::ProbeType where T: core::clone::Clone +pub type bpfman::types::ProbeType::Owned = T +pub fn bpfman::types::ProbeType::clone_into(&self, target: &mut T) +pub fn bpfman::types::ProbeType::to_owned(&self) -> T +impl alloc::string::ToString for bpfman::types::ProbeType where T: core::fmt::Display + core::marker::Sized +pub fn bpfman::types::ProbeType::to_string(&self) -> alloc::string::String +impl core::any::Any for bpfman::types::ProbeType where T: 'static + core::marker::Sized +pub fn bpfman::types::ProbeType::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::ProbeType where T: core::marker::Sized +pub fn bpfman::types::ProbeType::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::ProbeType where T: core::marker::Sized +pub fn bpfman::types::ProbeType::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::ProbeType +pub fn bpfman::types::ProbeType::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::ProbeType +pub type bpfman::types::ProbeType::Init = T +pub const bpfman::types::ProbeType::ALIGN: usize +pub unsafe fn bpfman::types::ProbeType::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::ProbeType::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::ProbeType::drop(ptr: usize) +pub unsafe fn bpfman::types::ProbeType::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::ProbeType where T: core::clone::Clone +pub fn bpfman::types::ProbeType::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl jwt::FromBase64 for bpfman::types::ProbeType where T: for<'de> serde::de::Deserialize<'de> +pub fn bpfman::types::ProbeType::from_base64(raw: &Input) -> core::result::Result where Input: core::convert::AsRef<[u8]> + core::marker::Sized +impl jwt::ToBase64 for bpfman::types::ProbeType where T: serde::ser::Serialize +pub fn bpfman::types::ProbeType::to_base64(&self) -> core::result::Result, jwt::error::Error> +impl serde::de::DeserializeOwned for bpfman::types::ProbeType where T: for<'de> serde::de::Deserialize<'de> +impl tracing::instrument::Instrument for bpfman::types::ProbeType +impl tracing::instrument::WithSubscriber for bpfman::types::ProbeType +impl typenum::type_operators::Same for bpfman::types::ProbeType +pub type bpfman::types::ProbeType::Output = T +impl ppv_lite86::types::VZip for bpfman::types::ProbeType where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::ProbeType::vzip(self) -> V +pub enum bpfman::types::Program +pub bpfman::types::Program::Fentry(bpfman::types::FentryProgram) +pub bpfman::types::Program::Fexit(bpfman::types::FexitProgram) +pub bpfman::types::Program::Kprobe(bpfman::types::KprobeProgram) +pub bpfman::types::Program::Tc(bpfman::types::TcProgram) +pub bpfman::types::Program::Tracepoint(bpfman::types::TracepointProgram) +pub bpfman::types::Program::Unsupported(bpfman::types::ProgramData) +pub bpfman::types::Program::Uprobe(bpfman::types::UprobeProgram) +pub bpfman::types::Program::Xdp(bpfman::types::XdpProgram) +impl bpfman::types::Program +pub fn bpfman::types::Program::get_data(&self) -> &bpfman::types::ProgramData +pub fn bpfman::types::Program::kind(&self) -> bpfman::types::ProgramType +impl core::clone::Clone for bpfman::types::Program +pub fn bpfman::types::Program::clone(&self) -> bpfman::types::Program +impl core::fmt::Debug for bpfman::types::Program +pub fn bpfman::types::Program::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::types::Program +impl core::marker::Send for bpfman::types::Program +impl core::marker::Sync for bpfman::types::Program +impl core::marker::Unpin for bpfman::types::Program +impl !core::panic::unwind_safe::RefUnwindSafe for bpfman::types::Program +impl !core::panic::unwind_safe::UnwindSafe for bpfman::types::Program +impl core::convert::Into for bpfman::types::Program where U: core::convert::From +pub fn bpfman::types::Program::into(self) -> U +impl core::convert::TryFrom for bpfman::types::Program where U: core::convert::Into +pub type bpfman::types::Program::Error = core::convert::Infallible +pub fn bpfman::types::Program::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::Program where U: core::convert::TryFrom +pub type bpfman::types::Program::Error = >::Error +pub fn bpfman::types::Program::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::Program where T: core::clone::Clone +pub type bpfman::types::Program::Owned = T +pub fn bpfman::types::Program::clone_into(&self, target: &mut T) +pub fn bpfman::types::Program::to_owned(&self) -> T +impl core::any::Any for bpfman::types::Program where T: 'static + core::marker::Sized +pub fn bpfman::types::Program::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::Program where T: core::marker::Sized +pub fn bpfman::types::Program::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::Program where T: core::marker::Sized +pub fn bpfman::types::Program::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::Program +pub fn bpfman::types::Program::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::Program +pub type bpfman::types::Program::Init = T +pub const bpfman::types::Program::ALIGN: usize +pub unsafe fn bpfman::types::Program::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::Program::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::Program::drop(ptr: usize) +pub unsafe fn bpfman::types::Program::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::Program where T: core::clone::Clone +pub fn bpfman::types::Program::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl tracing::instrument::Instrument for bpfman::types::Program +impl tracing::instrument::WithSubscriber for bpfman::types::Program +impl typenum::type_operators::Same for bpfman::types::Program +pub type bpfman::types::Program::Output = T +impl ppv_lite86::types::VZip for bpfman::types::Program where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::Program::vzip(self) -> V +pub enum bpfman::types::ProgramType +pub bpfman::types::ProgramType::CgroupDevice +pub bpfman::types::ProgramType::CgroupSkb +pub bpfman::types::ProgramType::CgroupSock +pub bpfman::types::ProgramType::CgroupSockAddr +pub bpfman::types::ProgramType::CgroupSockopt +pub bpfman::types::ProgramType::CgroupSysctl +pub bpfman::types::ProgramType::Ext +pub bpfman::types::ProgramType::FlowDissector +pub bpfman::types::ProgramType::LircMode2 +pub bpfman::types::ProgramType::Lsm +pub bpfman::types::ProgramType::LwtIn +pub bpfman::types::ProgramType::LwtOut +pub bpfman::types::ProgramType::LwtSeg6Local +pub bpfman::types::ProgramType::LwtXmit +pub bpfman::types::ProgramType::PerfEvent +pub bpfman::types::ProgramType::Probe +pub bpfman::types::ProgramType::RawTracepoint +pub bpfman::types::ProgramType::RawTracepointWritable +pub bpfman::types::ProgramType::SchedAct +pub bpfman::types::ProgramType::SkLookup +pub bpfman::types::ProgramType::SkMsg +pub bpfman::types::ProgramType::SkReuseport +pub bpfman::types::ProgramType::SkSkb +pub bpfman::types::ProgramType::SockOps +pub bpfman::types::ProgramType::SocketFilter +pub bpfman::types::ProgramType::StructOps +pub bpfman::types::ProgramType::Syscall +pub bpfman::types::ProgramType::Tc +pub bpfman::types::ProgramType::Tracepoint +pub bpfman::types::ProgramType::Tracing +pub bpfman::types::ProgramType::Unspec +pub bpfman::types::ProgramType::Xdp +impl clap_builder::derive::ValueEnum for bpfman::types::ProgramType +pub fn bpfman::types::ProgramType::to_possible_value<'a>(&self) -> core::option::Option +pub fn bpfman::types::ProgramType::value_variants<'a>() -> &'a [Self] +impl core::clone::Clone for bpfman::types::ProgramType +pub fn bpfman::types::ProgramType::clone(&self) -> bpfman::types::ProgramType +impl core::cmp::Eq for bpfman::types::ProgramType +impl core::cmp::PartialEq for bpfman::types::ProgramType +pub fn bpfman::types::ProgramType::eq(&self, other: &bpfman::types::ProgramType) -> bool +impl core::convert::From for u32 +pub fn u32::from(val: bpfman::types::ProgramType) -> Self +impl core::convert::TryFrom for bpfman::types::ProgramType +pub type bpfman::types::ProgramType::Error = bpfman::errors::ParseError +pub fn bpfman::types::ProgramType::try_from(value: alloc::string::String) -> core::result::Result +impl core::convert::TryFrom for bpfman::types::ProgramType +pub type bpfman::types::ProgramType::Error = bpfman::errors::ParseError +pub fn bpfman::types::ProgramType::try_from(value: u32) -> core::result::Result +impl core::fmt::Debug for bpfman::types::ProgramType +pub fn bpfman::types::ProgramType::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for bpfman::types::ProgramType +pub fn bpfman::types::ProgramType::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Copy for bpfman::types::ProgramType +impl core::marker::StructuralPartialEq for bpfman::types::ProgramType +impl serde::ser::Serialize for bpfman::types::ProgramType +pub fn bpfman::types::ProgramType::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde::ser::Serializer>::Ok, <__S as serde::ser::Serializer>::Error> where __S: serde::ser::Serializer +impl<'de> serde::de::Deserialize<'de> for bpfman::types::ProgramType +pub fn bpfman::types::ProgramType::deserialize<__D>(__deserializer: __D) -> core::result::Result::Error> where __D: serde::de::Deserializer<'de> +impl core::marker::Freeze for bpfman::types::ProgramType +impl core::marker::Send for bpfman::types::ProgramType +impl core::marker::Sync for bpfman::types::ProgramType +impl core::marker::Unpin for bpfman::types::ProgramType +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::types::ProgramType +impl core::panic::unwind_safe::UnwindSafe for bpfman::types::ProgramType +impl jwt::token::signed::SignWithKey for bpfman::types::ProgramType where C: jwt::ToBase64 +pub fn bpfman::types::ProgramType::sign_with_key(self, key: &impl jwt::algorithm::SigningAlgorithm) -> core::result::Result +impl equivalent::Equivalent for bpfman::types::ProgramType where Q: core::cmp::Eq + core::marker::Sized, K: core::borrow::Borrow + core::marker::Sized +pub fn bpfman::types::ProgramType::equivalent(&self, key: &K) -> bool +impl hashbrown::Equivalent for bpfman::types::ProgramType where Q: core::cmp::Eq + core::marker::Sized, K: core::borrow::Borrow + core::marker::Sized +pub fn bpfman::types::ProgramType::equivalent(&self, key: &K) -> bool +impl core::convert::Into for bpfman::types::ProgramType where U: core::convert::From +pub fn bpfman::types::ProgramType::into(self) -> U +impl core::convert::TryFrom for bpfman::types::ProgramType where U: core::convert::Into +pub type bpfman::types::ProgramType::Error = core::convert::Infallible +pub fn bpfman::types::ProgramType::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::ProgramType where U: core::convert::TryFrom +pub type bpfman::types::ProgramType::Error = >::Error +pub fn bpfman::types::ProgramType::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::ProgramType where T: core::clone::Clone +pub type bpfman::types::ProgramType::Owned = T +pub fn bpfman::types::ProgramType::clone_into(&self, target: &mut T) +pub fn bpfman::types::ProgramType::to_owned(&self) -> T +impl alloc::string::ToString for bpfman::types::ProgramType where T: core::fmt::Display + core::marker::Sized +pub fn bpfman::types::ProgramType::to_string(&self) -> alloc::string::String +impl core::any::Any for bpfman::types::ProgramType where T: 'static + core::marker::Sized +pub fn bpfman::types::ProgramType::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::ProgramType where T: core::marker::Sized +pub fn bpfman::types::ProgramType::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::ProgramType where T: core::marker::Sized +pub fn bpfman::types::ProgramType::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::ProgramType +pub fn bpfman::types::ProgramType::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::ProgramType +pub type bpfman::types::ProgramType::Init = T +pub const bpfman::types::ProgramType::ALIGN: usize +pub unsafe fn bpfman::types::ProgramType::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::ProgramType::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::ProgramType::drop(ptr: usize) +pub unsafe fn bpfman::types::ProgramType::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::ProgramType where T: core::clone::Clone +pub fn bpfman::types::ProgramType::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl jwt::FromBase64 for bpfman::types::ProgramType where T: for<'de> serde::de::Deserialize<'de> +pub fn bpfman::types::ProgramType::from_base64(raw: &Input) -> core::result::Result where Input: core::convert::AsRef<[u8]> + core::marker::Sized +impl jwt::ToBase64 for bpfman::types::ProgramType where T: serde::ser::Serialize +pub fn bpfman::types::ProgramType::to_base64(&self) -> core::result::Result, jwt::error::Error> +impl serde::de::DeserializeOwned for bpfman::types::ProgramType where T: for<'de> serde::de::Deserialize<'de> +impl tracing::instrument::Instrument for bpfman::types::ProgramType +impl tracing::instrument::WithSubscriber for bpfman::types::ProgramType +impl typenum::type_operators::Same for bpfman::types::ProgramType +pub type bpfman::types::ProgramType::Output = T +impl ppv_lite86::types::VZip for bpfman::types::ProgramType where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::ProgramType::vzip(self) -> V +pub enum bpfman::types::TcProceedOnEntry +pub bpfman::types::TcProceedOnEntry::DispatcherReturn = 30 +pub bpfman::types::TcProceedOnEntry::Ok = 0 +pub bpfman::types::TcProceedOnEntry::Pipe +pub bpfman::types::TcProceedOnEntry::Queued +pub bpfman::types::TcProceedOnEntry::Reclassify +pub bpfman::types::TcProceedOnEntry::Redirect +pub bpfman::types::TcProceedOnEntry::Repeat +pub bpfman::types::TcProceedOnEntry::Shot +pub bpfman::types::TcProceedOnEntry::Stolen +pub bpfman::types::TcProceedOnEntry::Trap +pub bpfman::types::TcProceedOnEntry::Unspec = -1 +impl core::clone::Clone for bpfman::types::TcProceedOnEntry +pub fn bpfman::types::TcProceedOnEntry::clone(&self) -> bpfman::types::TcProceedOnEntry +impl core::convert::TryFrom for bpfman::types::TcProceedOnEntry +pub type bpfman::types::TcProceedOnEntry::Error = bpfman::errors::ParseError +pub fn bpfman::types::TcProceedOnEntry::try_from(value: alloc::string::String) -> core::result::Result +impl core::convert::TryFrom for bpfman::types::TcProceedOnEntry +pub type bpfman::types::TcProceedOnEntry::Error = bpfman::errors::ParseError +pub fn bpfman::types::TcProceedOnEntry::try_from(value: i32) -> core::result::Result +impl core::fmt::Debug for bpfman::types::TcProceedOnEntry +pub fn bpfman::types::TcProceedOnEntry::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for bpfman::types::TcProceedOnEntry +pub fn bpfman::types::TcProceedOnEntry::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::iter::traits::collect::FromIterator for bpfman::types::TcProceedOn +pub fn bpfman::types::TcProceedOn::from_iter>(iter: I) -> Self +impl core::marker::Copy for bpfman::types::TcProceedOnEntry +impl serde::ser::Serialize for bpfman::types::TcProceedOnEntry +pub fn bpfman::types::TcProceedOnEntry::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde::ser::Serializer>::Ok, <__S as serde::ser::Serializer>::Error> where __S: serde::ser::Serializer +impl<'de> serde::de::Deserialize<'de> for bpfman::types::TcProceedOnEntry +pub fn bpfman::types::TcProceedOnEntry::deserialize<__D>(__deserializer: __D) -> core::result::Result::Error> where __D: serde::de::Deserializer<'de> +impl core::marker::Freeze for bpfman::types::TcProceedOnEntry +impl core::marker::Send for bpfman::types::TcProceedOnEntry +impl core::marker::Sync for bpfman::types::TcProceedOnEntry +impl core::marker::Unpin for bpfman::types::TcProceedOnEntry +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::types::TcProceedOnEntry +impl core::panic::unwind_safe::UnwindSafe for bpfman::types::TcProceedOnEntry +impl jwt::token::signed::SignWithKey for bpfman::types::TcProceedOnEntry where C: jwt::ToBase64 +pub fn bpfman::types::TcProceedOnEntry::sign_with_key(self, key: &impl jwt::algorithm::SigningAlgorithm) -> core::result::Result +impl core::convert::Into for bpfman::types::TcProceedOnEntry where U: core::convert::From +pub fn bpfman::types::TcProceedOnEntry::into(self) -> U +impl core::convert::TryFrom for bpfman::types::TcProceedOnEntry where U: core::convert::Into +pub type bpfman::types::TcProceedOnEntry::Error = core::convert::Infallible +pub fn bpfman::types::TcProceedOnEntry::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::TcProceedOnEntry where U: core::convert::TryFrom +pub type bpfman::types::TcProceedOnEntry::Error = >::Error +pub fn bpfman::types::TcProceedOnEntry::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::TcProceedOnEntry where T: core::clone::Clone +pub type bpfman::types::TcProceedOnEntry::Owned = T +pub fn bpfman::types::TcProceedOnEntry::clone_into(&self, target: &mut T) +pub fn bpfman::types::TcProceedOnEntry::to_owned(&self) -> T +impl alloc::string::ToString for bpfman::types::TcProceedOnEntry where T: core::fmt::Display + core::marker::Sized +pub fn bpfman::types::TcProceedOnEntry::to_string(&self) -> alloc::string::String +impl core::any::Any for bpfman::types::TcProceedOnEntry where T: 'static + core::marker::Sized +pub fn bpfman::types::TcProceedOnEntry::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::TcProceedOnEntry where T: core::marker::Sized +pub fn bpfman::types::TcProceedOnEntry::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::TcProceedOnEntry where T: core::marker::Sized +pub fn bpfman::types::TcProceedOnEntry::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::TcProceedOnEntry +pub fn bpfman::types::TcProceedOnEntry::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::TcProceedOnEntry +pub type bpfman::types::TcProceedOnEntry::Init = T +pub const bpfman::types::TcProceedOnEntry::ALIGN: usize +pub unsafe fn bpfman::types::TcProceedOnEntry::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::TcProceedOnEntry::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::TcProceedOnEntry::drop(ptr: usize) +pub unsafe fn bpfman::types::TcProceedOnEntry::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::TcProceedOnEntry where T: core::clone::Clone +pub fn bpfman::types::TcProceedOnEntry::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl jwt::FromBase64 for bpfman::types::TcProceedOnEntry where T: for<'de> serde::de::Deserialize<'de> +pub fn bpfman::types::TcProceedOnEntry::from_base64(raw: &Input) -> core::result::Result where Input: core::convert::AsRef<[u8]> + core::marker::Sized +impl jwt::ToBase64 for bpfman::types::TcProceedOnEntry where T: serde::ser::Serialize +pub fn bpfman::types::TcProceedOnEntry::to_base64(&self) -> core::result::Result, jwt::error::Error> +impl serde::de::DeserializeOwned for bpfman::types::TcProceedOnEntry where T: for<'de> serde::de::Deserialize<'de> +impl tracing::instrument::Instrument for bpfman::types::TcProceedOnEntry +impl tracing::instrument::WithSubscriber for bpfman::types::TcProceedOnEntry +impl typenum::type_operators::Same for bpfman::types::TcProceedOnEntry +pub type bpfman::types::TcProceedOnEntry::Output = T +impl ppv_lite86::types::VZip for bpfman::types::TcProceedOnEntry where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::TcProceedOnEntry::vzip(self) -> V +pub enum bpfman::types::XdpProceedOnEntry +pub bpfman::types::XdpProceedOnEntry::Aborted +pub bpfman::types::XdpProceedOnEntry::DispatcherReturn = 31 +pub bpfman::types::XdpProceedOnEntry::Drop +pub bpfman::types::XdpProceedOnEntry::Pass +pub bpfman::types::XdpProceedOnEntry::Redirect +pub bpfman::types::XdpProceedOnEntry::Tx +impl core::clone::Clone for bpfman::types::XdpProceedOnEntry +pub fn bpfman::types::XdpProceedOnEntry::clone(&self) -> bpfman::types::XdpProceedOnEntry +impl core::convert::TryFrom for bpfman::types::XdpProceedOnEntry +pub type bpfman::types::XdpProceedOnEntry::Error = bpfman::errors::ParseError +pub fn bpfman::types::XdpProceedOnEntry::try_from(value: alloc::string::String) -> core::result::Result +impl core::convert::TryFrom for bpfman::types::XdpProceedOnEntry +pub type bpfman::types::XdpProceedOnEntry::Error = bpfman::errors::ParseError +pub fn bpfman::types::XdpProceedOnEntry::try_from(value: i32) -> core::result::Result +impl core::fmt::Debug for bpfman::types::XdpProceedOnEntry +pub fn bpfman::types::XdpProceedOnEntry::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for bpfman::types::XdpProceedOnEntry +pub fn bpfman::types::XdpProceedOnEntry::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::iter::traits::collect::FromIterator for bpfman::types::XdpProceedOn +pub fn bpfman::types::XdpProceedOn::from_iter>(iter: I) -> Self +impl core::marker::Copy for bpfman::types::XdpProceedOnEntry +impl serde::ser::Serialize for bpfman::types::XdpProceedOnEntry +pub fn bpfman::types::XdpProceedOnEntry::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde::ser::Serializer>::Ok, <__S as serde::ser::Serializer>::Error> where __S: serde::ser::Serializer +impl<'de> serde::de::Deserialize<'de> for bpfman::types::XdpProceedOnEntry +pub fn bpfman::types::XdpProceedOnEntry::deserialize<__D>(__deserializer: __D) -> core::result::Result::Error> where __D: serde::de::Deserializer<'de> +impl core::marker::Freeze for bpfman::types::XdpProceedOnEntry +impl core::marker::Send for bpfman::types::XdpProceedOnEntry +impl core::marker::Sync for bpfman::types::XdpProceedOnEntry +impl core::marker::Unpin for bpfman::types::XdpProceedOnEntry +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::types::XdpProceedOnEntry +impl core::panic::unwind_safe::UnwindSafe for bpfman::types::XdpProceedOnEntry +impl jwt::token::signed::SignWithKey for bpfman::types::XdpProceedOnEntry where C: jwt::ToBase64 +pub fn bpfman::types::XdpProceedOnEntry::sign_with_key(self, key: &impl jwt::algorithm::SigningAlgorithm) -> core::result::Result +impl core::convert::Into for bpfman::types::XdpProceedOnEntry where U: core::convert::From +pub fn bpfman::types::XdpProceedOnEntry::into(self) -> U +impl core::convert::TryFrom for bpfman::types::XdpProceedOnEntry where U: core::convert::Into +pub type bpfman::types::XdpProceedOnEntry::Error = core::convert::Infallible +pub fn bpfman::types::XdpProceedOnEntry::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::XdpProceedOnEntry where U: core::convert::TryFrom +pub type bpfman::types::XdpProceedOnEntry::Error = >::Error +pub fn bpfman::types::XdpProceedOnEntry::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::XdpProceedOnEntry where T: core::clone::Clone +pub type bpfman::types::XdpProceedOnEntry::Owned = T +pub fn bpfman::types::XdpProceedOnEntry::clone_into(&self, target: &mut T) +pub fn bpfman::types::XdpProceedOnEntry::to_owned(&self) -> T +impl alloc::string::ToString for bpfman::types::XdpProceedOnEntry where T: core::fmt::Display + core::marker::Sized +pub fn bpfman::types::XdpProceedOnEntry::to_string(&self) -> alloc::string::String +impl core::any::Any for bpfman::types::XdpProceedOnEntry where T: 'static + core::marker::Sized +pub fn bpfman::types::XdpProceedOnEntry::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::XdpProceedOnEntry where T: core::marker::Sized +pub fn bpfman::types::XdpProceedOnEntry::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::XdpProceedOnEntry where T: core::marker::Sized +pub fn bpfman::types::XdpProceedOnEntry::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::XdpProceedOnEntry +pub fn bpfman::types::XdpProceedOnEntry::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::XdpProceedOnEntry +pub type bpfman::types::XdpProceedOnEntry::Init = T +pub const bpfman::types::XdpProceedOnEntry::ALIGN: usize +pub unsafe fn bpfman::types::XdpProceedOnEntry::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::XdpProceedOnEntry::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::XdpProceedOnEntry::drop(ptr: usize) +pub unsafe fn bpfman::types::XdpProceedOnEntry::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::XdpProceedOnEntry where T: core::clone::Clone +pub fn bpfman::types::XdpProceedOnEntry::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl jwt::FromBase64 for bpfman::types::XdpProceedOnEntry where T: for<'de> serde::de::Deserialize<'de> +pub fn bpfman::types::XdpProceedOnEntry::from_base64(raw: &Input) -> core::result::Result where Input: core::convert::AsRef<[u8]> + core::marker::Sized +impl jwt::ToBase64 for bpfman::types::XdpProceedOnEntry where T: serde::ser::Serialize +pub fn bpfman::types::XdpProceedOnEntry::to_base64(&self) -> core::result::Result, jwt::error::Error> +impl serde::de::DeserializeOwned for bpfman::types::XdpProceedOnEntry where T: for<'de> serde::de::Deserialize<'de> +impl tracing::instrument::Instrument for bpfman::types::XdpProceedOnEntry +impl tracing::instrument::WithSubscriber for bpfman::types::XdpProceedOnEntry +impl typenum::type_operators::Same for bpfman::types::XdpProceedOnEntry +pub type bpfman::types::XdpProceedOnEntry::Output = T +impl ppv_lite86::types::VZip for bpfman::types::XdpProceedOnEntry where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::XdpProceedOnEntry::vzip(self) -> V +pub struct bpfman::types::BytecodeImage +pub bpfman::types::BytecodeImage::image_pull_policy: bpfman::types::ImagePullPolicy +pub bpfman::types::BytecodeImage::image_url: alloc::string::String +pub bpfman::types::BytecodeImage::password: core::option::Option +pub bpfman::types::BytecodeImage::username: core::option::Option +impl bpfman::types::BytecodeImage +pub fn bpfman::types::BytecodeImage::get_pull_policy(&self) -> &bpfman::types::ImagePullPolicy +pub fn bpfman::types::BytecodeImage::get_url(&self) -> &str +pub fn bpfman::types::BytecodeImage::new(image_url: alloc::string::String, image_pull_policy: i32, username: core::option::Option, password: core::option::Option) -> Self +impl core::clone::Clone for bpfman::types::BytecodeImage +pub fn bpfman::types::BytecodeImage::clone(&self) -> bpfman::types::BytecodeImage +impl core::fmt::Debug for bpfman::types::BytecodeImage +pub fn bpfman::types::BytecodeImage::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl serde::ser::Serialize for bpfman::types::BytecodeImage +pub fn bpfman::types::BytecodeImage::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde::ser::Serializer>::Ok, <__S as serde::ser::Serializer>::Error> where __S: serde::ser::Serializer +impl<'de> serde::de::Deserialize<'de> for bpfman::types::BytecodeImage +pub fn bpfman::types::BytecodeImage::deserialize<__D>(__deserializer: __D) -> core::result::Result::Error> where __D: serde::de::Deserializer<'de> +impl core::marker::Freeze for bpfman::types::BytecodeImage +impl core::marker::Send for bpfman::types::BytecodeImage +impl core::marker::Sync for bpfman::types::BytecodeImage +impl core::marker::Unpin for bpfman::types::BytecodeImage +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::types::BytecodeImage +impl core::panic::unwind_safe::UnwindSafe for bpfman::types::BytecodeImage +impl jwt::token::signed::SignWithKey for bpfman::types::BytecodeImage where C: jwt::ToBase64 +pub fn bpfman::types::BytecodeImage::sign_with_key(self, key: &impl jwt::algorithm::SigningAlgorithm) -> core::result::Result +impl core::convert::Into for bpfman::types::BytecodeImage where U: core::convert::From +pub fn bpfman::types::BytecodeImage::into(self) -> U +impl core::convert::TryFrom for bpfman::types::BytecodeImage where U: core::convert::Into +pub type bpfman::types::BytecodeImage::Error = core::convert::Infallible +pub fn bpfman::types::BytecodeImage::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::BytecodeImage where U: core::convert::TryFrom +pub type bpfman::types::BytecodeImage::Error = >::Error +pub fn bpfman::types::BytecodeImage::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::BytecodeImage where T: core::clone::Clone +pub type bpfman::types::BytecodeImage::Owned = T +pub fn bpfman::types::BytecodeImage::clone_into(&self, target: &mut T) +pub fn bpfman::types::BytecodeImage::to_owned(&self) -> T +impl core::any::Any for bpfman::types::BytecodeImage where T: 'static + core::marker::Sized +pub fn bpfman::types::BytecodeImage::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::BytecodeImage where T: core::marker::Sized +pub fn bpfman::types::BytecodeImage::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::BytecodeImage where T: core::marker::Sized +pub fn bpfman::types::BytecodeImage::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::BytecodeImage +pub fn bpfman::types::BytecodeImage::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::BytecodeImage +pub type bpfman::types::BytecodeImage::Init = T +pub const bpfman::types::BytecodeImage::ALIGN: usize +pub unsafe fn bpfman::types::BytecodeImage::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::BytecodeImage::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::BytecodeImage::drop(ptr: usize) +pub unsafe fn bpfman::types::BytecodeImage::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::BytecodeImage where T: core::clone::Clone +pub fn bpfman::types::BytecodeImage::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl jwt::FromBase64 for bpfman::types::BytecodeImage where T: for<'de> serde::de::Deserialize<'de> +pub fn bpfman::types::BytecodeImage::from_base64(raw: &Input) -> core::result::Result where Input: core::convert::AsRef<[u8]> + core::marker::Sized +impl jwt::ToBase64 for bpfman::types::BytecodeImage where T: serde::ser::Serialize +pub fn bpfman::types::BytecodeImage::to_base64(&self) -> core::result::Result, jwt::error::Error> +impl serde::de::DeserializeOwned for bpfman::types::BytecodeImage where T: for<'de> serde::de::Deserialize<'de> +impl tracing::instrument::Instrument for bpfman::types::BytecodeImage +impl tracing::instrument::WithSubscriber for bpfman::types::BytecodeImage +impl typenum::type_operators::Same for bpfman::types::BytecodeImage +pub type bpfman::types::BytecodeImage::Output = T +impl ppv_lite86::types::VZip for bpfman::types::BytecodeImage where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::BytecodeImage::vzip(self) -> V +pub struct bpfman::types::FentryProgram +impl bpfman::types::FentryProgram +pub fn bpfman::types::FentryProgram::get_fn_name(&self) -> core::result::Result +pub fn bpfman::types::FentryProgram::new(data: bpfman::types::ProgramData, fn_name: alloc::string::String) -> core::result::Result +impl core::clone::Clone for bpfman::types::FentryProgram +pub fn bpfman::types::FentryProgram::clone(&self) -> bpfman::types::FentryProgram +impl core::fmt::Debug for bpfman::types::FentryProgram +pub fn bpfman::types::FentryProgram::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::types::FentryProgram +impl core::marker::Send for bpfman::types::FentryProgram +impl core::marker::Sync for bpfman::types::FentryProgram +impl core::marker::Unpin for bpfman::types::FentryProgram +impl !core::panic::unwind_safe::RefUnwindSafe for bpfman::types::FentryProgram +impl !core::panic::unwind_safe::UnwindSafe for bpfman::types::FentryProgram +impl core::convert::Into for bpfman::types::FentryProgram where U: core::convert::From +pub fn bpfman::types::FentryProgram::into(self) -> U +impl core::convert::TryFrom for bpfman::types::FentryProgram where U: core::convert::Into +pub type bpfman::types::FentryProgram::Error = core::convert::Infallible +pub fn bpfman::types::FentryProgram::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::FentryProgram where U: core::convert::TryFrom +pub type bpfman::types::FentryProgram::Error = >::Error +pub fn bpfman::types::FentryProgram::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::FentryProgram where T: core::clone::Clone +pub type bpfman::types::FentryProgram::Owned = T +pub fn bpfman::types::FentryProgram::clone_into(&self, target: &mut T) +pub fn bpfman::types::FentryProgram::to_owned(&self) -> T +impl core::any::Any for bpfman::types::FentryProgram where T: 'static + core::marker::Sized +pub fn bpfman::types::FentryProgram::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::FentryProgram where T: core::marker::Sized +pub fn bpfman::types::FentryProgram::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::FentryProgram where T: core::marker::Sized +pub fn bpfman::types::FentryProgram::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::FentryProgram +pub fn bpfman::types::FentryProgram::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::FentryProgram +pub type bpfman::types::FentryProgram::Init = T +pub const bpfman::types::FentryProgram::ALIGN: usize +pub unsafe fn bpfman::types::FentryProgram::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::FentryProgram::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::FentryProgram::drop(ptr: usize) +pub unsafe fn bpfman::types::FentryProgram::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::FentryProgram where T: core::clone::Clone +pub fn bpfman::types::FentryProgram::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl tracing::instrument::Instrument for bpfman::types::FentryProgram +impl tracing::instrument::WithSubscriber for bpfman::types::FentryProgram +impl typenum::type_operators::Same for bpfman::types::FentryProgram +pub type bpfman::types::FentryProgram::Output = T +impl ppv_lite86::types::VZip for bpfman::types::FentryProgram where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::FentryProgram::vzip(self) -> V +pub struct bpfman::types::FexitProgram +impl bpfman::types::FexitProgram +pub fn bpfman::types::FexitProgram::get_fn_name(&self) -> core::result::Result +pub fn bpfman::types::FexitProgram::new(data: bpfman::types::ProgramData, fn_name: alloc::string::String) -> core::result::Result +impl core::clone::Clone for bpfman::types::FexitProgram +pub fn bpfman::types::FexitProgram::clone(&self) -> bpfman::types::FexitProgram +impl core::fmt::Debug for bpfman::types::FexitProgram +pub fn bpfman::types::FexitProgram::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::types::FexitProgram +impl core::marker::Send for bpfman::types::FexitProgram +impl core::marker::Sync for bpfman::types::FexitProgram +impl core::marker::Unpin for bpfman::types::FexitProgram +impl !core::panic::unwind_safe::RefUnwindSafe for bpfman::types::FexitProgram +impl !core::panic::unwind_safe::UnwindSafe for bpfman::types::FexitProgram +impl core::convert::Into for bpfman::types::FexitProgram where U: core::convert::From +pub fn bpfman::types::FexitProgram::into(self) -> U +impl core::convert::TryFrom for bpfman::types::FexitProgram where U: core::convert::Into +pub type bpfman::types::FexitProgram::Error = core::convert::Infallible +pub fn bpfman::types::FexitProgram::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::FexitProgram where U: core::convert::TryFrom +pub type bpfman::types::FexitProgram::Error = >::Error +pub fn bpfman::types::FexitProgram::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::FexitProgram where T: core::clone::Clone +pub type bpfman::types::FexitProgram::Owned = T +pub fn bpfman::types::FexitProgram::clone_into(&self, target: &mut T) +pub fn bpfman::types::FexitProgram::to_owned(&self) -> T +impl core::any::Any for bpfman::types::FexitProgram where T: 'static + core::marker::Sized +pub fn bpfman::types::FexitProgram::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::FexitProgram where T: core::marker::Sized +pub fn bpfman::types::FexitProgram::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::FexitProgram where T: core::marker::Sized +pub fn bpfman::types::FexitProgram::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::FexitProgram +pub fn bpfman::types::FexitProgram::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::FexitProgram +pub type bpfman::types::FexitProgram::Init = T +pub const bpfman::types::FexitProgram::ALIGN: usize +pub unsafe fn bpfman::types::FexitProgram::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::FexitProgram::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::FexitProgram::drop(ptr: usize) +pub unsafe fn bpfman::types::FexitProgram::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::FexitProgram where T: core::clone::Clone +pub fn bpfman::types::FexitProgram::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl tracing::instrument::Instrument for bpfman::types::FexitProgram +impl tracing::instrument::WithSubscriber for bpfman::types::FexitProgram +impl typenum::type_operators::Same for bpfman::types::FexitProgram +pub type bpfman::types::FexitProgram::Output = T +impl ppv_lite86::types::VZip for bpfman::types::FexitProgram where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::FexitProgram::vzip(self) -> V +pub struct bpfman::types::KprobeProgram +impl bpfman::types::KprobeProgram +pub fn bpfman::types::KprobeProgram::get_container_pid(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::KprobeProgram::get_fn_name(&self) -> core::result::Result +pub fn bpfman::types::KprobeProgram::get_offset(&self) -> core::result::Result +pub fn bpfman::types::KprobeProgram::get_retprobe(&self) -> core::result::Result +pub fn bpfman::types::KprobeProgram::new(data: bpfman::types::ProgramData, fn_name: alloc::string::String, offset: u64, retprobe: bool, container_pid: core::option::Option) -> core::result::Result +impl core::clone::Clone for bpfman::types::KprobeProgram +pub fn bpfman::types::KprobeProgram::clone(&self) -> bpfman::types::KprobeProgram +impl core::fmt::Debug for bpfman::types::KprobeProgram +pub fn bpfman::types::KprobeProgram::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::types::KprobeProgram +impl core::marker::Send for bpfman::types::KprobeProgram +impl core::marker::Sync for bpfman::types::KprobeProgram +impl core::marker::Unpin for bpfman::types::KprobeProgram +impl !core::panic::unwind_safe::RefUnwindSafe for bpfman::types::KprobeProgram +impl !core::panic::unwind_safe::UnwindSafe for bpfman::types::KprobeProgram +impl core::convert::Into for bpfman::types::KprobeProgram where U: core::convert::From +pub fn bpfman::types::KprobeProgram::into(self) -> U +impl core::convert::TryFrom for bpfman::types::KprobeProgram where U: core::convert::Into +pub type bpfman::types::KprobeProgram::Error = core::convert::Infallible +pub fn bpfman::types::KprobeProgram::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::KprobeProgram where U: core::convert::TryFrom +pub type bpfman::types::KprobeProgram::Error = >::Error +pub fn bpfman::types::KprobeProgram::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::KprobeProgram where T: core::clone::Clone +pub type bpfman::types::KprobeProgram::Owned = T +pub fn bpfman::types::KprobeProgram::clone_into(&self, target: &mut T) +pub fn bpfman::types::KprobeProgram::to_owned(&self) -> T +impl core::any::Any for bpfman::types::KprobeProgram where T: 'static + core::marker::Sized +pub fn bpfman::types::KprobeProgram::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::KprobeProgram where T: core::marker::Sized +pub fn bpfman::types::KprobeProgram::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::KprobeProgram where T: core::marker::Sized +pub fn bpfman::types::KprobeProgram::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::KprobeProgram +pub fn bpfman::types::KprobeProgram::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::KprobeProgram +pub type bpfman::types::KprobeProgram::Init = T +pub const bpfman::types::KprobeProgram::ALIGN: usize +pub unsafe fn bpfman::types::KprobeProgram::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::KprobeProgram::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::KprobeProgram::drop(ptr: usize) +pub unsafe fn bpfman::types::KprobeProgram::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::KprobeProgram where T: core::clone::Clone +pub fn bpfman::types::KprobeProgram::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl tracing::instrument::Instrument for bpfman::types::KprobeProgram +impl tracing::instrument::WithSubscriber for bpfman::types::KprobeProgram +impl typenum::type_operators::Same for bpfman::types::KprobeProgram +pub type bpfman::types::KprobeProgram::Output = T +impl ppv_lite86::types::VZip for bpfman::types::KprobeProgram where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::KprobeProgram::vzip(self) -> V +pub struct bpfman::types::ListFilter +impl bpfman::types::ListFilter +pub fn bpfman::types::ListFilter::new(program_type: core::option::Option, metadata_selector: std::collections::hash::map::HashMap, bpfman_programs_only: bool) -> Self +impl core::clone::Clone for bpfman::types::ListFilter +pub fn bpfman::types::ListFilter::clone(&self) -> bpfman::types::ListFilter +impl core::default::Default for bpfman::types::ListFilter +pub fn bpfman::types::ListFilter::default() -> bpfman::types::ListFilter +impl core::fmt::Debug for bpfman::types::ListFilter +pub fn bpfman::types::ListFilter::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::types::ListFilter +impl core::marker::Send for bpfman::types::ListFilter +impl core::marker::Sync for bpfman::types::ListFilter +impl core::marker::Unpin for bpfman::types::ListFilter +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::types::ListFilter +impl core::panic::unwind_safe::UnwindSafe for bpfman::types::ListFilter +impl core::convert::Into for bpfman::types::ListFilter where U: core::convert::From +pub fn bpfman::types::ListFilter::into(self) -> U +impl core::convert::TryFrom for bpfman::types::ListFilter where U: core::convert::Into +pub type bpfman::types::ListFilter::Error = core::convert::Infallible +pub fn bpfman::types::ListFilter::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::ListFilter where U: core::convert::TryFrom +pub type bpfman::types::ListFilter::Error = >::Error +pub fn bpfman::types::ListFilter::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::ListFilter where T: core::clone::Clone +pub type bpfman::types::ListFilter::Owned = T +pub fn bpfman::types::ListFilter::clone_into(&self, target: &mut T) +pub fn bpfman::types::ListFilter::to_owned(&self) -> T +impl core::any::Any for bpfman::types::ListFilter where T: 'static + core::marker::Sized +pub fn bpfman::types::ListFilter::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::ListFilter where T: core::marker::Sized +pub fn bpfman::types::ListFilter::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::ListFilter where T: core::marker::Sized +pub fn bpfman::types::ListFilter::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::ListFilter +pub fn bpfman::types::ListFilter::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::ListFilter +pub type bpfman::types::ListFilter::Init = T +pub const bpfman::types::ListFilter::ALIGN: usize +pub unsafe fn bpfman::types::ListFilter::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::ListFilter::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::ListFilter::drop(ptr: usize) +pub unsafe fn bpfman::types::ListFilter::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::ListFilter where T: core::clone::Clone +pub fn bpfman::types::ListFilter::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl tracing::instrument::Instrument for bpfman::types::ListFilter +impl tracing::instrument::WithSubscriber for bpfman::types::ListFilter +impl typenum::type_operators::Same for bpfman::types::ListFilter +pub type bpfman::types::ListFilter::Output = T +impl ppv_lite86::types::VZip for bpfman::types::ListFilter where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::ListFilter::vzip(self) -> V +pub struct bpfman::types::ProgramData +impl bpfman::types::ProgramData +pub fn bpfman::types::ProgramData::get_global_data(&self) -> core::result::Result>, bpfman::errors::BpfmanError> +pub fn bpfman::types::ProgramData::get_id(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kernel_btf_id(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kernel_bytes_jited(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kernel_bytes_memlock(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kernel_bytes_xlated(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kernel_gpl_compatible(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kernel_jited(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kernel_loaded_at(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kernel_map_ids(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::ProgramData::get_kernel_name(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kernel_program_type(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kernel_tag(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kernel_verified_insns(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_kind(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::ProgramData::get_location(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::get_map_owner_id(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::ProgramData::get_map_pin_path(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::ProgramData::get_maps_used_by(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::ProgramData::get_metadata(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::ProgramData::get_name(&self) -> core::result::Result +pub fn bpfman::types::ProgramData::new(location: bpfman::types::Location, name: alloc::string::String, metadata: std::collections::hash::map::HashMap, global_data: std::collections::hash::map::HashMap>, map_owner_id: core::option::Option) -> core::result::Result +impl core::clone::Clone for bpfman::types::ProgramData +pub fn bpfman::types::ProgramData::clone(&self) -> bpfman::types::ProgramData +impl core::fmt::Debug for bpfman::types::ProgramData +pub fn bpfman::types::ProgramData::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::types::ProgramData +impl core::marker::Send for bpfman::types::ProgramData +impl core::marker::Sync for bpfman::types::ProgramData +impl core::marker::Unpin for bpfman::types::ProgramData +impl !core::panic::unwind_safe::RefUnwindSafe for bpfman::types::ProgramData +impl !core::panic::unwind_safe::UnwindSafe for bpfman::types::ProgramData +impl core::convert::Into for bpfman::types::ProgramData where U: core::convert::From +pub fn bpfman::types::ProgramData::into(self) -> U +impl core::convert::TryFrom for bpfman::types::ProgramData where U: core::convert::Into +pub type bpfman::types::ProgramData::Error = core::convert::Infallible +pub fn bpfman::types::ProgramData::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::ProgramData where U: core::convert::TryFrom +pub type bpfman::types::ProgramData::Error = >::Error +pub fn bpfman::types::ProgramData::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::ProgramData where T: core::clone::Clone +pub type bpfman::types::ProgramData::Owned = T +pub fn bpfman::types::ProgramData::clone_into(&self, target: &mut T) +pub fn bpfman::types::ProgramData::to_owned(&self) -> T +impl core::any::Any for bpfman::types::ProgramData where T: 'static + core::marker::Sized +pub fn bpfman::types::ProgramData::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::ProgramData where T: core::marker::Sized +pub fn bpfman::types::ProgramData::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::ProgramData where T: core::marker::Sized +pub fn bpfman::types::ProgramData::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::ProgramData +pub fn bpfman::types::ProgramData::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::ProgramData +pub type bpfman::types::ProgramData::Init = T +pub const bpfman::types::ProgramData::ALIGN: usize +pub unsafe fn bpfman::types::ProgramData::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::ProgramData::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::ProgramData::drop(ptr: usize) +pub unsafe fn bpfman::types::ProgramData::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::ProgramData where T: core::clone::Clone +pub fn bpfman::types::ProgramData::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl tracing::instrument::Instrument for bpfman::types::ProgramData +impl tracing::instrument::WithSubscriber for bpfman::types::ProgramData +impl typenum::type_operators::Same for bpfman::types::ProgramData +pub type bpfman::types::ProgramData::Output = T +impl ppv_lite86::types::VZip for bpfman::types::ProgramData where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::ProgramData::vzip(self) -> V +pub struct bpfman::types::TcProceedOn(_) +impl bpfman::types::TcProceedOn +pub fn bpfman::types::TcProceedOn::as_action_vec(&self) -> alloc::vec::Vec +pub fn bpfman::types::TcProceedOn::from_int32s>(values: T) -> core::result::Result +pub fn bpfman::types::TcProceedOn::from_strings>(values: T) -> core::result::Result +pub fn bpfman::types::TcProceedOn::is_empty(&self) -> bool +pub fn bpfman::types::TcProceedOn::mask(&self) -> u32 +impl core::clone::Clone for bpfman::types::TcProceedOn +pub fn bpfman::types::TcProceedOn::clone(&self) -> bpfman::types::TcProceedOn +impl core::default::Default for bpfman::types::TcProceedOn +pub fn bpfman::types::TcProceedOn::default() -> Self +impl core::fmt::Debug for bpfman::types::TcProceedOn +pub fn bpfman::types::TcProceedOn::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for bpfman::types::TcProceedOn +pub fn bpfman::types::TcProceedOn::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::iter::traits::collect::FromIterator for bpfman::types::TcProceedOn +pub fn bpfman::types::TcProceedOn::from_iter>(iter: I) -> Self +impl serde::ser::Serialize for bpfman::types::TcProceedOn +pub fn bpfman::types::TcProceedOn::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde::ser::Serializer>::Ok, <__S as serde::ser::Serializer>::Error> where __S: serde::ser::Serializer +impl<'de> serde::de::Deserialize<'de> for bpfman::types::TcProceedOn +pub fn bpfman::types::TcProceedOn::deserialize<__D>(__deserializer: __D) -> core::result::Result::Error> where __D: serde::de::Deserializer<'de> +impl core::marker::Freeze for bpfman::types::TcProceedOn +impl core::marker::Send for bpfman::types::TcProceedOn +impl core::marker::Sync for bpfman::types::TcProceedOn +impl core::marker::Unpin for bpfman::types::TcProceedOn +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::types::TcProceedOn +impl core::panic::unwind_safe::UnwindSafe for bpfman::types::TcProceedOn +impl jwt::token::signed::SignWithKey for bpfman::types::TcProceedOn where C: jwt::ToBase64 +pub fn bpfman::types::TcProceedOn::sign_with_key(self, key: &impl jwt::algorithm::SigningAlgorithm) -> core::result::Result +impl core::convert::Into for bpfman::types::TcProceedOn where U: core::convert::From +pub fn bpfman::types::TcProceedOn::into(self) -> U +impl core::convert::TryFrom for bpfman::types::TcProceedOn where U: core::convert::Into +pub type bpfman::types::TcProceedOn::Error = core::convert::Infallible +pub fn bpfman::types::TcProceedOn::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::TcProceedOn where U: core::convert::TryFrom +pub type bpfman::types::TcProceedOn::Error = >::Error +pub fn bpfman::types::TcProceedOn::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::TcProceedOn where T: core::clone::Clone +pub type bpfman::types::TcProceedOn::Owned = T +pub fn bpfman::types::TcProceedOn::clone_into(&self, target: &mut T) +pub fn bpfman::types::TcProceedOn::to_owned(&self) -> T +impl alloc::string::ToString for bpfman::types::TcProceedOn where T: core::fmt::Display + core::marker::Sized +pub fn bpfman::types::TcProceedOn::to_string(&self) -> alloc::string::String +impl core::any::Any for bpfman::types::TcProceedOn where T: 'static + core::marker::Sized +pub fn bpfman::types::TcProceedOn::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::TcProceedOn where T: core::marker::Sized +pub fn bpfman::types::TcProceedOn::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::TcProceedOn where T: core::marker::Sized +pub fn bpfman::types::TcProceedOn::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::TcProceedOn +pub fn bpfman::types::TcProceedOn::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::TcProceedOn +pub type bpfman::types::TcProceedOn::Init = T +pub const bpfman::types::TcProceedOn::ALIGN: usize +pub unsafe fn bpfman::types::TcProceedOn::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::TcProceedOn::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::TcProceedOn::drop(ptr: usize) +pub unsafe fn bpfman::types::TcProceedOn::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::TcProceedOn where T: core::clone::Clone +pub fn bpfman::types::TcProceedOn::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl jwt::FromBase64 for bpfman::types::TcProceedOn where T: for<'de> serde::de::Deserialize<'de> +pub fn bpfman::types::TcProceedOn::from_base64(raw: &Input) -> core::result::Result where Input: core::convert::AsRef<[u8]> + core::marker::Sized +impl jwt::ToBase64 for bpfman::types::TcProceedOn where T: serde::ser::Serialize +pub fn bpfman::types::TcProceedOn::to_base64(&self) -> core::result::Result, jwt::error::Error> +impl serde::de::DeserializeOwned for bpfman::types::TcProceedOn where T: for<'de> serde::de::Deserialize<'de> +impl tracing::instrument::Instrument for bpfman::types::TcProceedOn +impl tracing::instrument::WithSubscriber for bpfman::types::TcProceedOn +impl typenum::type_operators::Same for bpfman::types::TcProceedOn +pub type bpfman::types::TcProceedOn::Output = T +impl ppv_lite86::types::VZip for bpfman::types::TcProceedOn where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::TcProceedOn::vzip(self) -> V +pub struct bpfman::types::TcProgram +impl bpfman::types::TcProgram +pub fn bpfman::types::TcProgram::get_attached(&self) -> core::result::Result +pub fn bpfman::types::TcProgram::get_current_position(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::TcProgram::get_direction(&self) -> core::result::Result +pub fn bpfman::types::TcProgram::get_if_index(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::TcProgram::get_iface(&self) -> core::result::Result +pub fn bpfman::types::TcProgram::get_priority(&self) -> core::result::Result +pub fn bpfman::types::TcProgram::get_proceed_on(&self) -> core::result::Result +pub fn bpfman::types::TcProgram::new(data: bpfman::types::ProgramData, priority: i32, iface: alloc::string::String, proceed_on: bpfman::types::TcProceedOn, direction: bpfman::types::Direction) -> core::result::Result +impl core::clone::Clone for bpfman::types::TcProgram +pub fn bpfman::types::TcProgram::clone(&self) -> bpfman::types::TcProgram +impl core::fmt::Debug for bpfman::types::TcProgram +pub fn bpfman::types::TcProgram::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::types::TcProgram +impl core::marker::Send for bpfman::types::TcProgram +impl core::marker::Sync for bpfman::types::TcProgram +impl core::marker::Unpin for bpfman::types::TcProgram +impl !core::panic::unwind_safe::RefUnwindSafe for bpfman::types::TcProgram +impl !core::panic::unwind_safe::UnwindSafe for bpfman::types::TcProgram +impl core::convert::Into for bpfman::types::TcProgram where U: core::convert::From +pub fn bpfman::types::TcProgram::into(self) -> U +impl core::convert::TryFrom for bpfman::types::TcProgram where U: core::convert::Into +pub type bpfman::types::TcProgram::Error = core::convert::Infallible +pub fn bpfman::types::TcProgram::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::TcProgram where U: core::convert::TryFrom +pub type bpfman::types::TcProgram::Error = >::Error +pub fn bpfman::types::TcProgram::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::TcProgram where T: core::clone::Clone +pub type bpfman::types::TcProgram::Owned = T +pub fn bpfman::types::TcProgram::clone_into(&self, target: &mut T) +pub fn bpfman::types::TcProgram::to_owned(&self) -> T +impl core::any::Any for bpfman::types::TcProgram where T: 'static + core::marker::Sized +pub fn bpfman::types::TcProgram::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::TcProgram where T: core::marker::Sized +pub fn bpfman::types::TcProgram::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::TcProgram where T: core::marker::Sized +pub fn bpfman::types::TcProgram::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::TcProgram +pub fn bpfman::types::TcProgram::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::TcProgram +pub type bpfman::types::TcProgram::Init = T +pub const bpfman::types::TcProgram::ALIGN: usize +pub unsafe fn bpfman::types::TcProgram::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::TcProgram::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::TcProgram::drop(ptr: usize) +pub unsafe fn bpfman::types::TcProgram::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::TcProgram where T: core::clone::Clone +pub fn bpfman::types::TcProgram::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl tracing::instrument::Instrument for bpfman::types::TcProgram +impl tracing::instrument::WithSubscriber for bpfman::types::TcProgram +impl typenum::type_operators::Same for bpfman::types::TcProgram +pub type bpfman::types::TcProgram::Output = T +impl ppv_lite86::types::VZip for bpfman::types::TcProgram where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::TcProgram::vzip(self) -> V +pub struct bpfman::types::TracepointProgram +impl bpfman::types::TracepointProgram +pub fn bpfman::types::TracepointProgram::get_tracepoint(&self) -> core::result::Result +pub fn bpfman::types::TracepointProgram::new(data: bpfman::types::ProgramData, tracepoint: alloc::string::String) -> core::result::Result +impl core::clone::Clone for bpfman::types::TracepointProgram +pub fn bpfman::types::TracepointProgram::clone(&self) -> bpfman::types::TracepointProgram +impl core::fmt::Debug for bpfman::types::TracepointProgram +pub fn bpfman::types::TracepointProgram::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::types::TracepointProgram +impl core::marker::Send for bpfman::types::TracepointProgram +impl core::marker::Sync for bpfman::types::TracepointProgram +impl core::marker::Unpin for bpfman::types::TracepointProgram +impl !core::panic::unwind_safe::RefUnwindSafe for bpfman::types::TracepointProgram +impl !core::panic::unwind_safe::UnwindSafe for bpfman::types::TracepointProgram +impl core::convert::Into for bpfman::types::TracepointProgram where U: core::convert::From +pub fn bpfman::types::TracepointProgram::into(self) -> U +impl core::convert::TryFrom for bpfman::types::TracepointProgram where U: core::convert::Into +pub type bpfman::types::TracepointProgram::Error = core::convert::Infallible +pub fn bpfman::types::TracepointProgram::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::TracepointProgram where U: core::convert::TryFrom +pub type bpfman::types::TracepointProgram::Error = >::Error +pub fn bpfman::types::TracepointProgram::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::TracepointProgram where T: core::clone::Clone +pub type bpfman::types::TracepointProgram::Owned = T +pub fn bpfman::types::TracepointProgram::clone_into(&self, target: &mut T) +pub fn bpfman::types::TracepointProgram::to_owned(&self) -> T +impl core::any::Any for bpfman::types::TracepointProgram where T: 'static + core::marker::Sized +pub fn bpfman::types::TracepointProgram::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::TracepointProgram where T: core::marker::Sized +pub fn bpfman::types::TracepointProgram::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::TracepointProgram where T: core::marker::Sized +pub fn bpfman::types::TracepointProgram::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::TracepointProgram +pub fn bpfman::types::TracepointProgram::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::TracepointProgram +pub type bpfman::types::TracepointProgram::Init = T +pub const bpfman::types::TracepointProgram::ALIGN: usize +pub unsafe fn bpfman::types::TracepointProgram::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::TracepointProgram::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::TracepointProgram::drop(ptr: usize) +pub unsafe fn bpfman::types::TracepointProgram::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::TracepointProgram where T: core::clone::Clone +pub fn bpfman::types::TracepointProgram::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl tracing::instrument::Instrument for bpfman::types::TracepointProgram +impl tracing::instrument::WithSubscriber for bpfman::types::TracepointProgram +impl typenum::type_operators::Same for bpfman::types::TracepointProgram +pub type bpfman::types::TracepointProgram::Output = T +impl ppv_lite86::types::VZip for bpfman::types::TracepointProgram where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::TracepointProgram::vzip(self) -> V +pub struct bpfman::types::UprobeProgram +impl bpfman::types::UprobeProgram +pub fn bpfman::types::UprobeProgram::get_container_pid(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::UprobeProgram::get_fn_name(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::UprobeProgram::get_offset(&self) -> core::result::Result +pub fn bpfman::types::UprobeProgram::get_pid(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::UprobeProgram::get_retprobe(&self) -> core::result::Result +pub fn bpfman::types::UprobeProgram::get_target(&self) -> core::result::Result +pub fn bpfman::types::UprobeProgram::new(data: bpfman::types::ProgramData, fn_name: core::option::Option, offset: u64, target: alloc::string::String, retprobe: bool, pid: core::option::Option, container_pid: core::option::Option) -> core::result::Result +impl core::clone::Clone for bpfman::types::UprobeProgram +pub fn bpfman::types::UprobeProgram::clone(&self) -> bpfman::types::UprobeProgram +impl core::fmt::Debug for bpfman::types::UprobeProgram +pub fn bpfman::types::UprobeProgram::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::types::UprobeProgram +impl core::marker::Send for bpfman::types::UprobeProgram +impl core::marker::Sync for bpfman::types::UprobeProgram +impl core::marker::Unpin for bpfman::types::UprobeProgram +impl !core::panic::unwind_safe::RefUnwindSafe for bpfman::types::UprobeProgram +impl !core::panic::unwind_safe::UnwindSafe for bpfman::types::UprobeProgram +impl core::convert::Into for bpfman::types::UprobeProgram where U: core::convert::From +pub fn bpfman::types::UprobeProgram::into(self) -> U +impl core::convert::TryFrom for bpfman::types::UprobeProgram where U: core::convert::Into +pub type bpfman::types::UprobeProgram::Error = core::convert::Infallible +pub fn bpfman::types::UprobeProgram::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::UprobeProgram where U: core::convert::TryFrom +pub type bpfman::types::UprobeProgram::Error = >::Error +pub fn bpfman::types::UprobeProgram::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::UprobeProgram where T: core::clone::Clone +pub type bpfman::types::UprobeProgram::Owned = T +pub fn bpfman::types::UprobeProgram::clone_into(&self, target: &mut T) +pub fn bpfman::types::UprobeProgram::to_owned(&self) -> T +impl core::any::Any for bpfman::types::UprobeProgram where T: 'static + core::marker::Sized +pub fn bpfman::types::UprobeProgram::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::UprobeProgram where T: core::marker::Sized +pub fn bpfman::types::UprobeProgram::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::UprobeProgram where T: core::marker::Sized +pub fn bpfman::types::UprobeProgram::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::UprobeProgram +pub fn bpfman::types::UprobeProgram::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::UprobeProgram +pub type bpfman::types::UprobeProgram::Init = T +pub const bpfman::types::UprobeProgram::ALIGN: usize +pub unsafe fn bpfman::types::UprobeProgram::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::UprobeProgram::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::UprobeProgram::drop(ptr: usize) +pub unsafe fn bpfman::types::UprobeProgram::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::UprobeProgram where T: core::clone::Clone +pub fn bpfman::types::UprobeProgram::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl tracing::instrument::Instrument for bpfman::types::UprobeProgram +impl tracing::instrument::WithSubscriber for bpfman::types::UprobeProgram +impl typenum::type_operators::Same for bpfman::types::UprobeProgram +pub type bpfman::types::UprobeProgram::Output = T +impl ppv_lite86::types::VZip for bpfman::types::UprobeProgram where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::UprobeProgram::vzip(self) -> V +pub struct bpfman::types::XdpProceedOn(_) +impl bpfman::types::XdpProceedOn +pub fn bpfman::types::XdpProceedOn::as_action_vec(&self) -> alloc::vec::Vec +pub fn bpfman::types::XdpProceedOn::from_int32s>(values: T) -> core::result::Result +pub fn bpfman::types::XdpProceedOn::from_strings>(values: T) -> core::result::Result +pub fn bpfman::types::XdpProceedOn::mask(&self) -> u32 +impl core::clone::Clone for bpfman::types::XdpProceedOn +pub fn bpfman::types::XdpProceedOn::clone(&self) -> bpfman::types::XdpProceedOn +impl core::default::Default for bpfman::types::XdpProceedOn +pub fn bpfman::types::XdpProceedOn::default() -> Self +impl core::fmt::Debug for bpfman::types::XdpProceedOn +pub fn bpfman::types::XdpProceedOn::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::fmt::Display for bpfman::types::XdpProceedOn +pub fn bpfman::types::XdpProceedOn::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::iter::traits::collect::FromIterator for bpfman::types::XdpProceedOn +pub fn bpfman::types::XdpProceedOn::from_iter>(iter: I) -> Self +impl serde::ser::Serialize for bpfman::types::XdpProceedOn +pub fn bpfman::types::XdpProceedOn::serialize<__S>(&self, __serializer: __S) -> core::result::Result<<__S as serde::ser::Serializer>::Ok, <__S as serde::ser::Serializer>::Error> where __S: serde::ser::Serializer +impl<'de> serde::de::Deserialize<'de> for bpfman::types::XdpProceedOn +pub fn bpfman::types::XdpProceedOn::deserialize<__D>(__deserializer: __D) -> core::result::Result::Error> where __D: serde::de::Deserializer<'de> +impl core::marker::Freeze for bpfman::types::XdpProceedOn +impl core::marker::Send for bpfman::types::XdpProceedOn +impl core::marker::Sync for bpfman::types::XdpProceedOn +impl core::marker::Unpin for bpfman::types::XdpProceedOn +impl core::panic::unwind_safe::RefUnwindSafe for bpfman::types::XdpProceedOn +impl core::panic::unwind_safe::UnwindSafe for bpfman::types::XdpProceedOn +impl jwt::token::signed::SignWithKey for bpfman::types::XdpProceedOn where C: jwt::ToBase64 +pub fn bpfman::types::XdpProceedOn::sign_with_key(self, key: &impl jwt::algorithm::SigningAlgorithm) -> core::result::Result +impl core::convert::Into for bpfman::types::XdpProceedOn where U: core::convert::From +pub fn bpfman::types::XdpProceedOn::into(self) -> U +impl core::convert::TryFrom for bpfman::types::XdpProceedOn where U: core::convert::Into +pub type bpfman::types::XdpProceedOn::Error = core::convert::Infallible +pub fn bpfman::types::XdpProceedOn::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::XdpProceedOn where U: core::convert::TryFrom +pub type bpfman::types::XdpProceedOn::Error = >::Error +pub fn bpfman::types::XdpProceedOn::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::XdpProceedOn where T: core::clone::Clone +pub type bpfman::types::XdpProceedOn::Owned = T +pub fn bpfman::types::XdpProceedOn::clone_into(&self, target: &mut T) +pub fn bpfman::types::XdpProceedOn::to_owned(&self) -> T +impl alloc::string::ToString for bpfman::types::XdpProceedOn where T: core::fmt::Display + core::marker::Sized +pub fn bpfman::types::XdpProceedOn::to_string(&self) -> alloc::string::String +impl core::any::Any for bpfman::types::XdpProceedOn where T: 'static + core::marker::Sized +pub fn bpfman::types::XdpProceedOn::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::XdpProceedOn where T: core::marker::Sized +pub fn bpfman::types::XdpProceedOn::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::XdpProceedOn where T: core::marker::Sized +pub fn bpfman::types::XdpProceedOn::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::XdpProceedOn +pub fn bpfman::types::XdpProceedOn::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::XdpProceedOn +pub type bpfman::types::XdpProceedOn::Init = T +pub const bpfman::types::XdpProceedOn::ALIGN: usize +pub unsafe fn bpfman::types::XdpProceedOn::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::XdpProceedOn::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::XdpProceedOn::drop(ptr: usize) +pub unsafe fn bpfman::types::XdpProceedOn::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::XdpProceedOn where T: core::clone::Clone +pub fn bpfman::types::XdpProceedOn::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl jwt::FromBase64 for bpfman::types::XdpProceedOn where T: for<'de> serde::de::Deserialize<'de> +pub fn bpfman::types::XdpProceedOn::from_base64(raw: &Input) -> core::result::Result where Input: core::convert::AsRef<[u8]> + core::marker::Sized +impl jwt::ToBase64 for bpfman::types::XdpProceedOn where T: serde::ser::Serialize +pub fn bpfman::types::XdpProceedOn::to_base64(&self) -> core::result::Result, jwt::error::Error> +impl serde::de::DeserializeOwned for bpfman::types::XdpProceedOn where T: for<'de> serde::de::Deserialize<'de> +impl tracing::instrument::Instrument for bpfman::types::XdpProceedOn +impl tracing::instrument::WithSubscriber for bpfman::types::XdpProceedOn +impl typenum::type_operators::Same for bpfman::types::XdpProceedOn +pub type bpfman::types::XdpProceedOn::Output = T +impl ppv_lite86::types::VZip for bpfman::types::XdpProceedOn where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::XdpProceedOn::vzip(self) -> V +pub struct bpfman::types::XdpProgram +impl bpfman::types::XdpProgram +pub fn bpfman::types::XdpProgram::get_attached(&self) -> core::result::Result +pub fn bpfman::types::XdpProgram::get_current_position(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::XdpProgram::get_if_index(&self) -> core::result::Result, bpfman::errors::BpfmanError> +pub fn bpfman::types::XdpProgram::get_iface(&self) -> core::result::Result +pub fn bpfman::types::XdpProgram::get_priority(&self) -> core::result::Result +pub fn bpfman::types::XdpProgram::get_proceed_on(&self) -> core::result::Result +pub fn bpfman::types::XdpProgram::new(data: bpfman::types::ProgramData, priority: i32, iface: alloc::string::String, proceed_on: bpfman::types::XdpProceedOn) -> core::result::Result +impl core::clone::Clone for bpfman::types::XdpProgram +pub fn bpfman::types::XdpProgram::clone(&self) -> bpfman::types::XdpProgram +impl core::fmt::Debug for bpfman::types::XdpProgram +pub fn bpfman::types::XdpProgram::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +impl core::marker::Freeze for bpfman::types::XdpProgram +impl core::marker::Send for bpfman::types::XdpProgram +impl core::marker::Sync for bpfman::types::XdpProgram +impl core::marker::Unpin for bpfman::types::XdpProgram +impl !core::panic::unwind_safe::RefUnwindSafe for bpfman::types::XdpProgram +impl !core::panic::unwind_safe::UnwindSafe for bpfman::types::XdpProgram +impl core::convert::Into for bpfman::types::XdpProgram where U: core::convert::From +pub fn bpfman::types::XdpProgram::into(self) -> U +impl core::convert::TryFrom for bpfman::types::XdpProgram where U: core::convert::Into +pub type bpfman::types::XdpProgram::Error = core::convert::Infallible +pub fn bpfman::types::XdpProgram::try_from(value: U) -> core::result::Result>::Error> +impl core::convert::TryInto for bpfman::types::XdpProgram where U: core::convert::TryFrom +pub type bpfman::types::XdpProgram::Error = >::Error +pub fn bpfman::types::XdpProgram::try_into(self) -> core::result::Result>::Error> +impl alloc::borrow::ToOwned for bpfman::types::XdpProgram where T: core::clone::Clone +pub type bpfman::types::XdpProgram::Owned = T +pub fn bpfman::types::XdpProgram::clone_into(&self, target: &mut T) +pub fn bpfman::types::XdpProgram::to_owned(&self) -> T +impl core::any::Any for bpfman::types::XdpProgram where T: 'static + core::marker::Sized +pub fn bpfman::types::XdpProgram::type_id(&self) -> core::any::TypeId +impl core::borrow::Borrow for bpfman::types::XdpProgram where T: core::marker::Sized +pub fn bpfman::types::XdpProgram::borrow(&self) -> &T +impl core::borrow::BorrowMut for bpfman::types::XdpProgram where T: core::marker::Sized +pub fn bpfman::types::XdpProgram::borrow_mut(&mut self) -> &mut T +impl core::convert::From for bpfman::types::XdpProgram +pub fn bpfman::types::XdpProgram::from(t: T) -> T +impl crossbeam_epoch::atomic::Pointable for bpfman::types::XdpProgram +pub type bpfman::types::XdpProgram::Init = T +pub const bpfman::types::XdpProgram::ALIGN: usize +pub unsafe fn bpfman::types::XdpProgram::deref<'a>(ptr: usize) -> &'a T +pub unsafe fn bpfman::types::XdpProgram::deref_mut<'a>(ptr: usize) -> &'a mut T +pub unsafe fn bpfman::types::XdpProgram::drop(ptr: usize) +pub unsafe fn bpfman::types::XdpProgram::init(init: ::Init) -> usize +impl dyn_clone::DynClone for bpfman::types::XdpProgram where T: core::clone::Clone +pub fn bpfman::types::XdpProgram::__clone_box(&self, dyn_clone::sealed::Private) -> *mut () +impl tracing::instrument::Instrument for bpfman::types::XdpProgram +impl tracing::instrument::WithSubscriber for bpfman::types::XdpProgram +impl typenum::type_operators::Same for bpfman::types::XdpProgram +pub type bpfman::types::XdpProgram::Output = T +impl ppv_lite86::types::VZip for bpfman::types::XdpProgram where V: ppv_lite86::types::MultiLane +pub fn bpfman::types::XdpProgram::vzip(self) -> V +pub mod bpfman::utils +pub const bpfman::utils::SOCK_MODE: u32 = 432u32 +pub fn bpfman::utils::create_bpffs(directory: &str) -> anyhow::Result<()> +pub fn bpfman::utils::set_dir_permissions(directory: &str, mode: u32) +pub fn bpfman::utils::set_file_permissions(path: &std::path::Path, mode: u32) +pub async fn bpfman::add_program(program: bpfman::types::Program) -> core::result::Result +pub async fn bpfman::get_program(id: u32) -> core::result::Result +pub async fn bpfman::list_programs(filter: bpfman::types::ListFilter) -> core::result::Result, bpfman::errors::BpfmanError> +pub async fn bpfman::pull_bytecode(image: bpfman::types::BytecodeImage) -> anyhow::Result<()> +pub async fn bpfman::remove_program(id: u32) -> core::result::Result<(), bpfman::errors::BpfmanError> diff --git a/xtask/src/build_completion.rs b/xtask/src/build_completion.rs new file mode 100644 index 000000000..6af4d7c17 --- /dev/null +++ b/xtask/src/build_completion.rs @@ -0,0 +1,35 @@ +#[allow(dead_code)] +mod cli { + include!("../../bpfman/src/bin/cli/args.rs"); +} + +use std::{ffi::OsStr, fs::create_dir_all, path::PathBuf}; + +use clap::{CommandFactory, Parser}; +use clap_complete::{Generator, Shell}; +use cli::Cli; + +use crate::workspace::WORKSPACE_ROOT; + +#[derive(Debug, Parser)] +pub struct Options {} + +fn write_completions_file>(generator: G, out_dir: P) { + let mut cmd = Cli::command().name("bpfman").version("0.4.0"); + clap_complete::generate_to(generator, &mut cmd, "bpfman", &out_dir) + .expect("clap complete generation failed"); +} + +pub fn build_completion(_opts: Options) -> Result<(), anyhow::Error> { + let mut out_dir = PathBuf::from(WORKSPACE_ROOT.to_string()); + out_dir.push(".output/completions"); + create_dir_all(&out_dir)?; + + write_completions_file(Shell::Bash, &out_dir); + write_completions_file(Shell::Elvish, &out_dir); + write_completions_file(Shell::Fish, &out_dir); + write_completions_file(Shell::PowerShell, &out_dir); + write_completions_file(Shell::Zsh, &out_dir); + eprintln!("completion scripts generated in {out_dir:?}"); + Ok(()) +} diff --git a/xtask/src/build_ebpf.rs b/xtask/src/build_ebpf.rs index 97d3cbcd7..25cc96653 100644 --- a/xtask/src/build_ebpf.rs +++ b/xtask/src/build_ebpf.rs @@ -3,13 +3,12 @@ use std::{ fs::{self, create_dir_all}, path::{Path, PathBuf}, process::Command, - string::String, }; use anyhow::{bail, Context, Result}; use clap::Parser; -use lazy_static::lazy_static; -use serde_json::Value; + +use crate::workspace::WORKSPACE_ROOT; #[derive(Debug, Copy, Clone)] pub enum Architecture { @@ -54,20 +53,6 @@ pub struct Options { pub libbpf_dir: PathBuf, } -lazy_static! { - pub static ref WORKSPACE_ROOT: String = workspace_root(); -} - -fn workspace_root() -> String { - let output = Command::new("cargo").arg("metadata").output().unwrap(); - if !output.status.success() { - panic!("unable to run cargo metadata") - } - let stdout = String::from_utf8(output.stdout).unwrap(); - let v: Value = serde_json::from_str(&stdout).unwrap(); - v["workspace_root"].as_str().unwrap().to_string() -} - fn get_libbpf_headers>(libbpf_dir: P, include_path: P) -> anyhow::Result<()> { let dir = include_path.as_ref(); fs::create_dir_all(dir)?; diff --git a/xtask/src/build_manpage.rs b/xtask/src/build_manpage.rs new file mode 100644 index 000000000..4b4e75f9a --- /dev/null +++ b/xtask/src/build_manpage.rs @@ -0,0 +1,87 @@ +#[allow(dead_code)] +mod cli { + include!("../../bpfman/src/bin/cli/args.rs"); +} + +use std::{ + fs::{create_dir_all, remove_dir_all}, + path::{Path, PathBuf}, +}; + +use clap::{CommandFactory, Parser}; +use cli::Cli; + +use crate::workspace::WORKSPACE_ROOT; + +#[derive(Debug, Parser)] +pub struct Options {} + +fn generate_manpage( + cmd: clap::Command, + name: String, + out_dir: &PathBuf, +) -> Result<(), anyhow::Error> { + let man = clap_mangen::Man::new(cmd.version("0.4.0")); + let mut buffer: Vec = Default::default(); + man.render(&mut buffer)?; + let file_path = Path::new(out_dir).join(format!("{}.1", name)); + std::fs::write(&file_path, buffer)?; + eprintln!("map page generated in {file_path:?}"); + Ok(()) +} + +pub fn build_manpage(_opts: Options) -> Result<(), anyhow::Error> { + let mut out_dir = PathBuf::from(WORKSPACE_ROOT.to_string()); + out_dir.push(".output/manpage"); + + // If a command is renamed or removed, old files still remain. + // Clean out first. Ignore error if it doesn't exist. + let _ = remove_dir_all(&out_dir); + create_dir_all(&out_dir)?; + + let cmd: clap::Command = Cli::command(); + let bpfman_name = cmd.get_name().to_owned(); + + // Generate `bpfman` manpage + generate_manpage(cmd, bpfman_name.to_string(), &out_dir)?; + + // Generate `bpfman ` manpages (get, list, load, unload, etc) + for depth1 in Cli::command().get_subcommands() { + generate_manpage( + depth1.clone(), + format!("{}-{}", bpfman_name, depth1.get_name()), + &out_dir, + )?; + + // `bpfman load` has additional subcommands (file and image) + for depth2 in depth1.get_subcommands() { + generate_manpage( + depth2.clone(), + format!( + "{}-{}-{}", + bpfman_name, + depth1.get_name(), + depth2.get_name() + ), + &out_dir, + )?; + + // `bpfman load file` and `bpfman load image` have subcommands (xdp, tc, ...) + for depth3 in depth2.get_subcommands() { + generate_manpage( + depth3.clone(), + format!( + "{}-{}-{}-{}", + bpfman_name, + depth1.get_name(), + depth2.get_name(), + depth3.get_name() + ), + &out_dir, + )?; + } + } + } + + Ok(()) +} diff --git a/xtask/src/copy.rs b/xtask/src/copy.rs index 907ff7951..9b453abb0 100644 --- a/xtask/src/copy.rs +++ b/xtask/src/copy.rs @@ -1,8 +1,8 @@ -use std::{path::PathBuf, process::Command, string::String}; +use std::{path::PathBuf, process::Command}; use clap::Parser; -use lazy_static::lazy_static; -use serde_json::Value; + +use crate::workspace::WORKSPACE_ROOT; #[derive(Debug, Parser)] pub struct Options { @@ -11,20 +11,6 @@ pub struct Options { pub release: bool, } -lazy_static! { - pub static ref WORKSPACE_ROOT: String = workspace_root(); -} - -fn workspace_root() -> String { - let output = Command::new("cargo").arg("metadata").output().unwrap(); - if !output.status.success() { - panic!("unable to run cargo metadata") - } - let stdout = String::from_utf8(output.stdout).unwrap(); - let v: Value = serde_json::from_str(&stdout).unwrap(); - v["workspace_root"].as_str().unwrap().to_string() -} - /// Copy the binaries pub fn copy(opts: Options) -> Result<(), anyhow::Error> { let root = PathBuf::from(WORKSPACE_ROOT.to_string()); diff --git a/xtask/src/lint.rs b/xtask/src/lint.rs new file mode 100644 index 000000000..0bbabbcb1 --- /dev/null +++ b/xtask/src/lint.rs @@ -0,0 +1,25 @@ +use std::{path::PathBuf, process::Command}; + +use clap::Parser; + +use crate::workspace::WORKSPACE_ROOT; + +#[derive(Debug, Parser)] +pub struct Options {} + +/// Run linter +pub fn lint() -> Result<(), anyhow::Error> { + let root = PathBuf::from(WORKSPACE_ROOT.to_string()); + let scripts_dir = root.join("scripts"); + + let args = vec!["-E", "./lint.sh"]; + + println!("scripts_dir: {}", scripts_dir.display()); + let status = Command::new("sh") + .current_dir(&scripts_dir) + .args(args) + .status() + .expect("failed to run lint"); + assert!(status.success()); + Ok(()) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index e1b1a7361..e2fb4962e 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,11 +1,18 @@ +mod build_completion; mod build_ebpf; +mod build_manpage; mod copy; mod integration_test; +mod lint; mod protobuf; +mod public_api; mod run; +mod unit_test; +mod workspace; use std::process::exit; +use cargo_metadata::MetadataCommand; use clap::Parser; #[derive(Debug, Parser)] @@ -27,11 +34,26 @@ enum Command { Run(run::Options), /// Run the integration tests for bpfman. IntegrationTest(integration_test::Options), + /// Build the man pages for bpfman. + BuildManPage(build_manpage::Options), + /// Build the completion scripts for bpfman. + BuildCompletion(build_completion::Options), + /// Generate the public API documentation for bpfman. + PublicApi(public_api::Options), + /// Run lint. + Lint(lint::Options), + /// Run unit tests. + UnitTest(unit_test::Options), } fn main() { let opts = Options::parse(); + let metadata = MetadataCommand::new() + .no_deps() + .exec() + .expect("failed to run cargo metadata"); + use Command::*; let ret = match opts.command { BuildEbpf(opts) => build_ebpf::build_ebpf(opts), @@ -39,6 +61,11 @@ fn main() { Copy(opts) => copy::copy(opts), Run(opts) => run::run(opts), IntegrationTest(opts) => integration_test::test(opts), + BuildManPage(opts) => build_manpage::build_manpage(opts), + BuildCompletion(opts) => build_completion::build_completion(opts), + PublicApi(opts) => public_api::public_api(opts, metadata), + Lint(_) => lint::lint(), + UnitTest(opts) => unit_test::unit_test(opts), }; if let Err(e) = ret { diff --git a/xtask/src/protobuf.rs b/xtask/src/protobuf.rs index ec238c274..2c778632c 100644 --- a/xtask/src/protobuf.rs +++ b/xtask/src/protobuf.rs @@ -1,26 +1,12 @@ -use std::{path::PathBuf, process::Command, string::String}; +use std::{path::PathBuf, process::Command}; use clap::Parser; -use lazy_static::lazy_static; -use serde_json::Value; + +use crate::workspace::WORKSPACE_ROOT; #[derive(Debug, Parser)] pub struct Options {} -lazy_static! { - pub static ref WORKSPACE_ROOT: String = workspace_root(); -} - -fn workspace_root() -> String { - let output = Command::new("cargo").arg("metadata").output().unwrap(); - if !output.status.success() { - panic!("unable to run cargo metadata") - } - let stdout = String::from_utf8(output.stdout).unwrap(); - let v: Value = serde_json::from_str(&stdout).unwrap(); - v["workspace_root"].as_str().unwrap().to_string() -} - pub fn build(_opts: Options) -> anyhow::Result<()> { build_bpfman(&_opts)?; build_csi(&_opts)?; diff --git a/xtask/src/public_api.rs b/xtask/src/public_api.rs new file mode 100644 index 000000000..c4c305c8e --- /dev/null +++ b/xtask/src/public_api.rs @@ -0,0 +1,167 @@ +use std::{ + fmt::Write as _, + fs::{read_to_string, File}, + io::Write as _, + path::Path, +}; + +use anyhow::{bail, Context as _, Result}; +use cargo_metadata::{Metadata, Package, Target}; +use clap::Parser; +use dialoguer::{theme::ColorfulTheme, Confirm}; +use diff::{lines, Result as Diff}; + +#[derive(Debug, Parser)] +pub struct Options { + /// Bless new API changes. + #[clap(long)] + pub bless: bool, + + /// Bless new API changes. + #[clap(long)] + pub target: Option, +} + +pub fn public_api(options: Options, metadata: Metadata) -> Result<()> { + let toolchain = "nightly"; + let Options { bless, target } = options; + + if !rustup_toolchain::is_installed(toolchain)? { + if Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt( + format! {"No {toolchain} toolchain detected. Would you like to install one?"}, + ) + .interact()? + { + rustup_toolchain::install(toolchain)?; + } else { + bail!(format! {"{toolchain} toolchain not installed"}) + } + } + + let Metadata { + workspace_root, + packages, + .. + } = metadata; + + let errors: Vec<_> = packages + .into_iter() + .map( + |Package { + name, + publish, + targets, + .. + }| { + if matches!(publish, Some(publish) if publish.is_empty()) + || name == "bpfman-api" + || name == "bpfman-csi" + { + Ok(()) + } else { + if !targets + .clone() + .into_iter() + .any(|target| target.kind.contains(&"lib".to_string())) + { + return Ok(()); + } + + let arch = target.as_ref().and_then(|target| { + let proc_macro = targets.iter().any(|Target { kind, .. }| { + kind.iter().any(|kind| kind == "proc-macro") + }); + (!proc_macro).then_some(target) + }); + + let diff = check_package_api( + &name, + toolchain, + arch.cloned(), + bless, + workspace_root.as_std_path(), + ) + .with_context(|| format!("{name} failed to check public API"))?; + if diff.is_empty() { + Ok(()) + } else { + Err(anyhow::anyhow!( + "{name} public API changed; re-run with --bless. diff:\n{diff}" + )) + } + } + }, + ) + .filter_map(|result| match result { + Ok(()) => None, + Err(err) => Some(err), + }) + .collect(); + + if errors.is_empty() { + Ok(()) + } else { + for error in errors { + eprintln!("{}", error); + } + bail!("public API generation failed") + } +} + +fn check_package_api( + package: &str, + toolchain: &str, + target: Option, + bless: bool, + workspace_root: &Path, +) -> Result { + let path = workspace_root + .join("xtask") + .join("public-api") + .join(package) + .with_extension("txt"); + + let mut builder = rustdoc_json::Builder::default() + .toolchain(toolchain) + .package(package) + .all_features(true); + if let Some(target) = target { + builder = builder.target(target); + } + let rustdoc_json = builder.build().with_context(|| { + format!( + "rustdoc_json::Builder::default().toolchain({}).package({}).build()", + toolchain, package + ) + })?; + + let public_api = public_api::Builder::from_rustdoc_json(&rustdoc_json) + .build() + .with_context(|| { + format!( + "public_api::Builder::from_rustdoc_json({})::build()", + rustdoc_json.display() + ) + })?; + + if bless { + let mut output = + File::create(&path).with_context(|| format!("error creating {}", path.display()))?; + write!(&mut output, "{}", public_api) + .with_context(|| format!("error writing {}", path.display()))?; + } + let current_api = + read_to_string(&path).with_context(|| format!("error reading {}", path.display()))?; + + Ok(lines(&public_api.to_string(), ¤t_api) + .into_iter() + .fold(String::new(), |mut buf, diff| { + match diff { + Diff::Both(..) => (), + Diff::Right(line) => writeln!(&mut buf, "-{}", line).unwrap(), + Diff::Left(line) => writeln!(&mut buf, "+{}", line).unwrap(), + }; + buf + })) +} diff --git a/xtask/src/unit_test.rs b/xtask/src/unit_test.rs new file mode 100644 index 000000000..a0a6aaaa9 --- /dev/null +++ b/xtask/src/unit_test.rs @@ -0,0 +1,32 @@ +use std::process::Command; + +use clap::Parser; + +#[derive(Debug, Parser)] +pub struct Options { + /// Optional: Build the release target + #[clap(long)] + pub release: bool, +} + +/// Run unit-test +pub fn unit_test(opts: Options) -> Result<(), anyhow::Error> { + let mut args = vec!["test"]; + if opts.release { + args.push("--release") + } + let status = Command::new("cargo") + .args(&args) + .status() + .expect("failed to run rust unit-test"); + assert!(status.success()); + + let args = vec!["test", "./...", "-coverprofile", "cover.out"]; + let status = Command::new("go") + .args(&args) + .status() + .expect("failed to run go unit-test"); + assert!(status.success()); + + Ok(()) +} diff --git a/xtask/src/workspace.rs b/xtask/src/workspace.rs new file mode 100644 index 000000000..9f4e675c4 --- /dev/null +++ b/xtask/src/workspace.rs @@ -0,0 +1,18 @@ +use std::process::Command; + +use lazy_static::lazy_static; +use serde_json::Value; + +lazy_static! { + pub static ref WORKSPACE_ROOT: String = workspace_root(); +} + +fn workspace_root() -> String { + let output = Command::new("cargo").arg("metadata").output().unwrap(); + if !output.status.success() { + panic!("unable to run cargo metadata") + } + let stdout = String::from_utf8(output.stdout).unwrap(); + let v: Value = serde_json::from_str(&stdout).unwrap(); + v["workspace_root"].as_str().unwrap().to_string() +}