Showing preview only (497K chars total). Download the full file or copy to clipboard to get everything.
Repository: l3af-project/l3afd
Branch: main
Commit: 4ce859684188
Files: 80
Total size: 472.1 KB
Directory structure:
gitextract_j3_x9dxv/
├── .github/
│ ├── dependabot.yml
│ ├── release.yml
│ └── workflows/
│ ├── ci-build-windows.yaml
│ ├── ci-build.yaml
│ ├── ci-e2e.yaml
│ ├── codeql.yaml
│ └── scorecards-analysis.yml
├── .gitignore
├── CMakeLists.txt
├── CODEOWNERS
├── LICENSE
├── Makefile
├── README.md
├── apis/
│ ├── configwatch.go
│ ├── configwatch_test.go
│ ├── handlers/
│ │ ├── addprog.go
│ │ ├── addprog_test.go
│ │ ├── deleteprog.go
│ │ ├── deleteprog_test.go
│ │ ├── getconfig.go
│ │ ├── getconfig_test.go
│ │ ├── restart_linux.go
│ │ ├── restart_linux_test.go
│ │ ├── restart_windows.go
│ │ ├── restart_windows_test.go
│ │ ├── updateconfig.go
│ │ └── updateconfig_test.go
│ └── routes.go
├── bpfprogs/
│ ├── bpf.go
│ ├── bpfCfgs_internal.go
│ ├── bpf_test.go
│ ├── bpf_test_unix.go
│ ├── bpf_test_windows.go
│ ├── bpf_unix.go
│ ├── bpf_windows.go
│ ├── bpfdebug.go
│ ├── bpfmap.go
│ ├── bpfmap_test.go
│ ├── bpfmetrics.go
│ ├── bpfmetrics_test.go
│ ├── nfconfig.go
│ ├── nfconfig_test.go
│ ├── probes.go
│ ├── probes_test.go
│ ├── processCheck.go
│ └── processCheck_test.go
├── build-docker/
│ ├── Dockerfile
│ └── start.sh
├── config/
│ ├── config.go
│ ├── config_loader.go
│ └── l3afd.cfg
├── config.yml
├── docs/
│ ├── CONTRIBUTING.md
│ ├── api/
│ │ └── README.md
│ ├── configdoc.md
│ ├── docs.go
│ ├── graceful-restart-guide.md
│ ├── prod-deploy-guide.md
│ ├── swagger.json
│ ├── swagger.md
│ └── swagger.yaml
├── go.mod
├── go.sum
├── main.go
├── main_test.go
├── mocks/
│ └── mocked_interfaces.go
├── models/
│ └── l3afd.go
├── pidfile/
│ └── pidfile.go
├── register_internal.go
├── restart/
│ ├── restart.go
│ └── restart_test.go
├── routes/
│ ├── route.go
│ └── router.go
├── signals/
│ ├── signal_unix.go
│ └── signal_windows.go
├── stats/
│ └── metrics.go
├── testdata/
│ ├── Test_l3af-config.json
│ └── l3afd.cdb
├── utils/
│ └── utils.go
└── version.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
# Copyright Contributors to the L3AF Project.
# SPDX-License-Identifier: Apache-2.0
#
# For documentation on the format of this file, see
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions"
# Workflow files are stored in the default location of `.github/workflows`
directory: "/"
schedule:
interval: "weekly"
day: "saturday"
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
day: "saturday"
================================================
FILE: .github/release.yml
================================================
# .github/release.yml
---
changelog:
exclude:
labels:
- ignore-for-release
categories:
- title: Breaking Changes 💥
labels:
- breaking-change
- breaking
- title: New Features 🎉
labels:
- feat
- enhancement
- title: Bug Fixes 🐛
labels:
- fix
- bugfix
- bug
- title: Other Changes
labels:
- "*"
================================================
FILE: .github/workflows/ci-build-windows.yaml
================================================
# Copyright Contributors to the L3AF Project.
# SPDX-License-Identifier: Apache-2.0
#
# For documentation on the github environment, see
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
#
# For documentation on the syntax of this file, see
# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
name: CI Windows build
on:
pull_request: {}
push:
branches:
- main
permissions:
contents: read
jobs:
build:
runs-on: windows-latest
steps:
- name: Setup Go 1.26.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c
with:
go-version: '1.26.0'
- name: Harden Runner
uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Set up git env
run: |
git config --global core.autocrlf false
$gopath = (go env GOPATH)
echo "GOPATH=$gopath" >> $env:GITHUB_ENV
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Format
run: |
go install golang.org/x/tools/cmd/goimports@latest
$goimp = (Join-path -Path (go env GOPATH) -ChildPath "\bin\goimports")
$res = (&$goimp -l .) -replace "$_"
if ($res -ne "") {
echo "Unformatted source code:"
echo $res
exit 1
}
- name: Vet
run: |
go vet -tags WINDOWS ./...
- name: Test
run: |
go test -tags WINDOWS ./...
- uses: dominikh/staticcheck-action@9716614d4101e79b4340dd97b10e54d68234e431
with:
version: "2025.1.1"
install-go: false
cache-key: "1.24.x"
build-tags: WINDOWS
- name: Build
env:
GOPATH: ${{env.GOPATH}}
run: |
cmake -B build
cmake --build build
================================================
FILE: .github/workflows/ci-build.yaml
================================================
# Copyright Contributors to the L3AF Project.
# SPDX-License-Identifier: Apache-2.0
#
# For documentation on the github environment, see
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
#
# For documentation on the syntax of this file, see
# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
name: CI Ubuntu build
on:
pull_request: {}
push:
branches:
- main
permissions:
contents: read
jobs:
build:
strategy:
matrix:
os:
- ubuntu-24.04
- ubuntu-22.04
runs-on: ${{ matrix.os }}
steps:
- name: Setup Go 1.26.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c
with:
go-version: '1.26.0'
- name: Harden Runner
uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Set up environment
run: |
sudo apt-get update
sudo apt-get remove -y containerd.io docker docker.io moby-engine moby-cli || true # Remove any existing Docker-related packages
sudo apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
# docker src
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
sudo apt-get install -y gcc libc-dev bash perl curl make
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Format
run: |
go install golang.org/x/tools/cmd/goimports@latest
res="$(goimports -l .)"
if [[ "$(printf '%s' "$res")" != '' ]]; then
echo "Unformatted source code:"
echo "$res"
exit 1
fi
- name: Vet
run: |
go vet ./...
- name: Test
run: |
go test ./...
go clean -modcache
- uses: dominikh/staticcheck-action@9716614d4101e79b4340dd97b10e54d68234e431
with:
version: "2025.1.1"
install-go: false
cache-key: "1.24.x"
- name: Build
run: |
make
- name: Copy files
if: github.ref == 'refs/heads/main'
run: |
sudo cp ./config/l3afd.cfg ./build-docker
sudo cp l3afd ./build-docker
- name: login to docker registry
if: github.ref == 'refs/heads/main'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121
with:
username: ${{secrets.DOCKER_USERNAME}}
password: ${{secrets.DOCKER_TOKEN}}
- name: build and push docker image to registry
if: github.ref == 'refs/heads/main'
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f
with:
context: ./build-docker
push: true
tags: linuxfoundationl3af/l3afd:latest
- name: upload l3afd binary
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: l3afd-latest-linux-x86_64-${{ matrix.os }}
path: l3afd
================================================
FILE: .github/workflows/ci-e2e.yaml
================================================
# Copyright Contributors to the L3AF Project.
# SPDX-License-Identifier: Apache-2.0
#
# For documentation on the github environment, see
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
#
# For documentation on the syntax of this file, see
# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
name: CI E2E build
on:
pull_request: {}
push:
branches:
- main
permissions:
contents: read
jobs:
build:
strategy:
matrix:
os:
- ubuntu-24.04
- ubuntu-22.04
runs-on: ${{ matrix.os }}
steps:
- name: Update and firewall stop
run: |
sudo apt update
sudo systemctl stop ufw
sudo apt install -y iproute2
sudo apt install git curl hey
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Prep
run: |
sudo cp -r /home/runner/work/l3afd/l3afd /root
sudo git clone https://github.com/l3af-project/l3af-arch.git /root/l3af-arch
sudo bash /root/l3af-arch/dev_environment/e2e_test/prep_env.sh
sudo bash /root/l3af-arch/dev_environment/setup_linux_dev_env.sh --ci-build
hm=$(hostname)
sudo find /root/l3af-arch/dev_environment/e2e_test -type f -name "*.json" -exec sed -i "s/l3af-test-host/$hm/g" {} +
- name: Run Tests
run: |
sudo bash /root/l3af-arch/dev_environment/e2e_test/test_suite.sh
================================================
FILE: .github/workflows/codeql.yaml
================================================
# Copyright Contributors to the L3AF Project.
# SPDX-License-Identifier: Apache-2.0
#
# For documentation on the github environment, see
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
#
# For documentation on the syntax of this file, see
# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225
================================================
FILE: .github/workflows/scorecards-analysis.yml
================================================
# Copyright Contributors to the L3AF Project.
# SPDX-License-Identifier: Apache-2.0
#
# For documentation on the github environment, see
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
#
# For documentation on the syntax of this file, see
# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions
name: Scorecards
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
concurrency:
# Cancel any Scorecards workflow currently in progress for the same PR.
# Allow running concurrently with any other commits.
group: scorecards-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecards analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
id-token: write
actions: read
contents: read
steps:
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: ${{ github.event_name != 'pull_request' }}
# Upload the results as artifacts (optional).
- name: "Upload artifact"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard so it will be visible
# at https://github.com/l3af-project/l3afd/security/code-scanning.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225
with:
sarif_file: results.sarif
================================================
FILE: .gitignore
================================================
.idea/
vendor/
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.10)
project(l3afd)
add_custom_target(swagger ALL
DEPENDS ${CMAKE_SOURCE_DIR}/docs/docs.go
${CMAKE_SOURCE_DIR}/docs/swagger.json
${CMAKE_SOURCE_DIR}/docs/swagger.yaml)
add_custom_command(OUTPUT $ENV{GOPATH}/bin/swag.exe
COMMAND go install github.com/swaggo/swag/cmd/swag@latest
COMMAND go get -u github.com/swaggo/http-swagger
COMMAND go get -u github.com/alecthomas/template)
add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/docs/docs.go
${CMAKE_SOURCE_DIR}/docs/swagger.json
${CMAKE_SOURCE_DIR}/docs/swagger.yaml
DEPENDS ${CMAKE_SOURCE_DIR}/apis/configwatch.go
$ENV{GOPATH}/bin/swag.exe
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND "$ENV{GOPATH}/bin/swag.exe" init -d "./" -g "apis/configwatch.go")
add_custom_target(build ALL
DEPENDS ${CMAKE_SOURCE_DIR}/l3afd.exe)
if (${WIN32})
add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/l3afd.exe
DEPENDS ${CMAKE_SOURCE_DIR}/main.go
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND go build -tags WINDOWS .)
else ()
add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/l3afd.exe
DEPENDS ${CMAKE_SOURCE_DIR}/main.go
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND go build .)
endif ()
================================================
FILE: CODEOWNERS
================================================
# Default Code Owners
* @sanfern @charleskbliu0 @jniesz @dalalkaran @pmoroney
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
================================================
FILE: Makefile
================================================
.PHONY: all
export GOPATH := $(HOME)/go
all: swagger build
swagger:
@mkdir $(GOPATH) || true
@go install github.com/swaggo/swag/cmd/swag@latest
@$(GOPATH)/bin/swag init -d "./" -g "apis/configwatch.go"
build:
@CGO_ENABLED=0 go build -ldflags \
"-X main.Version=v2.1.0 \
-X main.VersionSHA=`git rev-parse HEAD`"
install: swagger
@go mod tidy
@CGO_ENABLED=0 go install -ldflags \
"-X main.Version=v2.1.0 \
-X main.VersionSHA=`git rev-parse HEAD`"
cibuild: swagger
@go mod tidy
@CGO_ENABLED=0 go install -cover -ldflags \
"-X main.Version=v2.1.0 \
-X main.VersionSHA=`git rev-parse HEAD`"
================================================
FILE: README.md
================================================
# L3AFD: Lightweight eBPF Daemon

[](https://bestpractices.coreinfrastructure.org/projects/6075)
[](https://goreportcard.com/report/github.com/l3af-project/l3afd)
[](https://pkg.go.dev/github.com/l3af-project/l3afd)
[](LICENSE)
[](http://l3afworkspace.slack.com/)
L3AFD is a crucial part of the L3AF ecosystem. For more information on L3AF see
https://l3af.io/
# Overview
L3AFD is the primary component of the L3AF control plane. L3AFD is a daemon
that orchestrates and manages multiple eBPF programs. L3AFD runs on each node
where the user wishes to run eBPF programs. L3AFD reads configuration data and
manages the execution and monitoring of eBPF programs running on the node.
L3AFD downloads pre-built eBPF programs from a user-configured repository.
However, we envision the creation of a community-driven eBPF package marketplace
where L3AF users can obtain a variety of eBPF programs developed by multiple
sources.

# Try it out
See our [L3AF Development Environment](https://github.com/l3af-project/l3af-arch/tree/main/dev_environment)
for a quick and easy way to try out L3AF on your local machine.
# Installing
Try [a binary release](https://github.com/l3af-project/l3afd/releases/latest).
# Building
To build on your local machine, including swagger docs do the following.
For Linux:
```
make
```
For Windows:
```
cmake -B build
cmake --build build
```
# Docker build
- L3AFD binary & configuration that is required in the Docker image needs to be built locally and copied to build-docker directory
- Execute below command to build the docker image
```
docker build -t l3afd:<version> -f Dockerfile .
```
Requirements to run L3AFD as a Container
- BPF, debugfs & shared-memory filesystems mount points should be available in the container
- L3AFD container needs privileged access as it needs to manage eBPF programs
- eBPF programs should be attached to the host interface so that it will apply to all the containers in the host
In order to satisfy the above requirements L3afd docker container needs to be run using the below command
```
docker run -d -v /sys/fs/bpf:/sys/fs/bpf -v /sys/kernel/debug/:/sys/kernel/debug/ -v /dev/shm:/dev/shm --privileged --net=host l3afd:<version>
```
# Testing
To test on your local machine, do the following.
For Linux:
```
go test ./...
```
For Windows:
```
go test -tags WINDOWS ./...
```
# Generate Swagger Docs
See our [Swaggo setup](docs/swagger.md)
# Contributing
Contributing to L3afd is fun. To get started:
- [Contributing guide](docs/CONTRIBUTING.md)
================================================
FILE: apis/configwatch.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
//
//go:build !configs
// +build !configs
package apis
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"path"
"regexp"
"strings"
"time"
"unicode/utf8"
httpSwagger "github.com/swaggo/http-swagger"
"github.com/l3af-project/l3afd/v2/bpfprogs"
"github.com/l3af-project/l3afd/v2/config"
"github.com/l3af-project/l3afd/v2/models"
"github.com/l3af-project/l3afd/v2/routes"
"github.com/l3af-project/l3afd/v2/signals"
_ "github.com/l3af-project/l3afd/v2/docs"
"github.com/rs/zerolog/log"
)
type Server struct {
BPFRTConfigs *bpfprogs.NFConfigs
HostName string
l3afdServer *http.Server
CaCertPool *x509.CertPool
SANMatchRules []string
}
// @title L3AFD APIs
// @version 1.0
// @description Configuration APIs to deploy and get the details of the eBPF Programs on the node
// @host
// @BasePath /
func StartConfigWatcher(ctx context.Context, hostname, daemonName string, conf *config.Config, bpfrtconfg *bpfprogs.NFConfigs) error {
log.Info().Msgf("%s config server setup started on host %s", daemonName, hostname)
s := &Server{
BPFRTConfigs: bpfrtconfg,
HostName: hostname,
l3afdServer: &http.Server{
Addr: conf.L3afConfigsRestAPIAddr,
},
SANMatchRules: conf.MTLSSANMatchRules,
}
if _, ok := models.AllNetListeners.Load("main_http"); !ok {
tcpAddr, err := net.ResolveTCPAddr("tcp", conf.L3afConfigsRestAPIAddr)
if err != nil {
return fmt.Errorf("error resolving TCP address:%w", err)
}
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
return fmt.Errorf("creating tcp listner failed with %w", err)
}
models.AllNetListeners.Store("main_http", listener)
}
term := make(chan os.Signal, 1)
signal.Notify(term, signals.ShutdownSignals...)
go func() {
<-term
s.GracefulStop(conf.ShutdownTimeout)
ctx.Done()
log.Info().Msg("L3afd gracefulStop completed")
}()
go func() {
r := routes.NewRouter(apiRoutes(ctx, bpfrtconfg))
if conf.SwaggerApiEnabled {
r.Mount("/swagger", httpSwagger.WrapHandler)
}
s.l3afdServer.Handler = r
// As per design discussion when mTLS flag is not set and not listening on loopback or localhost
if !conf.MTLSEnabled && !isLoopback(conf.L3afConfigsRestAPIAddr) && conf.Environment == config.ENV_PROD {
conf.MTLSEnabled = true
}
val, _ := models.AllNetListeners.Load("main_http")
l, _ := val.(*net.TCPListener)
if conf.MTLSEnabled {
log.Info().Msgf("l3afd server listening with mTLS - %s ", conf.L3afConfigsRestAPIAddr)
// Create a CA certificate pool and add client ca's to it
caCert, err := os.ReadFile(path.Join(conf.MTLSCertDir, conf.MTLSCACertFilename))
if err != nil {
log.Fatal().Err(err).Msgf("client CA %s file not found", conf.MTLSCACertFilename)
}
s.CaCertPool, _ = x509.SystemCertPool()
if s.CaCertPool == nil {
s.CaCertPool = x509.NewCertPool()
}
if ok := s.CaCertPool.AppendCertsFromPEM(caCert); !ok {
log.Warn().Msgf("No client certs appended for mTLS")
}
serverCertFile := path.Join(conf.MTLSCertDir, conf.MTLSServerCertFilename)
serverKeyFile := path.Join(conf.MTLSCertDir, conf.MTLSServerKeyFilename)
serverCert, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile)
if err != nil {
log.Fatal().Err(err).Msgf("failure loading certs")
}
// build server config
s.l3afdServer.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{serverCert},
GetConfigForClient: func(hi *tls.ClientHelloInfo) (*tls.Config, error) {
serverConf := &tls.Config{
Certificates: []tls.Certificate{serverCert},
MinVersion: tls.VersionTLS12,
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: s.CaCertPool,
VerifyPeerCertificate: s.getClientValidator(hi),
}
return serverConf, nil
},
}
cpb, _ := pem.Decode(caCert)
cert, err := x509.ParseCertificate(cpb.Bytes)
if err != nil {
log.Fatal().Err(err).Msgf("error in parsing tls certificate : %v", conf.MTLSCACertFilename)
}
expiry := cert.NotAfter
start := cert.NotBefore
go func() {
period := time.Hour * 24
ticker := time.NewTicker(period)
defer ticker.Stop()
for {
select {
case <-ticker.C:
MonitorTLS(start, expiry, conf)
case <-ctx.Done():
return
}
}
}()
if err := s.l3afdServer.ServeTLS(l, serverCertFile, serverKeyFile); !errors.Is(err, http.ErrServerClosed) {
log.Fatal().Err(err).Msgf("failed to start L3AFD server with mTLS enabled")
}
} else {
log.Info().Msgf("l3afd server listening - %s ", conf.L3afConfigsRestAPIAddr)
if err := s.l3afdServer.Serve(l); !errors.Is(err, http.ErrServerClosed) {
log.Fatal().Err(err).Msgf("failed to start L3AFD server")
}
}
}()
return nil
}
func (s *Server) GracefulStop(shutdownTimeout time.Duration) error {
log.Info().Msg("L3afd graceful stop initiated")
exitCode := 0
if len(s.BPFRTConfigs.IngressXDPBpfs) > 0 || len(s.BPFRTConfigs.IngressTCBpfs) > 0 || len(s.BPFRTConfigs.EgressTCBpfs) > 0 || s.BPFRTConfigs.ProbesBpfs.Len() > 0 {
ctx, cancelfunc := context.WithTimeout(context.Background(), shutdownTimeout)
defer cancelfunc()
if err := s.BPFRTConfigs.Close(ctx); err != nil {
log.Error().Err(err).Msg("stopping all network functions failed")
exitCode = 1
}
}
os.Exit(exitCode)
return nil
}
// isLoopback - Check for localhost or loopback address
func isLoopback(addr string) bool {
if strings.Contains(addr, "localhost:") {
return true
}
if id := strings.LastIndex(addr, ":"); id > -1 {
addr = addr[:id]
}
if ipAddr := net.ParseIP(addr); ipAddr != nil {
return ipAddr.IsLoopback()
}
// :port scenario
return true
}
func MonitorTLS(start time.Time, expiry time.Time, conf *config.Config) {
todayDate := time.Now()
expiryDate := expiry
startDate := start
diff := expiryDate.Sub(todayDate)
remainingHoursToStart := todayDate.Sub(startDate)
limit := conf.MTLSCertExpiryWarningDays * 24
remainingHoursToExpire := int(diff.Hours())
if remainingHoursToStart > 0 {
log.Fatal().Msgf("tls certificate start from : %v", startDate)
}
if remainingHoursToExpire <= limit {
if remainingHoursToExpire < 0 {
log.Fatal().Msgf("tls certificate is expired on : %v", expiryDate)
} else {
log.Warn().Msgf("tls certificate will expire in %v days", int64(remainingHoursToExpire/24))
}
}
}
func (s *Server) getClientValidator(helloInfo *tls.ClientHelloInfo) func([][]byte, [][]*x509.Certificate) error {
log.Debug().Msgf("Inside get client validator - %v", helloInfo.Conn.RemoteAddr())
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
// Verifying client certs with root ca
opts := x509.VerifyOptions{
Roots: s.CaCertPool,
CurrentTime: time.Now(),
Intermediates: x509.NewCertPool(),
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
_, err := verifiedChains[0][0].Verify(opts)
if err != nil {
log.Error().Err(err).Msgf("certs verification failed")
return err
}
log.Debug().Msgf("validating with SAN match rules - %s", s.SANMatchRules)
if len(s.SANMatchRules) == 0 {
return nil
}
for _, dnsName := range verifiedChains[0][0].DNSNames {
if !validHostname(dnsName, true) {
continue
}
dnsName = toLowerCaseASCII(dnsName)
for _, sanMatchRule := range s.SANMatchRules {
sanMatchRule = toLowerCaseASCII(sanMatchRule)
if matchExactly(dnsName, sanMatchRule) {
log.Debug().Msgf("Successfully matched matchExactly cert dns %s SANMatchRule %s", dnsName, sanMatchRule)
return nil
} else if matchHostnamesWithRegexp(dnsName, sanMatchRule) {
log.Debug().Msgf("Successfully matched matchHostnamesWithRegexp cert dns %s SANMatchRule %s", dnsName, sanMatchRule)
return nil
}
}
}
err = errors.New("certs verification with SAN match not found")
log.Error().Err(err).Msgf("SAN match rules %s", s.SANMatchRules)
return err
}
}
// toLowerCaseASCII returns a lower-case version of in. See RFC 6125 6.4.1. We use
// an explicitly ASCII function to avoid any sharp corners resulting from
// performing Unicode operations on DNS labels.
func toLowerCaseASCII(in string) string {
// If the string is already lower-case then there's nothing to do.
isAlreadyLowerCase := true
for _, c := range in {
if c == utf8.RuneError {
// If we get a UTF-8 error then there might be
// upper-case ASCII bytes in the invalid sequence.
isAlreadyLowerCase = false
break
}
if 'A' <= c && c <= 'Z' {
isAlreadyLowerCase = false
break
}
}
if isAlreadyLowerCase {
return in
}
out := []byte(in)
for i, c := range out {
if 'A' <= c && c <= 'Z' {
out[i] += 'a' - 'A'
}
}
return string(out)
}
// validHostname reports whether host is a valid hostname that can be matched or
// matched against according to RFC 6125 2.2, with some leniency to accommodate
// legacy values.
func validHostname(host string, isPattern bool) bool {
if !isPattern {
host = strings.TrimSuffix(host, ".")
}
if len(host) == 0 {
return false
}
for i, part := range strings.Split(host, ".") {
if part == "" {
// Empty label.
return false
}
if isPattern && i == 0 && part == "*" {
// Only allow full left-most wildcards, as those are the only ones
// we match, and matching literal '*' characters is probably never
// the expected behavior.
continue
}
for j, c := range part {
if 'a' <= c && c <= 'z' {
continue
}
if '0' <= c && c <= '9' {
continue
}
if 'A' <= c && c <= 'Z' {
continue
}
if c == '-' && j != 0 {
continue
}
if c == '_' {
// Not a valid character in hostnames, but commonly
// found in deployments outside the WebPKI.
continue
}
return false
}
}
return true
}
// matchExactly - match hostnames
func matchExactly(hostA, hostB string) bool {
// Here checking hostB (i.e. sanMatchRule) is valid hostname and not regex/pattern
if !validHostname(hostB, false) {
return false
}
return hostA == hostB
}
// matchHostnamesWithRegexp - To match the san rules with regexp
func matchHostnamesWithRegexp(dnsName, sanMatchRule string) bool {
defer func() bool {
if err := recover(); err != nil {
log.Warn().Msgf("panic occurred: %v", err)
}
return false
}()
if len(dnsName) == 0 || len(sanMatchRule) == 0 {
return false
}
re := regexp.MustCompile(sanMatchRule)
return re.MatchString(dnsName)
}
================================================
FILE: apis/configwatch_test.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package apis
import (
"reflect"
"testing"
)
func TestMatchHostnamesWithRegexp(t *testing.T) {
type args struct {
dnsName string
sanMatchRule string
}
tests := []struct {
name string
args args
want bool
wantErr bool
}{
{
name: "EmptyCheck",
args: args{dnsName: "", sanMatchRule: ""},
want: false,
wantErr: false,
},
{
name: "LengthMissMatchCheck",
args: args{dnsName: "l3afd-lfn.us.l3af.io", sanMatchRule: "l3afd-lfn.l3af.io"},
want: false,
wantErr: false,
},
{
name: "LengthMatchCheck",
args: args{dnsName: "l3afd-lfn.l3af.io", sanMatchRule: "l3afd-lfn.l3af.io"},
want: true,
wantErr: false,
},
{
name: "LengthMatchPatternMissCheck",
args: args{dnsName: "l3afd-us.l3af.io", sanMatchRule: "l3afd-lf.l3af.io"},
want: false,
wantErr: false,
},
{
name: "PatternMatchCheck",
args: args{dnsName: "l3afd-*.l3af.io", sanMatchRule: "l3afd-lfn.l3af.io"},
want: false,
wantErr: false,
},
{
name: "PatternMissMatchCheck",
args: args{dnsName: "*l3afd-.l3af.io", sanMatchRule: "l3afd-lfn.l3af.io"},
want: false,
wantErr: true,
},
{
name: "PatternRegExMatchCheck",
args: args{dnsName: "asnl3afd-lfn.l3af.io", sanMatchRule: ".*l3afd-lfn.l3af.io"},
want: true,
wantErr: false,
},
{
name: "PatternRegExExactMatchCheck",
args: args{dnsName: "l3afd-dev.l3af.io", sanMatchRule: "^dev.l3af.io$"},
want: false,
wantErr: false,
},
{
name: "PatternRegExFindMatch",
args: args{dnsName: "l3afd-dev.l3af.io", sanMatchRule: "dev.l3af.io"},
want: true,
wantErr: false,
},
{
name: "PatternRegExFindMatchPattern",
args: args{dnsName: "l3afd-dev-10.l3af.io", sanMatchRule: "^l3afd-dev-[0-9][0-9]\\.l3af\\.io$"},
want: true,
wantErr: false,
},
{
name: "PatternRegExLowerCaseMatch",
args: args{dnsName: "l3afd-dev-a0.l3af.io", sanMatchRule: "^l3afd-dev-[a-z][0-9]\\.l3af\\.io$"},
want: true,
wantErr: false,
},
{
name: "PatternRegExUpperCaseMatch",
args: args{dnsName: "l3afd-dev-A0.l3af.io", sanMatchRule: "^l3afd-dev-[A-Z][0-9]\\.l3af\\.io$"},
want: true,
wantErr: false,
},
{
name: "PatternRegExPanicCheck",
args: args{dnsName: "l3afd-dev-A0.l3af.io", sanMatchRule: "*l3afd-dev.l3af.io"},
want: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := matchHostnamesWithRegexp(tt.args.dnsName, tt.args.sanMatchRule)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("matchHostnamesWithRegexp() = %v, want %v", got, tt.want)
}
})
}
}
func TestMatchExactly(t *testing.T) {
type args struct {
hostA string
hostB string
}
tests := []struct {
name string
args args
want bool
wantErr bool
}{
{
name: "EmptyCheck",
args: args{hostA: "", hostB: ""},
want: false,
wantErr: false,
},
{
name: "ExactMatchCheck",
args: args{hostA: "l3afd-lfn.l3af.io", hostB: "l3afd-lfn.l3af.io"},
want: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := matchExactly(tt.args.hostA, tt.args.hostB)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("matchHostnames() = %v, want %v", got, tt.want)
}
})
}
}
func TestToLowerCaseASCII(t *testing.T) {
type args struct {
in string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "EmptyCheck",
args: args{in: ""},
want: "",
wantErr: false,
},
{
name: "RegexLowerCheck",
args: args{in: "^l3afd-dev-[0-9][0-9]\\.l3af\\.io$"},
want: "^l3afd-dev-[0-9][0-9]\\.l3af\\.io$",
wantErr: false,
},
{
name: "RegexUpperValueCheck",
args: args{in: "^L3AFd-dev-[0-9][0-9]\\.l3af\\.io$"},
want: "^l3afd-dev-[0-9][0-9]\\.l3af\\.io$",
wantErr: false,
},
{
name: "RegexLowerCheckRuneError",
args: args{in: "^�l3afd-dev-[0-9][0-9]\\.l3af\\.io$"},
want: "^�l3afd-dev-[0-9][0-9]\\.l3af\\.io$",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := toLowerCaseASCII(tt.args.in)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("matchHostnames() = %v, want %v", got, tt.want)
}
})
}
}
================================================
FILE: apis/handlers/addprog.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package handlers
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/rs/zerolog/log"
"github.com/l3af-project/l3afd/v2/bpfprogs"
"github.com/l3af-project/l3afd/v2/models"
)
// AddEbpfPrograms add new eBPF programs on node
// @Summary Adds new eBPF Programs on node
// @Description Adds new eBPF Programs on node
// @Accept json
// @Produce json
// @Param cfgs body []models.L3afBPFPrograms true "BPF programs"
// @Success 200
// @Router /l3af/configs/v1/add [post]
func AddEbpfPrograms(ctx context.Context, bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
mesg := ""
statusCode := http.StatusOK
w.Header().Add("Content-Type", "application/json")
defer func(mesg *string, statusCode *int) {
w.WriteHeader(*statusCode)
_, err := w.Write([]byte(*mesg))
if err != nil {
log.Warn().Msgf("Failed to write response bytes: %v", err)
}
}(&mesg, &statusCode)
if models.IsReadOnly {
log.Warn().Msgf("We are in between restart please try after some time")
mesg = "We are currently in the middle of a restart. Please attempt again after a while."
return
}
defer DecWriteReq()
IncWriteReq()
if r.Body == nil {
log.Warn().Msgf("Empty request body")
return
}
bodyBuffer, err := io.ReadAll(r.Body)
if err != nil {
mesg = fmt.Sprintf("failed to read request body: %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
var t []models.L3afBPFPrograms
if err := json.Unmarshal(bodyBuffer, &t); err != nil {
mesg = fmt.Sprintf("failed to unmarshal payload: %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
if err := bpfcfg.AddeBPFPrograms(t); err != nil {
mesg = fmt.Sprintf("failed to AddEbpfPrograms : %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
}
}
func IncWriteReq() {
models.StateLock.Lock()
models.CurrentWriteReq++
models.StateLock.Unlock()
}
func DecWriteReq() {
models.StateLock.Lock()
models.CurrentWriteReq--
models.StateLock.Unlock()
}
================================================
FILE: apis/handlers/addprog_test.go
================================================
package handlers
import (
"context"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"github.com/l3af-project/l3afd/v2/bpfprogs"
"github.com/l3af-project/l3afd/v2/config"
"github.com/l3af-project/l3afd/v2/models"
)
const dummypayload string = `[
{
"host_name" : "l3af-local-test",
"iface" : "fakeif0",
"bpf_programs" : {
"xdp_ingress" : [
],
"tc_egress": [
],
"tc_ingress": [
]
}
}
]
`
func Test_addprog(t *testing.T) {
tests := []struct {
name string
Body *strings.Reader
header map[string]string
status int
cfg *bpfprogs.NFConfigs
isreadonly bool
}{
{
name: "NilBody",
Body: nil,
status: http.StatusOK,
isreadonly: false,
cfg: &bpfprogs.NFConfigs{
HostConfig: &config.Config{
L3afConfigStoreFileName: filepath.FromSlash("../../testdata/Test_l3af-config.json"),
},
},
},
{
name: "FailedToUnmarshal",
Body: strings.NewReader("Something"),
status: http.StatusInternalServerError,
header: map[string]string{},
isreadonly: false,
cfg: &bpfprogs.NFConfigs{
HostConfig: &config.Config{
L3afConfigStoreFileName: filepath.FromSlash("../../testdata/Test_l3af-config.json"),
},
},
},
{
name: "EmptyInput",
Body: strings.NewReader("[]"),
header: map[string]string{
"Content-Type": "application/json",
},
isreadonly: false,
cfg: &bpfprogs.NFConfigs{
HostConfig: &config.Config{
L3afConfigStoreFileName: filepath.FromSlash("../../testdata/Test_l3af-config.json"),
},
},
status: http.StatusOK,
},
{
name: "UnknownHostName",
Body: strings.NewReader(dummypayload),
status: http.StatusInternalServerError,
header: map[string]string{},
cfg: &bpfprogs.NFConfigs{
HostName: "dummy",
HostConfig: &config.Config{
L3afConfigStoreFileName: filepath.FromSlash("../../testdata/Test_l3af-config.json"),
},
},
isreadonly: false,
},
{
name: "InReadonly",
Body: nil,
header: map[string]string{
"Content-Type": "application/json",
},
isreadonly: true,
cfg: nil,
status: http.StatusOK,
},
}
for _, tt := range tests {
var req *http.Request
if tt.Body == nil {
req, _ = http.NewRequest("POST", "/l3af/configs/v1/add", nil)
} else {
req, _ = http.NewRequest("POST", "/l3af/configs/v1/add", tt.Body)
}
for key, val := range tt.header {
req.Header.Set(key, val)
}
models.IsReadOnly = tt.isreadonly
rr := httptest.NewRecorder()
handler := AddEbpfPrograms(context.Background(), tt.cfg)
handler.ServeHTTP(rr, req)
if rr.Code != tt.status {
models.IsReadOnly = false
t.Error("AddEbpfPrograms Failed")
}
models.IsReadOnly = false
}
}
================================================
FILE: apis/handlers/deleteprog.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package handlers
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/rs/zerolog/log"
"github.com/l3af-project/l3afd/v2/bpfprogs"
"github.com/l3af-project/l3afd/v2/models"
)
// DeleteEbpfPrograms remove eBPF programs on node
// @Summary Removes eBPF Programs on node
// @Description Removes eBPF Programs on node
// @Accept json
// @Produce json
// @Param cfgs body []models.L3afBPFProgramNames true "BPF program names"
// @Success 200
// @Router /l3af/configs/v1/delete [post]
func DeleteEbpfPrograms(ctx context.Context, bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
mesg := ""
statusCode := http.StatusOK
w.Header().Add("Content-Type", "application/json")
defer func(mesg *string, statusCode *int) {
w.WriteHeader(*statusCode)
_, err := w.Write([]byte(*mesg))
if err != nil {
log.Warn().Msgf("Failed to write response bytes: %v", err)
}
}(&mesg, &statusCode)
if models.IsReadOnly {
log.Warn().Msgf("We are in between restart please try after some time")
mesg = "We are currently in the middle of a restart. Please attempt again after a while."
return
}
defer DecWriteReq()
IncWriteReq()
if r.Body == nil {
log.Warn().Msgf("Empty request body")
return
}
bodyBuffer, err := io.ReadAll(r.Body)
if err != nil {
mesg = fmt.Sprintf("failed to read request body: %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
var t []models.L3afBPFProgramNames
if err := json.Unmarshal(bodyBuffer, &t); err != nil {
mesg = fmt.Sprintf("failed to unmarshal payload: %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
if err := bpfcfg.DeleteEbpfPrograms(t); err != nil {
mesg = fmt.Sprintf("failed to DeleteEbpfPrograms : %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
}
}
================================================
FILE: apis/handlers/deleteprog_test.go
================================================
package handlers
import (
"context"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"github.com/l3af-project/l3afd/v2/bpfprogs"
"github.com/l3af-project/l3afd/v2/config"
"github.com/l3af-project/l3afd/v2/models"
)
const payloadfordelete string = `[
{
"host_name": "l3af-local-test",
"iface": "fakeif0",
"bpf_programs": {
"xdp_ingress": [
"ratelimiting",
"connection-limit"
]
}
}
]
`
func Test_DeleteEbpfPrograms(t *testing.T) {
tests := []struct {
name string
Body *strings.Reader
header map[string]string
status int
cfg *bpfprogs.NFConfigs
isreadonly bool
}{
{
name: "NilBody",
Body: nil,
status: http.StatusOK,
isreadonly: false,
cfg: &bpfprogs.NFConfigs{
HostConfig: &config.Config{
L3afConfigStoreFileName: filepath.FromSlash("../../testdata/Test_l3af-config.json"),
},
},
},
{
name: "FailedToUnmarshal",
Body: strings.NewReader("Something"),
status: http.StatusInternalServerError,
header: map[string]string{},
isreadonly: false,
cfg: &bpfprogs.NFConfigs{
HostConfig: &config.Config{
L3afConfigStoreFileName: filepath.FromSlash("../../testdata/Test_l3af-config.json"),
},
},
},
{
name: "EmptyInput",
Body: strings.NewReader(`[]`),
header: map[string]string{
"Content-Type": "application/json",
},
isreadonly: false,
cfg: &bpfprogs.NFConfigs{
HostConfig: &config.Config{
L3afConfigStoreFileName: filepath.FromSlash("../../testdata/Test_l3af-config.json"),
},
},
status: http.StatusOK,
},
{
name: "UnknownHostName",
Body: strings.NewReader(payloadfordelete),
status: http.StatusInternalServerError,
header: map[string]string{},
isreadonly: false,
cfg: &bpfprogs.NFConfigs{
HostName: "dummy",
HostConfig: &config.Config{
L3afConfigStoreFileName: filepath.FromSlash("../../testdata/Test_l3af-config.json"),
},
},
},
{
name: "InReadonly",
Body: nil,
status: http.StatusOK,
header: map[string]string{
"Content-Type": "application/json",
},
isreadonly: true,
cfg: nil,
},
}
for _, tt := range tests {
var req *http.Request
if tt.Body == nil {
req, _ = http.NewRequest("POST", "/l3af/configs/v1/delete", nil)
} else {
req, _ = http.NewRequest("POST", "/l3af/configs/v1/delete", tt.Body)
}
for key, val := range tt.header {
req.Header.Set(key, val)
}
models.IsReadOnly = tt.isreadonly
rr := httptest.NewRecorder()
handler := DeleteEbpfPrograms(context.Background(), tt.cfg)
handler.ServeHTTP(rr, req)
if rr.Code != tt.status {
models.IsReadOnly = false
t.Error("DeleteEbpfPrograms Failed")
}
models.IsReadOnly = false
}
}
================================================
FILE: apis/handlers/getconfig.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package handlers
import (
"encoding/json"
"net/http"
chi "github.com/go-chi/chi/v5"
"github.com/l3af-project/l3afd/v2/bpfprogs"
"github.com/rs/zerolog/log"
)
var bpfcfgs *bpfprogs.NFConfigs
func InitConfigs(cfgs *bpfprogs.NFConfigs) error {
bpfcfgs = cfgs
return nil
}
// GetConfig Returns details of the configuration of eBPF Programs for a given interface
// @Summary Returns details of the configuration of eBPF Programs for a given interface
// @Description Returns details of the configuration of eBPF Programs for a given interface
// @Accept json
// @Produce json
// @Param iface path string true "interface name"
// @Success 200
// @Router /l3af/configs/v1/{iface} [get]
func GetConfig(w http.ResponseWriter, r *http.Request) {
mesg := ""
statusCode := http.StatusOK
w.Header().Add("Content-Type", "application/json")
defer func(mesg *string, statusCode *int) {
w.WriteHeader(*statusCode)
_, err := w.Write([]byte(*mesg))
if err != nil {
log.Warn().Msgf("Failed to write response bytes: %v", err)
}
}(&mesg, &statusCode)
iface := chi.URLParam(r, "iface")
if len(iface) == 0 {
mesg = "iface value is empty"
log.Error().Msg(mesg)
statusCode = http.StatusBadRequest
return
}
resp, err := json.MarshalIndent(bpfcfgs.EBPFPrograms(iface), "", " ")
if err != nil {
mesg = "internal server error"
log.Error().Msgf("failed to marshal response: %v", err)
statusCode = http.StatusInternalServerError
return
}
mesg = string(resp)
}
// GetConfigAll Returns details of the configuration of eBPF Programs for all interfaces on a node
// @Summary Returns details of the configuration of eBPF Programs for all interfaces on a node
// @Description Returns details of the configuration of eBPF Programs for all interfaces on a node
// @Accept json
// @Produce json
// @Success 200
// @Router /l3af/configs/v1 [get]
func GetConfigAll(w http.ResponseWriter, r *http.Request) {
mesg := ""
statusCode := http.StatusOK
w.Header().Add("Content-Type", "application/json")
defer func(mesg *string, statusCode *int) {
w.WriteHeader(*statusCode)
_, err := w.Write([]byte(*mesg))
if err != nil {
log.Warn().Msgf("Failed to write response bytes: %v", err)
}
}(&mesg, &statusCode)
resp, err := json.MarshalIndent(bpfcfgs.EBPFProgramsAll(), "", " ")
if err != nil {
mesg = "internal server error"
log.Error().Msgf("failed to marshal response: %v", err)
statusCode = http.StatusInternalServerError
return
}
mesg = string(resp)
}
================================================
FILE: apis/handlers/getconfig_test.go
================================================
package handlers
import (
"container/list"
"context"
"net/http"
"net/http/httptest"
"testing"
chi "github.com/go-chi/chi/v5"
"github.com/l3af-project/l3afd/v2/bpfprogs"
)
func Test_GetConfig(t *testing.T) {
tests := []struct {
name string
iface string
status int
cfg *bpfprogs.NFConfigs
}{
{
name: "EmptyInterfaceInRequest",
iface: "",
status: http.StatusBadRequest,
cfg: &bpfprogs.NFConfigs{},
},
{
name: "GoodInput",
iface: "fakeif0",
status: http.StatusOK,
cfg: &bpfprogs.NFConfigs{
IngressXDPBpfs: map[string]*list.List{"fakeif0": nil},
IngressTCBpfs: map[string]*list.List{"fakeif0": nil},
EgressTCBpfs: map[string]*list.List{"fakeif0": nil},
},
},
}
for _, tt := range tests {
req, _ := http.NewRequest("GET", "l3af/configs/v1/"+tt.iface, nil)
rctx := chi.NewRouteContext()
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
rctx.URLParams.Add("iface", tt.iface)
rr := httptest.NewRecorder()
handler := http.HandlerFunc(GetConfig)
InitConfigs(tt.cfg)
handler.ServeHTTP(rr, req)
if rr.Code != tt.status {
t.Errorf("GetConfig Failed")
}
}
}
func Test_GetConfigAll(t *testing.T) {
tests := []struct {
name string
status int
cfg *bpfprogs.NFConfigs
}{
{
name: "GoodInput",
status: http.StatusOK,
cfg: &bpfprogs.NFConfigs{},
},
}
for _, tt := range tests {
req, _ := http.NewRequest("GET", "l3af/configs/v1", nil)
rr := httptest.NewRecorder()
handler := http.HandlerFunc(GetConfigAll)
InitConfigs(tt.cfg)
handler.ServeHTTP(rr, req)
if rr.Code != tt.status {
t.Errorf("GetConfigAll Failed")
}
}
}
================================================
FILE: apis/handlers/restart_linux.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package handlers
import (
"encoding/gob"
"encoding/json"
"fmt"
"io"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"net/http"
"github.com/rs/zerolog/log"
"github.com/l3af-project/l3afd/v2/bpfprogs"
"github.com/l3af-project/l3afd/v2/models"
"github.com/l3af-project/l3afd/v2/pidfile"
"github.com/l3af-project/l3afd/v2/restart"
)
// HandleRestart will start new instance of l3afd provided by payload
// @Summary this api will start new instance of l3afd provided by payload
// @Description this api will start new instance of l3afd provided by payload
// @Accept json
// @Produce json
// @Param cfgs body []models.L3afBPFPrograms true "BPF programs"
// @Success 200
// @Router /l3af/configs/v1/restart [put]
func HandleRestart(bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
mesg := ""
statusCode := http.StatusOK
w.Header().Add("Content-Type", "application/json")
defer func(mesg *string, statusCode *int) {
w.WriteHeader(*statusCode)
_, err := w.Write([]byte(*mesg))
if err != nil {
log.Warn().Msgf("Failed to write response bytes: %v", err)
}
}(&mesg, &statusCode)
if models.IsReadOnly {
log.Warn().Msgf("We are in between restart please try after some time")
mesg = "We are currently in the middle of a restart. Please attempt again after a while."
statusCode = http.StatusInternalServerError
return
}
if r.Body == nil {
mesg = "nil request body"
statusCode = http.StatusInternalServerError
log.Warn().Msgf("Empty request body")
return
}
bodyBuffer, err := io.ReadAll(r.Body)
if err != nil {
mesg = fmt.Sprintf("failed to read request body: %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
var t models.RestartConfig
if err := json.Unmarshal(bodyBuffer, &t); err != nil {
mesg = fmt.Sprintf("failed to unmarshal payload: %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
machineHostname, err := os.Hostname()
if err != nil {
mesg = "failed to get os hostname"
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
if machineHostname != t.HostName {
mesg = "this api request is not for provided host"
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
defer func() {
models.IsReadOnly = false
}()
models.IsReadOnly = true
// complete active requests
for {
models.StateLock.Lock()
if models.CurrentWriteReq == 0 {
models.StateLock.Unlock()
break
}
models.StateLock.Unlock()
time.Sleep(5 * time.Millisecond)
}
// Now our system is in Readonly state
oldCfgPath, err := restart.ReadSymlink(filepath.Join(bpfcfg.HostConfig.BasePath, "latest/l3afd.cfg"))
if err != nil {
mesg = fmt.Sprintf("failed read symlink %v with error: %v", filepath.Join(bpfcfg.HostConfig.BasePath, "latest/l3afd.cfg"), err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
oldBinPath, err := restart.ReadSymlink(filepath.Join(bpfcfg.HostConfig.BasePath, "latest/l3afd"))
if err != nil {
mesg = fmt.Sprintf("failed read symlink %v with error: %v", filepath.Join(bpfcfg.HostConfig.BasePath, "latest/l3afd"), err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
// /usr/local/l3afd/v2.0.0/l3afd/l3afd --> v2.0.0/l3afd/l3afd --> v2.0.0
oldVersion := strings.Split(strings.Trim(oldBinPath, bpfcfg.HostConfig.BasePath+"/"), "/")[0]
if _, ok := models.AvailableVersions[t.Version]; !ok {
mesg = "invalid version to upgrade"
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
err = restart.GetNewVersion(models.L3AFDRestartArtifactName, oldVersion, models.AvailableVersions[t.Version], bpfcfg.HostConfig)
if err != nil {
mesg = fmt.Sprintf("failed to get new version: %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
err = restart.RollBackSymlink(oldCfgPath, oldBinPath, oldVersion, models.AvailableVersions[t.Version], bpfcfg.HostConfig)
mesg = mesg + fmt.Sprintf("rollback of symlinks failed: %v", err)
return
}
bpfProgs := bpfcfg.GetL3AFHOSTDATA()
ln, err := net.Listen("unix", models.HostSock)
if err != nil {
log.Err(err)
err = restart.RollBackSymlink(oldCfgPath, oldBinPath, oldVersion, models.AvailableVersions[t.Version], bpfcfg.HostConfig)
mesg = mesg + fmt.Sprintf("rollback of symlinks failed: %v", err)
statusCode = http.StatusInternalServerError
return
}
srvError := make(chan error, 1)
go func() {
defer ln.Close()
conn, err := ln.Accept()
if err != nil {
log.Err(err)
srvError <- err
return
}
defer conn.Close()
encoder := gob.NewEncoder(conn)
err = encoder.Encode(bpfProgs)
if err != nil {
log.Err(err)
srvError <- err
return
}
srvError <- nil
}()
files := make([]*os.File, 3)
srvToIndex := make(map[string]int)
srvToIndex["stat_http"] = 0
srvToIndex["main_http"] = 1
srvToIndex["debug_http"] = 2
isErr := false
models.AllNetListeners.Range(func(srvr, listr interface{}) bool { // iterate over the map
srv, _ := srvr.(string)
lis, _ := listr.(*net.TCPListener)
idx := srvToIndex[srv]
lf, err := lis.File()
if err != nil {
log.Error().Msgf("%v", err)
err = restart.RollBackSymlink(oldCfgPath, oldBinPath, oldVersion, models.AvailableVersions[t.Version], bpfcfg.HostConfig)
mesg = mesg + fmt.Sprintf("rollback of symlinks failed: %v", err)
statusCode = http.StatusInternalServerError
isErr = true
return false
}
newFile := os.NewFile(uintptr(lf.Fd()), "dupFdlistner"+strconv.Itoa(idx))
files[idx] = newFile
return true
})
if isErr {
return
}
cmd := exec.Command(filepath.Join(bpfcfg.HostConfig.BasePath, "latest/l3afd"), "--config", filepath.Join(bpfcfg.HostConfig.BasePath, "latest/l3afd.cfg"))
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true,
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.ExtraFiles = files
err = bpfcfg.StopAllProbesAndUserPrograms()
if err != nil {
log.Err(err)
err = bpfcfg.StartAllUserProgramsAndProbes()
if err != nil {
log.Error().Msgf("%v", err)
mesg = mesg + fmt.Sprintf("unable to start all userprograms and probes: %v", err)
}
err = restart.RollBackSymlink(oldCfgPath, oldBinPath, oldVersion, models.AvailableVersions[t.Version], bpfcfg.HostConfig)
mesg = mesg + fmt.Sprintf("rollback of symlinks failed: %v", err)
statusCode = http.StatusInternalServerError
return
}
log.Info().Msg("Starting child Process")
err = cmd.Start()
if err != nil {
log.Error().Msgf("%v", err)
mesg = mesg + fmt.Sprintf("unable to start new instance %v", err)
err = cmd.Process.Kill()
if err != nil {
log.Error().Msgf("%v", err)
mesg = mesg + fmt.Sprintf("unable to kill the new instance %v", err)
}
err = bpfcfg.StartAllUserProgramsAndProbes()
if err != nil {
log.Error().Msgf("%v", err)
mesg = mesg + fmt.Sprintf("unable to start all userprograms and probes: %v", err)
}
err = pidfile.CreatePID(bpfcfg.HostConfig.PIDFilename)
if err != nil {
log.Error().Msgf("%v", err)
mesg = mesg + fmt.Sprintf("unable to create pid file: %v", err)
}
err = restart.RollBackSymlink(oldCfgPath, oldBinPath, oldVersion, models.AvailableVersions[t.Version], bpfcfg.HostConfig)
if err != nil {
mesg = mesg + fmt.Sprintf("rollback of symlink failed: %v", err)
}
statusCode = http.StatusInternalServerError
return
}
NewProcessStatus := make(chan string)
go func() {
// I need to write client code for reading the state of new process
var err error
var conn net.Conn
f := false
for i := 1; i <= bpfcfg.HostConfig.TimetoRestart; i++ {
conn, err = net.Dial("unix", models.StateSock)
if err == nil {
f = true
break
}
log.Info().Msgf("Waiting for socket to be up...")
time.Sleep(time.Second) // sleep for a second before trying again
}
if !f {
conn.Close()
NewProcessStatus <- models.StatusFailed
return
}
defer conn.Close()
decoder := gob.NewDecoder(conn)
var data string
err = decoder.Decode(&data)
if err != nil {
NewProcessStatus <- models.StatusFailed
return
}
NewProcessStatus <- data
}()
// time to bootup
select {
case terr := <-srvError:
if terr != nil {
statusCode = http.StatusInternalServerError
err = cmd.Process.Kill()
if err != nil {
log.Error().Msgf("%v", err)
mesg = mesg + fmt.Sprintf("unable to kill the new instance %v", err)
}
err = bpfcfg.StartAllUserProgramsAndProbes()
if err != nil {
log.Error().Msgf("%v", err)
mesg = mesg + fmt.Sprintf("unable to start all userprograms and probes: %v", err)
}
err = pidfile.CreatePID(bpfcfg.HostConfig.PIDFilename)
if err != nil {
log.Error().Msgf("%v", err)
mesg = mesg + fmt.Sprintf("unable to create pid file: %v", err)
}
err = restart.RollBackSymlink(oldCfgPath, oldBinPath, oldVersion, models.AvailableVersions[t.Version], bpfcfg.HostConfig)
if err != nil {
mesg = mesg + fmt.Sprintf("rollback of symlink failed: %v", err)
}
statusCode = http.StatusInternalServerError
log.Err(terr)
return
}
break
default:
time.Sleep(time.Second)
}
st := <-NewProcessStatus
if st == models.StatusFailed {
err = cmd.Process.Kill()
if err != nil {
log.Error().Msgf("%v", err)
mesg = mesg + fmt.Sprintf("unable to kill the new instance %v", err)
}
err = bpfcfg.StartAllUserProgramsAndProbes()
if err != nil {
log.Error().Msgf("%v", err)
mesg = mesg + fmt.Sprintf("unable to start all userprograms and probes: %v", err)
}
err = pidfile.CreatePID(bpfcfg.HostConfig.PIDFilename)
if err != nil {
log.Error().Msgf("%v", err)
mesg = mesg + fmt.Sprintf("unable to create pid file: %v", err)
}
err = restart.RollBackSymlink(oldCfgPath, oldBinPath, oldVersion, models.AvailableVersions[t.Version], bpfcfg.HostConfig)
if err != nil {
mesg = mesg + fmt.Sprintf("rollback of symlink failed: %v", err)
}
statusCode = http.StatusInternalServerError
return
} else {
log.Info().Msgf("doing exiting old process")
models.CloseForRestart <- struct{}{}
}
}
}
================================================
FILE: apis/handlers/restart_linux_test.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package handlers
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/l3af-project/l3afd/v2/models"
)
func Test_HandleRestart(t *testing.T) {
var req *http.Request
req, _ = http.NewRequest("PUT", "/l3af/configs/v1/restart", nil)
req.Header.Set("Content-Type", "application/json")
models.IsReadOnly = true
rr := httptest.NewRecorder()
handler := HandleRestart(nil)
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusInternalServerError {
models.IsReadOnly = false
t.Error("Handle restart Failed")
}
models.IsReadOnly = false
}
================================================
FILE: apis/handlers/restart_windows.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package handlers
import (
"net/http"
"github.com/rs/zerolog/log"
"github.com/l3af-project/l3afd/v2/bpfprogs"
)
// HandleRestart Store meta data about ebpf programs and exit
// @Summary Store meta data about ebpf programs and exit
// @Description Store meta data about ebpf programs and exit
// @Accept json
// @Produce json
// @Param cfgs body []models.L3afBPFPrograms true "BPF programs"
// @Success 200
// @Router /l3af/configs/v1/restart [put]
func HandleRestart(bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
mesg := ""
statusCode := http.StatusOK
w.Header().Add("Content-Type", "application/json")
defer func(mesg *string, statusCode *int) {
w.WriteHeader(*statusCode)
_, err := w.Write([]byte(*mesg))
if err != nil {
log.Warn().Msgf("Failed to write response bytes: %v", err)
}
}(&mesg, &statusCode)
mesg = "Graceful restart is only supported for linux as of now"
}
}
================================================
FILE: apis/handlers/restart_windows_test.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package handlers
import (
"net/http"
"net/http/httptest"
"testing"
)
func Test_HandleRestart(t *testing.T) {
var req *http.Request
req, _ = http.NewRequest("PUT", "/l3af/configs/v1/restart", nil)
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler := HandleRestart(nil)
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Error("Handle restart Failed")
}
}
================================================
FILE: apis/handlers/updateconfig.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package handlers
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/rs/zerolog/log"
"github.com/l3af-project/l3afd/v2/bpfprogs"
"github.com/l3af-project/l3afd/v2/models"
)
// UpdateConfig Update eBPF Programs configuration
// @Summary Update eBPF Programs configuration
// @Description Update eBPF Programs configuration
// @Accept json
// @Produce json
// @Param cfgs body []models.L3afBPFPrograms true "BPF programs"
// @Success 200
// @Router /l3af/configs/v1/update [post]
func UpdateConfig(ctx context.Context, bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
mesg := ""
statusCode := http.StatusOK
w.Header().Add("Content-Type", "application/json")
defer func(mesg *string, statusCode *int) {
w.WriteHeader(*statusCode)
_, err := w.Write([]byte(*mesg))
if err != nil {
log.Warn().Msgf("Failed to write response bytes: %v", err)
}
}(&mesg, &statusCode)
if models.IsReadOnly {
log.Warn().Msgf("We are in between restart please try after some time")
mesg = "We are currently in the middle of a restart. Please attempt again after a while."
return
}
defer DecWriteReq()
IncWriteReq()
if r.Body == nil {
log.Warn().Msgf("Empty request body")
return
}
bodyBuffer, err := io.ReadAll(r.Body)
if err != nil {
mesg = fmt.Sprintf("failed to read request body: %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
var t []models.L3afBPFPrograms
if err := json.Unmarshal(bodyBuffer, &t); err != nil {
mesg = fmt.Sprintf("failed to unmarshal payload: %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
if err := bpfcfg.DeployeBPFPrograms(t); err != nil {
mesg = fmt.Sprintf("failed to deploy ebpf programs: %v", err)
log.Error().Msg(mesg)
statusCode = http.StatusInternalServerError
return
}
}
}
================================================
FILE: apis/handlers/updateconfig_test.go
================================================
package handlers
import (
"context"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"github.com/l3af-project/l3afd/v2/bpfprogs"
"github.com/l3af-project/l3afd/v2/config"
"github.com/l3af-project/l3afd/v2/models"
)
func Test_UpdateConfig(t *testing.T) {
tests := []struct {
name string
Body *strings.Reader
header map[string]string
status int
cfg *bpfprogs.NFConfigs
isreadonly bool
}{
{
name: "NilBody",
Body: nil,
status: http.StatusOK,
isreadonly: false,
cfg: &bpfprogs.NFConfigs{
HostConfig: &config.Config{
L3afConfigStoreFileName: filepath.FromSlash("../../testdata/Test_l3af-config.json"),
},
},
},
{
name: "FailedToUnmarshal",
Body: strings.NewReader("Something"),
status: http.StatusInternalServerError,
header: map[string]string{},
isreadonly: false,
cfg: &bpfprogs.NFConfigs{
HostConfig: &config.Config{
L3afConfigStoreFileName: filepath.FromSlash("../../testdata/Test_l3af-config.json"),
},
},
},
{
name: "UnknownHostName",
Body: strings.NewReader(dummypayload),
status: http.StatusInternalServerError,
header: map[string]string{},
isreadonly: false,
cfg: &bpfprogs.NFConfigs{
HostName: "dummy",
HostConfig: &config.Config{
L3afConfigStoreFileName: filepath.FromSlash("../../testdata/Test_l3af-config.json"),
},
},
},
{
name: "InReadonly",
Body: nil,
status: http.StatusOK,
header: map[string]string{
"Content-Type": "application/json",
},
isreadonly: true,
cfg: nil,
},
}
for _, tt := range tests {
var req *http.Request
if tt.Body == nil {
req, _ = http.NewRequest("POST", "/l3af/configs/v1/update", nil)
} else {
req, _ = http.NewRequest("POST", "/l3af/configs/v1/update", tt.Body)
}
for key, val := range tt.header {
req.Header.Set(key, val)
}
models.IsReadOnly = tt.isreadonly
rr := httptest.NewRecorder()
handler := UpdateConfig(context.Background(), tt.cfg)
handler.ServeHTTP(rr, req)
if rr.Code != tt.status {
models.IsReadOnly = false
t.Error("UpdateConfig Failed")
}
models.IsReadOnly = false
}
}
================================================
FILE: apis/routes.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package apis
import (
"context"
"github.com/l3af-project/l3afd/v2/apis/handlers"
"github.com/l3af-project/l3afd/v2/bpfprogs"
"github.com/l3af-project/l3afd/v2/routes"
)
func apiRoutes(ctx context.Context, bpfcfg *bpfprogs.NFConfigs) []routes.Route {
r := []routes.Route{
{
Method: "POST",
Path: "/l3af/configs/{version}/update",
HandlerFunc: handlers.UpdateConfig(ctx, bpfcfg),
},
{
Method: "GET",
Path: "/l3af/configs/{version}/{iface}",
HandlerFunc: handlers.GetConfig,
},
{
Method: "GET",
Path: "/l3af/configs/{version}",
HandlerFunc: handlers.GetConfigAll,
},
{
Method: "POST",
Path: "/l3af/configs/{version}/add",
HandlerFunc: handlers.AddEbpfPrograms(ctx, bpfcfg),
},
{
Method: "POST",
Path: "/l3af/configs/{version}/delete",
HandlerFunc: handlers.DeleteEbpfPrograms(ctx, bpfcfg),
},
{
Method: "PUT",
Path: "/l3af/configs/{version}/restart",
HandlerFunc: handlers.HandleRestart(bpfcfg),
},
}
return r
}
================================================
FILE: bpfprogs/bpf.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
// Package bpfprogs provides primitives for BPF programs / Network Functions.
package bpfprogs
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"container/ring"
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"unsafe"
"github.com/l3af-project/l3afd/v2/config"
"github.com/l3af-project/l3afd/v2/models"
"github.com/l3af-project/l3afd/v2/stats"
"github.com/l3af-project/l3afd/v2/utils"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/rlimit"
ps "github.com/mitchellh/go-ps"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/zerolog/log"
)
var (
execCommand = exec.Command
copyBufPool sync.Pool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}
)
//lint:ignore U1000 avoid false linter error on windows, since this variable is only used in linux code
const executePerm uint32 = 0111
const bpfStatus string = "RUNNING"
// BPF defines run time details for BPFProgram.
type BPF struct {
Program models.BPFProgram
Cmd *exec.Cmd `json:"-"`
FilePath string // Binary file path
RestartCount int // To track restart count
PrevMapNamePath string // Previous Map name with path to link
MapNamePath string // Map name with path
ProgID ebpf.ProgramID // eBPF Program ID
BpfMaps map[string]BPFMap // Config maps passed as map-args, Map name is Key
MetricsBpfMaps map[string]*MetricsBPFMap // Metrics map name+key+aggregator is key
Ctx context.Context `json:"-"`
Done chan bool `json:"-"`
ProgMapCollection *ebpf.Collection `json:"_"` // eBPF Collection reference
ProgMapID ebpf.MapID // Prog map id
PrevProgMapID ebpf.MapID // Prev prog map id
HostConfig *config.Config
Link link.Link `json:"-"` // handle link object
ProbeLinks []*link.Link
}
func NewBpfProgram(ctx context.Context, program models.BPFProgram, conf *config.Config, ifaceName string) *BPF {
var progMapFilePath string
version := utils.ReplaceDotsWithUnderscores(program.Version)
if len(program.MapName) > 0 {
if program.ProgType == models.XDPType {
progMapFilePath = filepath.Join(conf.BpfMapDefaultPath, ifaceName, program.Name, version, program.MapName)
} else if program.ProgType == models.TCType {
progMapFilePath = filepath.Join(conf.BpfMapDefaultPath, models.TCMapPinPath, ifaceName, program.Name, version, program.MapName)
}
if strings.Contains(progMapFilePath, "..") {
log.Error().Msgf("program map file contains relative path %s", progMapFilePath)
return nil
}
}
bpf := &BPF{
Program: program,
RestartCount: 0,
Cmd: nil,
FilePath: "",
BpfMaps: make(map[string]BPFMap, 0),
MetricsBpfMaps: make(map[string]*MetricsBPFMap, 0),
Ctx: ctx,
Done: nil,
HostConfig: conf,
MapNamePath: progMapFilePath,
}
return bpf
}
// LoadRootProgram - Loading the Root Program for a given interface.
func LoadRootProgram(ifaceName string, direction string, progType string, conf *config.Config) (*BPF, error) {
log.Info().Msgf("LoadRootProgram iface %s direction %s progType %s", ifaceName, direction, progType)
var rootProgBPF *BPF
switch progType {
case models.XDPType:
version := utils.ReplaceDotsWithUnderscores(conf.XDPRootVersion)
rootProgBPF = &BPF{
Program: models.BPFProgram{
Name: conf.XDPRootPackageName,
Artifact: conf.XDPRootArtifact,
MapName: conf.XDPRootMapName,
Version: conf.XDPRootVersion,
UserProgramDaemon: false,
AdminStatus: models.Enabled,
ProgType: models.XDPType,
SeqID: 0,
StartArgs: map[string]interface{}{},
StopArgs: map[string]interface{}{},
StatusArgs: map[string]interface{}{},
ObjectFile: conf.XDPRootObjectFile,
EntryFunctionName: conf.XDPRootEntryFunctionName,
},
RestartCount: 0,
Cmd: nil,
FilePath: "",
PrevMapNamePath: "",
HostConfig: conf,
MapNamePath: filepath.Join(conf.BpfMapDefaultPath, ifaceName, conf.XDPRootPackageName, version, conf.XDPRootMapName),
Link: nil,
}
case models.TCType:
rootProgBPF = &BPF{
Program: models.BPFProgram{
Name: conf.TCRootPackageName,
Artifact: conf.TCRootArtifact,
Version: conf.TCRootVersion,
UserProgramDaemon: false,
AdminStatus: models.Enabled,
ProgType: models.TCType,
StartArgs: map[string]interface{}{},
StopArgs: map[string]interface{}{},
StatusArgs: map[string]interface{}{},
},
RestartCount: 0,
Cmd: nil,
FilePath: "",
PrevMapNamePath: "",
HostConfig: conf,
}
if direction == models.IngressType {
rootProgBPF.Program.MapName = conf.TCRootIngressMapName
rootProgBPF.Program.ObjectFile = conf.TCRootIngressObjectFile
rootProgBPF.Program.EntryFunctionName = conf.TCRootIngressEntryFunctionName
} else if direction == models.EgressType {
rootProgBPF.Program.MapName = conf.TCRootEgressMapName
rootProgBPF.Program.ObjectFile = conf.TCRootEgressObjectFile
rootProgBPF.Program.EntryFunctionName = conf.TCRootEgressEntryFunctionName
}
version := utils.ReplaceDotsWithUnderscores(rootProgBPF.Program.Version)
rootProgBPF.MapNamePath = filepath.Join(conf.BpfMapDefaultPath, models.TCMapPinPath, ifaceName, rootProgBPF.Program.Name, version, rootProgBPF.Program.MapName)
default:
return nil, fmt.Errorf("unknown direction %s for root program in iface %s", direction, ifaceName)
}
if err := rootProgBPF.VerifyAndGetArtifacts(conf); err != nil {
log.Error().Err(err).Msg("failed to get root artifacts")
return nil, err
}
// On l3afd crashing scenario verify root program are unloaded properly by checking existence of persisted maps
// if map file exists then root program didn't clean up pinned map files
if fileExists(rootProgBPF.MapNamePath) {
log.Warn().Msgf("previous instance of root program %s persisted map %s file exists", rootProgBPF.Program.Name, rootProgBPF.MapNamePath)
if err := rootProgBPF.RemoveRootProgMapFile(ifaceName); err != nil {
log.Warn().Err(err).Msgf("previous instance of root program %s map file not removed successfully - %s ", rootProgBPF.Program.Name, rootProgBPF.MapNamePath)
}
}
version := utils.ReplaceDotsWithUnderscores(rootProgBPF.Program.Version)
if progType == models.XDPType {
rlimit.RemoveMemlock()
if err := rootProgBPF.LoadXDPAttachProgram(ifaceName); err != nil {
return nil, fmt.Errorf("failed to load xdp root program on iface %s name %s direction %s with err %w", ifaceName, rootProgBPF.Program.Name, direction, err)
}
// pin the program also
progPinPath := utils.ProgPinPath(rootProgBPF.HostConfig.BpfMapDefaultPath, ifaceName, rootProgBPF.Program.Name, version, rootProgBPF.Program.EntryFunctionName, rootProgBPF.Program.ProgType)
if err := rootProgBPF.ProgMapCollection.Programs[rootProgBPF.Program.EntryFunctionName].Pin(progPinPath); err != nil {
return nil, err
}
} else {
if utils.CheckTCXSupport() {
if err := rootProgBPF.LoadTCXAttachProgram(ifaceName, direction); err != nil {
return nil, fmt.Errorf("failed to load tcx root program on iface %s name %s direction %s with err %w", ifaceName, rootProgBPF.Program.Name, direction, err)
}
} else {
if err := rootProgBPF.LoadTCAttachProgram(ifaceName, direction); err != nil {
return nil, fmt.Errorf("failed to load tc root program on iface %s name %s direction %s with err %w", ifaceName, rootProgBPF.Program.Name, direction, err)
}
}
// pin the program also
progPinPath := utils.ProgPinPath(rootProgBPF.HostConfig.BpfMapDefaultPath, ifaceName, rootProgBPF.Program.Name, version, rootProgBPF.Program.EntryFunctionName, rootProgBPF.Program.ProgType)
if err := rootProgBPF.ProgMapCollection.Programs[rootProgBPF.Program.EntryFunctionName].Pin(progPinPath); err != nil {
return nil, err
}
}
return rootProgBPF, nil
}
// Stop the NF process if running outside l3afd
func StopExternalRunningProcess(processName string) error {
// validate process name
if len(processName) < 1 {
return fmt.Errorf("process name can not be empty")
}
// process names are truncated to 15 chars
psName := processName
if len(processName) > 15 {
psName = processName[:15]
}
myPid := os.Getpid()
processList, err := ps.Processes()
if err != nil {
return fmt.Errorf("failed to fetch processes list with err %w", err)
}
log.Info().Msgf("Searching for process %s and not ppid %d", processName, myPid)
for _, process := range processList {
if strings.Contains(process.Executable(), psName) {
if process.PPid() != myPid {
log.Warn().Msgf("found process id %d name %s ppid %d, stopping it", process.Pid(), process.Executable(), process.PPid())
osProcess, err := os.FindProcess(process.Pid())
if err == nil {
err = osProcess.Kill()
}
if err != nil {
return fmt.Errorf("external BPFProgram stop failed with error: %w", err)
}
}
}
}
return nil
}
// Stop returns the last error seen, but stops bpf program.
// Stops the user programs if any, and unloads the BPF program.
// Clean up all map handles.
// Verify next program pinned map file is removed
func (b *BPF) Stop(ifaceName, ipv4_address, direction string, chain bool) error {
if b.Program.UserProgramDaemon && b.Cmd == nil {
return fmt.Errorf("BPFProgram is not running %s", b.Program.Name)
}
log.Info().Msgf("Stopping BPF Program - %s", b.Program.Name)
// Removing maps
for key, val := range b.BpfMaps {
log.Debug().Msgf("removing BPF maps %s value map %#v", key, val)
delete(b.BpfMaps, key)
}
// Removing Metrics maps
for key, val := range b.MetricsBpfMaps {
log.Debug().Msgf("removing metric bpf maps %s value %#v", key, val)
delete(b.MetricsBpfMaps, key)
}
// Stop BPF configs
if len(b.Program.CmdConfig) > 0 && len(b.Program.ConfigFilePath) > 0 {
log.Info().Msgf("Stopping BPF configs %s ", b.Program.Name)
b.Done <- true
}
// Reset ProgID
b.ProgID = 0
stats.Add(1, stats.BPFStopCount, b.Program.Name, direction, ifaceName, ipv4_address)
// Deleting stale metrics indicates that the ebpf program is not running.
stats.BPFRunning.Delete(prometheus.Labels{"ebpf_program": b.Program.Name, "version": b.Program.Version, "direction": direction, "interface_name": ifaceName, "ipv4_address": ipv4_address})
stats.BPFStartTime.Delete(prometheus.Labels{"ebpf_program": b.Program.Name, "direction": direction, "interface_name": ifaceName, "ipv4_address": ipv4_address})
// Stop User Programs if any
if len(b.Program.CmdStop) < 1 && b.Program.UserProgramDaemon {
// Loaded using user program
if err := b.ProcessTerminate(); err != nil {
return fmt.Errorf("BPFProgram %s process terminate failed with error: %w", b.Program.Name, err)
}
if b.Cmd != nil {
if err := b.Cmd.Wait(); err != nil {
log.Error().Err(err).Msgf("cmd wait at stopping bpf program %s errored", b.Program.Name)
}
b.Cmd = nil
}
} else if len(b.Program.CmdStop) > 0 && b.Program.UserProgramDaemon {
cmd := filepath.Join(b.FilePath, b.Program.CmdStop)
if err := assertExecutable(cmd); err != nil {
return fmt.Errorf("no executable permissions on %s - error %w", b.Program.CmdStop, err)
}
args := make([]string, 0, len(b.Program.StopArgs)<<1)
if len(ifaceName) > 0 {
args = append(args, "--iface="+ifaceName) // detaching from iface
}
if len(direction) > 0 {
args = append(args, "--direction="+direction) // xdpingress or ingress or egress
}
for k, val := range b.Program.StopArgs {
if v, ok := val.(string); !ok {
err := fmt.Errorf("stop args is not a string for the bpf program %s", b.Program.Name)
log.Error().Err(err).Msgf("failed to convert stop args value into string for program %s", b.Program.Name)
return err
} else {
args = append(args, "--"+k+" ="+v)
}
}
log.Info().Msgf("bpf program stop command : %s %v", cmd, args)
prog := execCommand(cmd, args...)
if err := prog.Run(); err != nil {
log.Warn().Err(err).Msgf("l3afd : Failed to stop the program %s", b.Program.CmdStop)
}
b.Cmd = nil
}
// unload the BPF programs
if allInterfaces, err := getHostInterfaces(); err != nil {
errOut := fmt.Errorf("failed get interfaces in Stop Function: %v", err)
log.Error().Err(errOut)
return errOut
} else {
if _, ok := allInterfaces[ifaceName]; ok {
if b.ProgMapCollection != nil {
if err := b.UnloadProgram(ifaceName, direction); err != nil {
return fmt.Errorf("BPFProgram %s unload failed on interface %s with error: %w", b.Program.Name, ifaceName, err)
}
log.Info().Msgf("%s => %s direction => %s - program is unloaded/detached successfully", ifaceName, b.Program.Name, direction)
}
} else {
if err := b.RemovePinnedFiles(ifaceName); err != nil {
log.Error().Err(err).Msgf("stop user program - failed to remove map files %s", b.Program.Name)
return fmt.Errorf("stop user program - failed to remove map files %s", b.Program.Name)
}
}
}
if err := b.VerifyCleanupMaps(chain); err != nil {
log.Error().Err(err).Msgf("stop user program - failed to remove map files %s", b.Program.Name)
return fmt.Errorf("stop user program - failed to remove map files %s", b.Program.Name)
}
return nil
}
// Start returns the last error seen, but starts bpf program.
// Here initially prevprogmap entry is removed and passed to the bpf program
// After starting the user program, will update the kernel progam fd into prevprogram map.
// This method waits till prog fd entry is updated, else returns error assuming kernel program is not loaded.
// It also verifies the next program pinned map is created or not.
func (b *BPF) Start(ifaceName, ipv4_address, direction string, chain bool) error {
if b.FilePath == "" {
return errors.New("no program binary path found")
}
if len(b.Program.CmdStart) > 0 {
// Verify other instance is running
if err := StopExternalRunningProcess(b.Program.CmdStart); err != nil {
return fmt.Errorf("failed to stop external instance of the program %s with error : %w", b.Program.CmdStart, err)
}
}
// Making sure old map entry is removed before passing the prog fd map to the program.
if len(b.PrevMapNamePath) > 0 {
if err := b.RemovePrevProgFD(); err != nil {
log.Error().Err(err).Msgf("ProgramMap %s entry removal failed", b.PrevMapNamePath)
}
}
// Both xdp and tc are loaded using the same mechanism.
if len(b.Program.ObjectFile) > 0 {
if chain {
if err := b.LoadBPFProgramChain(ifaceName, direction); err != nil {
return fmt.Errorf("loading bpf program %s - error %w", b.Program.Name, err)
}
} else {
if err := b.AttachBPFProgram(ifaceName, direction); err != nil {
return fmt.Errorf("attaching bpf program %s - error %w", b.Program.Name, err)
}
}
} else {
log.Info().Msgf("bpf program object file is not defined - %s", b.Program.Name)
}
log.Info().Msgf("successfully Loaded & Attached %s in direction %s on interface %s", b.Program.Name, direction, ifaceName)
// Start user program before loading
if len(b.Program.CmdStart) > 0 {
if err := b.StartUserProgram(ifaceName, direction, chain); err != nil {
return fmt.Errorf("user program startup failed %s - error %w", b.Program.CmdStart, err)
}
}
// making sure program fd map pinned file is created
if err := b.VerifyPinnedProgMap(chain, true); err != nil {
return fmt.Errorf("failed to find pinned file %s %w", b.MapNamePath, err)
}
// BPF map config values
if len(b.Program.MapArgs) > 0 {
if err := b.UpdateBPFMaps(ifaceName, ipv4_address, direction); err != nil {
log.Error().Err(err).Msg("failed to update ebpf program BPF maps")
return fmt.Errorf("failed to update ebpf program BPF maps %w", err)
}
}
// Update args config values
if len(b.Program.UpdateArgs) > 0 {
if err := b.UpdateArgs(ifaceName, ipv4_address, direction); err != nil {
log.Error().Err(err).Msg("failed to update ebpf program config update")
return fmt.Errorf("failed to update ebpf program config update %w", err)
}
}
// Fetch when prev program map is updated only when loaded using user program
if len(b.PrevMapNamePath) > 0 && b.ProgMapCollection == nil {
var err error
// retry 10 times to verify entry is created
for i := 0; i < 10; i++ {
b.ProgID, err = b.GetProgID()
if err == nil {
break
}
log.Warn().Msg("failed to fetch the program ID, retrying after a second ... ")
time.Sleep(1 * time.Second)
}
if err != nil {
log.Error().Err(err).Msg("failed to fetch ebpf program FD")
return fmt.Errorf("failed to fetch ebpf program FD %w", err)
}
}
// BPFconfigs
if len(b.Program.CmdConfig) > 0 && len(b.Program.ConfigFilePath) > 0 {
log.Info().Msgf("eBPF program specific config monitoring - %s", b.Program.ConfigFilePath)
b.Done = make(chan bool)
go b.RunBPFConfigs()
}
stats.Set(float64(time.Now().Unix()), stats.BPFStartTime, b.Program.Name, direction, ifaceName, ipv4_address)
userProgram, bpfProgram, err := b.isRunning()
if !userProgram && !bpfProgram {
log.Error().Err(err).Msg("eBPF program failed to start")
return fmt.Errorf("bpf program %s failed to start %w", b.Program.Name, err)
}
stats.Add(1, stats.BPFStartCount, b.Program.Name, direction, ifaceName, ipv4_address)
log.Info().Msgf("BPF program - %s started Program ID %d", b.Program.Name, uint32(b.ProgID))
return nil
}
// UpdateBPFMaps - Update the config ebpf maps via map arguments
func (b *BPF) UpdateBPFMaps(ifaceName, ipv4_address, direction string) error {
for _, val := range b.Program.MapArgs {
bpfMap, ok := b.BpfMaps[val.Name]
if !ok {
if err := b.AddBPFMap(val.Name); err != nil {
return err
}
bpfMap = b.BpfMaps[val.Name]
}
for _, v := range val.Args {
log.Info().Msgf("Update map args key %v val %v", v.Key, v.Value)
bpfMap.Update(v.Key, v.Value)
}
if err := bpfMap.RemoveMissingKeys(val.Args); err != nil {
return fmt.Errorf("failed to remove missing entries of map %s with err %w", val.Name, err)
}
}
stats.Add(1, stats.BPFUpdateCount, b.Program.Name, direction, ifaceName, ipv4_address)
return nil
}
// Update config arguments using user program
func (b *BPF) UpdateArgs(ifaceName, ipv4_address, direction string) error {
if b.FilePath == "" {
return errors.New("update - no program binary path found")
}
version := utils.ReplaceDotsWithUnderscores(b.Program.Version)
cmd := filepath.Join(b.FilePath, b.Program.CmdUpdate)
// Validate
if err := assertExecutable(cmd); err != nil {
return fmt.Errorf("no executable permissions on %s - error %w", b.Program.CmdUpdate, err)
}
args := make([]string, 0, len(b.Program.UpdateArgs)<<1)
args = append(args, "--iface="+ifaceName) // attaching to interface
args = append(args, "--direction="+direction) // direction xdpingress or ingress or egress
args = append(args, "--cmd="+models.UpdateType) // argument cmd to update configs
args = append(args, "--version="+version) // argument version of ebpf program
if len(b.HostConfig.BPFLogDir) > 1 {
args = append(args, "--log-dir="+b.HostConfig.BPFLogDir)
}
for k, val := range b.Program.UpdateArgs {
if v, ok := val.(string); !ok {
err := fmt.Errorf("update args is not a string for the ebpf program %s", b.Program.Name)
log.Error().Err(err).Msgf("failed to convert update args value into string for program %s", b.Program.Name)
return err
} else {
args = append(args, "--"+k+"="+v)
}
}
log.Info().Msgf("BPF Program update command : %s %v", cmd, args)
UpdateCmd := execCommand(cmd, args...)
if err := UpdateCmd.Start(); err != nil {
stats.Add(1, stats.BPFUpdateFailedCount, b.Program.Name, direction, ifaceName, ipv4_address)
customerr := fmt.Errorf("failed to start : %s %v %w", cmd, args, err)
log.Warn().Err(customerr).Msgf("user mode BPF program failed - %s", b.Program.Name)
return customerr
}
if err := UpdateCmd.Wait(); err != nil {
stats.Add(1, stats.BPFUpdateFailedCount, b.Program.Name, direction, ifaceName, ipv4_address)
return fmt.Errorf("cmd wait at starting of bpf program returned with error %w", err)
}
stats.Add(1, stats.BPFUpdateCount, b.Program.Name, direction, ifaceName, ipv4_address)
log.Info().Msgf("BPF program - %s config updated", b.Program.Name)
return nil
}
// Status of user program is running
func (b *BPF) isRunning() (bool, bool, error) {
userProgram := true
var err error
// CmdStatus should check for user and BPF program
if len(b.Program.CmdStatus) > 1 {
cmd := filepath.Join(b.FilePath, b.Program.CmdStatus)
if err := assertExecutable(cmd); err != nil {
userProgram = false
} else {
args := make([]string, 0, len(b.Program.StatusArgs)<<1)
for k, val := range b.Program.StatusArgs {
if v, ok := val.(string); !ok {
err = fmt.Errorf("status args is not a string for the ebpf program %s", b.Program.Name)
log.Warn().Err(err).Msgf("failed to convert status args value into string for program %s", b.Program.Name)
} else {
args = append(args, "--"+k+" ="+v)
}
}
prog := execCommand(cmd, args...)
var out bytes.Buffer
prog.Stdout = &out
prog.Stderr = &out
if err = prog.Run(); err != nil {
log.Warn().Err(err).Msgf("l3afd : Failed to execute %s", b.Program.CmdStatus)
}
outStr, errStr := out.String(), out.String()
if strings.EqualFold(outStr, bpfStatus) {
userProgram = true
} else {
userProgram = false
log.Warn().Msgf("bpf program not running error - %s", errStr)
}
}
return userProgram, b.IsLoaded(), err
}
if len(b.Program.CmdStart) > 1 && b.Program.UserProgramDaemon {
if err := b.VerifyProcessObject(); err != nil {
userProgram = false
log.Warn().Err(err).Msgf("process object is not created for command start - %s", b.Program.CmdStart)
} else {
userProgram, err = IsProcessRunning(b.Cmd.Process.Pid, b.Program.Name)
if err != nil {
log.Warn().Err(err).Msgf("failed to execute command status - %s user program %v", b.Program.CmdStart, userProgram)
} else {
userProgram = true
}
}
}
return userProgram, b.IsLoaded(), err
}
// VerifyAndGetArtifacts -Check binary already exists
func (b *BPF) VerifyAndGetArtifacts(conf *config.Config) error {
fPath := filepath.Join(conf.BPFDir, b.Program.Name, b.Program.Version, strings.Split(b.Program.Artifact, ".")[0])
if _, err := os.Stat(fPath); os.IsNotExist(err) {
return b.GetArtifacts(conf)
}
b.FilePath = fPath
return nil
}
// GetArtifacts downloads artifacts from the specified eBPF repo
func (b *BPF) GetArtifacts(conf *config.Config) error {
buf := &bytes.Buffer{}
isDefaultURLUsed := false
platform, err := GetPlatform()
if err != nil {
return fmt.Errorf("failed to identify platform type: %w", err)
}
RepoURL := b.Program.EPRURL
if len(b.Program.EPRURL) == 0 {
RepoURL = conf.EBPFRepoURL
isDefaultURLUsed = true
}
_, err = url.Parse(RepoURL)
if err != nil {
if isDefaultURLUsed {
return fmt.Errorf("unknown ebpf-repo format : %w", err)
} else {
return fmt.Errorf("unknown ebpf_package_repo_url format : %w", err)
}
}
urlpath, err := url.JoinPath(RepoURL, b.Program.Name, b.Program.Version, platform, b.Program.Artifact)
if err != nil {
return fmt.Errorf("not able to join the artifact path %w", err)
}
log.Info().Msgf("Retrieving artifact - %s", urlpath)
err = DownloadArtifact(urlpath, conf.HttpClientTimeout, buf)
if err != nil {
return err
}
tempDir := filepath.Join(conf.BPFDir, b.Program.Name, b.Program.Version)
err = ExtractArtifact(b.Program.Artifact, buf, tempDir)
if err != nil {
return fmt.Errorf("unable to extract artifact %w", err)
}
newDir := strings.Split(b.Program.Artifact, ".")
b.FilePath = filepath.Join(tempDir, newDir[0])
return nil
}
// create rules file
func (b *BPF) createUpdateRulesFile(direction string) (string, error) {
if len(b.Program.RulesFile) < 1 {
return "", fmt.Errorf("RulesFile name is empty")
}
fileName := path.Join(b.FilePath, direction, b.Program.RulesFile)
if err := os.WriteFile(fileName, []byte(b.Program.Rules), 0644); err != nil {
return "", fmt.Errorf("create or Update Rules File failed with error %w", err)
}
return fileName, nil
}
// fileExists checks if a file exists or not
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return err == nil && !info.IsDir()
}
// Add eBPF map into BPFMaps list
func (b *BPF) AddBPFMap(mapName string) error {
bpfMap, err := b.GetBPFMap(mapName)
if err != nil {
return err
}
b.BpfMaps[mapName] = *bpfMap
return nil
}
func (b *BPF) GetBPFMap(mapName string) (*BPFMap, error) {
if b.ProgMapCollection == nil {
return nil, fmt.Errorf("no handle to prog map collection")
}
ebpfMap, ok := b.ProgMapCollection.Maps[mapName]
if !ok {
return nil, fmt.Errorf("not found")
}
ebpfInfo, err := ebpfMap.Info()
if err != nil {
return nil, fmt.Errorf("fetching map info failed %w", err)
}
tempMapID, ok := ebpfInfo.ID()
if !ok {
return nil, fmt.Errorf("fetching map id failed %w", err)
}
newBPFMap := BPFMap{
Name: mapName,
MapID: tempMapID,
Type: ebpfInfo.Type,
BPFProg: b,
}
log.Info().Msgf("added mapID %d Name %s Type %s", newBPFMap.MapID, newBPFMap.Name, newBPFMap.Type)
return &newBPFMap, nil
}
// Add eBPF map into BPFMaps list
func (b *BPF) AddMetricsBPFMap(mapName, aggregator string, key, samplesLength int) error {
var tmpMetricsBPFMap MetricsBPFMap
bpfMap, err := b.GetBPFMap(mapName)
if err != nil {
return fmt.Errorf("program %s metrics map %s not found : %w", b.Program.Name, mapName, err)
}
tmpMetricsBPFMap.BPFMap = *bpfMap
tmpMetricsBPFMap.Key = key
tmpMetricsBPFMap.Aggregator = aggregator
tmpMetricsBPFMap.Values = ring.New(samplesLength)
log.Info().Msgf("added Metrics map ID %d Name %s Type %s Key %d Aggregator %s", tmpMetricsBPFMap.MapID, tmpMetricsBPFMap.Name, tmpMetricsBPFMap.Type, tmpMetricsBPFMap.Key, tmpMetricsBPFMap.Aggregator)
map_key := mapName + strconv.Itoa(key) + aggregator
b.MetricsBpfMaps[map_key] = &tmpMetricsBPFMap
return nil
}
// This method to fetch values from bpf maps and publish to metrics
func (b *BPF) MonitorMaps(ifaceName, ipv4_address string, intervals int) error {
for _, element := range b.Program.MonitorMaps {
log.Debug().Msgf("monitor maps element %s key %d aggregator %s", element.Name, element.Key, element.Aggregator)
mapKey := element.Name + strconv.Itoa(element.Key) + element.Aggregator
_, ok := b.MetricsBpfMaps[mapKey]
if !ok {
if err := b.AddMetricsBPFMap(element.Name, element.Aggregator, element.Key, intervals); err != nil {
return fmt.Errorf("unable to fetch map %s key %d aggregator %s : %w", element.Name, element.Key, element.Aggregator, err)
}
}
bpfMap := b.MetricsBpfMaps[mapKey]
MetricName := element.Name + "_" + strconv.Itoa(element.Key) + "_" + element.Aggregator
stats.SetValue(bpfMap.GetValue(), stats.BPFMonitorMap, b.Program.Name, MetricName, ifaceName, ipv4_address)
}
return nil
}
// Updating next program FD from program ID
func (b *BPF) PutNextProgFDFromID(progID int) error {
if len(b.Program.MapName) == 0 {
// no chaining map
return nil
}
log.Info().Msgf("PutNextProgFDFromID : Map Name %s ID %d", b.Program.MapName, progID)
ebpfMap, err := ebpf.NewMapFromID(b.ProgMapID)
if err != nil {
return fmt.Errorf("unable to access pinned next prog map %s %w", b.Program.MapName, err)
}
defer ebpfMap.Close()
bpfProg, err := ebpf.NewProgramFromID(ebpf.ProgramID(progID))
if err != nil {
return fmt.Errorf("failed to get next prog FD from ID for program %s %w", b.Program.Name, err)
}
key := 0
fd := bpfProg.FD()
log.Info().Msgf("PutNextProgFDFromID : Map Name %s FD %d", b.Program.MapName, fd)
if err = ebpfMap.Update(unsafe.Pointer(&key), unsafe.Pointer(&fd), 0); err != nil {
return fmt.Errorf("unable to update prog next map %s %w", b.Program.MapName, err)
}
return nil
}
// GetProgID - This returns ID of the bpf program
func (b *BPF) GetProgID() (ebpf.ProgramID, error) {
ebpfMap, err := ebpf.LoadPinnedMap(b.PrevMapNamePath, &ebpf.LoadPinOptions{ReadOnly: true})
if err != nil {
log.Error().Err(err).Msgf("unable to access pinned prog map %s", b.PrevMapNamePath)
return 0, fmt.Errorf("unable to access pinned prog map %s %w", b.PrevMapNamePath, err)
}
defer ebpfMap.Close()
var value ebpf.ProgramID
key := 0
if err = ebpfMap.Lookup(unsafe.Pointer(&key), unsafe.Pointer(&value)); err != nil {
log.Warn().Err(err).Msgf("unable to look up prog map %s", b.PrevMapNamePath)
return 0, fmt.Errorf("unable to look up prog map %w", err)
}
// verify progID before storing in locally.
_, err = ebpf.NewProgramFromID(ebpf.ProgramID(value))
if err != nil {
log.Warn().Err(err).Msgf("failed to verify program ID %s", b.PrevMapNamePath)
return 0, fmt.Errorf("failed to verify program ID %s %w", b.Program.Name, err)
}
log.Info().Msgf("GetProgID - Name %s PrevMapName %s ID %d", b.Program.Name, b.PrevMapNamePath, value)
return value, nil
}
// RemoveNextProgFD Delete the entry if its last program in the chain.
// This method is called when sequence of the program changed to last in the chain
func (b *BPF) RemoveNextProgFD() error {
if len(b.Program.MapName) == 0 {
// no chaining map in case of root programs
return nil
}
ebpfMap, err := ebpf.NewMapFromID(b.PrevProgMapID)
if err != nil {
return fmt.Errorf("unable to access pinned next prog map %s %w", b.Program.MapName, err)
}
defer ebpfMap.Close()
key := 0
if err := ebpfMap.Delete(unsafe.Pointer(&key)); err != nil {
return fmt.Errorf("failed to delete prog fd entry : %w", err)
}
return nil
}
// RemovePrevProgFD Delete the entry if the last element
func (b *BPF) RemovePrevProgFD() error {
ebpfMap, err := ebpf.NewMapFromID(b.PrevProgMapID)
if err != nil {
return fmt.Errorf("unable to access pinned prev prog map %s %w", b.PrevMapNamePath, err)
}
defer ebpfMap.Close()
key := 0
if err := ebpfMap.Delete(unsafe.Pointer(&key)); err != nil {
// Some cases map may be empty ignore it.
log.Debug().Err(err).Msg("RemovePrevProgFD failed")
}
return nil
}
// VerifyPinnedProgMap - making sure program fd map's pinned file is created if exists flag is true
func (b *BPF) VerifyPinnedProgMap(chain, exists bool) error {
if !chain {
return nil
}
var err error
if len(b.Program.MapName) > 0 {
log.Debug().Msgf("VerifyPinnedMap : Program %s MapName %s Exists %t", b.Program.Name, b.Program.MapName, exists)
for i := 0; i < 10; i++ {
_, err = os.Stat(b.MapNamePath)
if err == nil && exists {
log.Info().Msgf("VerifyPinnedProgMap creation : map file created %s", b.MapNamePath)
return nil
} else if err != nil && !exists {
if _, err = os.Stat(b.MapNamePath); os.IsNotExist(err) {
log.Info().Msgf("VerifyPinnedProgMap removal : map file removed successfully - %s ", b.MapNamePath)
return nil
} else if err != nil {
log.Warn().Err(err).Msg("VerifyPinnedProgMap removal : Error checking for map file")
} else {
log.Warn().Msg("VerifyPinnedProgMap removal : program pinned file still exists, checking again after a second")
}
}
time.Sleep(1 * time.Second)
}
if err != nil && exists {
err = fmt.Errorf("VerifyPinnedProgMap creation : failed to find pinned file %s err %w", b.MapNamePath, err)
log.Error().Err(err).Msg("")
} else if err != nil {
err = fmt.Errorf("VerifyPinnedProgMap removal : %s map file was never removed by BPF program %s err %w", b.MapNamePath, b.Program.Name, err)
log.Error().Err(err).Msg("")
}
return err
}
return nil
}
// VerifyProcessObject - This method to verify cmd and process object is populated or not
func (b *BPF) VerifyProcessObject() error {
if b.Cmd == nil {
err := fmt.Errorf("command object is nil - %s", b.Program.Name)
log.Error().Err(err).Msg("command object is nil -")
return err
}
for i := 0; i < 10; i++ {
if b.Cmd.Process != nil {
return nil
}
log.Warn().Msgf("VerifyProcessObject: process object not found, checking again after a second")
time.Sleep(1 * time.Second)
}
err := fmt.Errorf("process object is nil - %s", b.Program.Name)
log.Error().Err(err).Msg("")
return err
}
// VerifyMetricsMapsVanish - checks for all metrics maps references are removed from the kernel
func (b *BPF) VerifyMetricsMapsVanish() error {
for i := 0; i < 10; i++ {
mapExists := false
for _, v := range b.BpfMaps {
_, err := ebpf.NewMapFromID(v.MapID)
if err == nil {
log.Warn().Msgf("VerifyMetricsMapsVanish: bpf map reference still exists - %s", v.Name)
mapExists = true
}
}
if !mapExists {
return nil
}
log.Warn().Msgf("VerifyMetricsMapsVanish: bpf map reference still exists - %s, checking again after a second", b.Program.Name)
time.Sleep(1 * time.Second)
}
err := fmt.Errorf("metrics maps are never removed by Kernel %s", b.Program.Name)
log.Error().Err(err).Msg("")
return err
}
// UnloadProgram - Unload or detach the program from the interface and close all the program resources
func (b *BPF) UnloadProgram(ifaceName, direction string) error {
// remove pinned files
if err := b.RemovePinnedFiles(ifaceName); err != nil {
log.Error().Err(err).Msgf("failed to remove map file for program %s => %s", ifaceName, b.Program.Name)
}
// Verifying program attached to the interface.
// SeqID will be 0 for root program or any other program without chaining
if b.Program.SeqID == 0 || !b.HostConfig.BpfChainingEnabled {
if b.Program.ProgType == models.TCType {
if utils.CheckTCXSupport() {
if b.Link != nil {
if err := b.Link.Close(); err != nil {
log.Warn().Msgf("removing tc attached program %s failed iface %q direction %s error - %v", b.Program.Name, ifaceName, direction, err)
}
} else {
log.Warn().Msgf("attach program %s link file is missing iface %q direction %s", b.Program.Name, ifaceName, direction)
}
} else {
if err := b.UnloadTCProgram(ifaceName, direction); err != nil {
log.Warn().Msgf("removing tc filter failed iface %q direction %s error - %v", ifaceName, direction, err)
}
}
} else if b.Program.ProgType == models.XDPType {
if err := b.Link.Close(); err != nil {
log.Warn().Msgf("removing xdp attached program failed iface %q direction %s error - %v", ifaceName, direction, err)
}
}
}
for _, LinkObject := range b.ProbeLinks {
(*LinkObject).Close()
}
// Release all the resources of the epbf program
if b.ProgMapCollection != nil {
b.ProgMapCollection.Close()
}
return nil
}
// RemovePinnedFiles - removes all the pinned files
func (b *BPF) RemovePinnedFiles(ifaceName string) error {
if b.ProgMapCollection != nil {
for k, v := range b.ProgMapCollection.Maps {
var mapFilename string
version := utils.ReplaceDotsWithUnderscores(b.Program.Version)
if b.Program.ProgType == models.TCType {
mapFilename = filepath.Join(b.HostConfig.BpfMapDefaultPath, models.TCMapPinPath, ifaceName, b.Program.Name, version, k)
} else {
mapFilename = filepath.Join(b.HostConfig.BpfMapDefaultPath, ifaceName, b.Program.Name, version, k)
}
if err := v.Unpin(); err != nil {
return fmt.Errorf("BPF program %s prog type %s ifacename %s map %s:failed to pin the map err - %w",
b.Program.Name, b.Program.ProgType, ifaceName, mapFilename, err)
}
}
}
// remove pinned links
if b.Link != nil {
if err := b.Link.Unpin(); err != nil {
return fmt.Errorf("unable to unpin the xdp link for %s with err : %w", b.Program.Name, err)
}
}
// remove programs pins
if b.ProgMapCollection != nil {
for _, v := range b.ProgMapCollection.Programs {
if err := v.Unpin(); err != nil {
return err
}
}
}
return nil
}
// RemoveRootProgMapFile - removes root pinned prog map file
// This is invoked if any stale map file persists for root map
func (b *BPF) RemoveRootProgMapFile(ifacename string) error {
var mapFilename string
version := utils.ReplaceDotsWithUnderscores(b.Program.Version)
switch b.Program.ProgType {
case models.TCType:
mapFilename = filepath.Join(b.HostConfig.BpfMapDefaultPath, models.TCMapPinPath, ifacename, b.Program.Name, version, b.Program.MapName)
case models.XDPType:
mapFilename = filepath.Join(b.HostConfig.BpfMapDefaultPath, ifacename, b.Program.Name, version, b.Program.MapName)
default:
log.Warn().Msgf("RemoveRootProgMapFile: program %s map file %s - unknown type", b.Program.Name, b.MapNamePath)
return fmt.Errorf("removeMapFile: program %s unknown type %s", b.Program.Name, b.Program.ProgType)
}
// codeQL Check
if strings.Contains(mapFilename, "..") {
return fmt.Errorf("%s contains relative path is not supported - %s", mapFilename, b.Program.Name)
}
if err := os.Remove(mapFilename); err != nil {
if !os.IsNotExist(err) {
log.Warn().Msgf("RemoveRootProgMapFile: %s program type %s map file remove unsuccessfully - %s err - %#v", b.Program.ProgType, b.Program.Name, mapFilename, err)
return fmt.Errorf("%s - remove failed with error %w", mapFilename, err)
}
}
return nil
}
// VerifyCleanupMaps - This method verifies map entries in the fs is removed
func (b *BPF) VerifyCleanupMaps(chain bool) error {
// verify pinned file is removed.
if err := b.VerifyPinnedProgMap(chain, false); err != nil {
log.Error().Err(err).Msgf("stop user program - failed to remove pinned file %s", b.Program.Name)
return fmt.Errorf("stop user program - failed to remove pinned file %s : %w", b.Program.Name, err)
}
// Verify all metrics map references are removed from kernel
if err := b.VerifyMetricsMapsVanish(); err != nil {
log.Error().Err(err).Msgf("stop user program - failed to remove metric map references %s", b.Program.Name)
return fmt.Errorf("stop user program - failed to remove metric map references %s : %w", b.Program.Name, err)
}
return nil
}
// LoadBPFProgram - This method loads the eBPF program natively.
func (b *BPF) LoadBPFProgram(ifaceName string) error {
ObjectFile := filepath.Join(b.FilePath, b.Program.ObjectFile)
if _, err := os.Stat(ObjectFile); os.IsNotExist(err) {
return fmt.Errorf("%s: file doesn't exist", ObjectFile)
}
// Allow the current process to lock memory for eBPF resources.
if err := rlimit.RemoveMemlock(); err != nil {
log.Error().Msgf("failed to remove memory lock limits %#v", err)
return fmt.Errorf("%s: remove rlimit lock failed : %w", b.Program.Name, err)
}
objSpec, err := ebpf.LoadCollectionSpec(ObjectFile)
if err != nil {
return fmt.Errorf("%s: loading collection spec failed - %w", ObjectFile, err)
}
version := utils.ReplaceDotsWithUnderscores(b.Program.Version)
if err := b.CreatePinDirectories(ifaceName, b.Program.Name, version); err != nil {
return err
}
var mapPinPath string
if b.Program.ProgType == models.TCType {
mapPinPath = filepath.Join(b.HostConfig.BpfMapDefaultPath, models.TCMapPinPath, ifaceName, b.Program.Name, version)
} else if b.Program.ProgType == models.XDPType {
mapPinPath = filepath.Join(b.HostConfig.BpfMapDefaultPath, ifaceName, b.Program.Name, version)
}
collOptions := ebpf.CollectionOptions{
Maps: ebpf.MapOptions{
PinPath: mapPinPath,
},
}
// Load the BPF program with the updated map options
prg, err := ebpf.NewCollectionWithOptions(objSpec, collOptions)
if err != nil {
return fmt.Errorf("%s: loading of bpf program failed - %w", b.Program.Name, err)
}
// Persist program handle
b.ProgMapCollection = prg
if err := b.LoadBPFProgramProbeTypes(objSpec); err != nil {
return fmt.Errorf("LoadBPFProgramProbeTypes failed with error %v ", err)
}
//var bpfProg *ebpf.Program
if len(b.Program.EntryFunctionName) > 0 {
bpfProg := prg.Programs[b.Program.EntryFunctionName]
if bpfProg == nil {
return fmt.Errorf("%s entry function is not found in the loaded object file of the program %s", b.Program.EntryFunctionName, b.Program.Name)
}
progInfo, err := bpfProg.Info()
if err != nil {
return fmt.Errorf("%s: information of bpf program failed : %w", b.Program.Name, err)
}
ok := false
b.ProgID, ok = progInfo.ID()
if !ok {
log.Warn().Msgf("Program ID fetch failed: %s", b.Program.Name)
}
// Initialise metric maps
if err := b.InitialiseMetricMaps(); err != nil {
return fmt.Errorf("initialising metric maps failed %w", err)
}
if err := b.PinBpfMaps(ifaceName); err != nil {
return err
}
}
return nil
}
// InitialiseMetricMaps - This method initialises all the monitor maps
func (b *BPF) InitialiseMetricMaps() error {
if b.ProgMapCollection == nil {
log.Warn().Msgf("prog is not loaded by l3afd")
return nil
}
for _, tmpMap := range b.Program.MonitorMaps {
tmpMetricsMap := b.ProgMapCollection.Maps[tmpMap.Name]
if tmpMetricsMap == nil {
log.Error().Msgf("%s map is not loaded", tmpMap.Name)
continue
}
var err error
log.Debug().Msgf("Program - %s map name %s key size %d value size %d\n", b.Program.Name, tmpMap.Name, tmpMetricsMap.KeySize(), tmpMetricsMap.ValueSize())
if tmpMetricsMap.KeySize() == 1 {
var k int8
switch tmpMetricsMap.ValueSize() {
case 1:
var v int8
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 2:
var v int16
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 4:
var v int32
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 8:
var v int64
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
default:
log.Error().Msgf("unsupported key type int8 and value size - %d", tmpMetricsMap.ValueSize())
}
} else if tmpMetricsMap.KeySize() == 2 {
var k int16
switch tmpMetricsMap.ValueSize() {
case 1:
var v int8
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 2:
var v int16
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 4:
var v int32
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 8:
var v int64
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
default:
log.Error().Msgf("unsupported map key type int16 and value size - %d", tmpMetricsMap.ValueSize())
}
} else if tmpMetricsMap.KeySize() == 4 {
var k int32
switch tmpMetricsMap.ValueSize() {
case 1:
var v int8
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 2:
var v int16
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 4:
var v int32
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 8:
var v int64
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
default:
log.Error().Msgf("unsupported map key type int32 and value size - %d", tmpMetricsMap.ValueSize())
}
} else if tmpMetricsMap.KeySize() == 8 {
var k int64
switch tmpMetricsMap.ValueSize() {
case 1:
var v int8
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 2:
var v int16
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 4:
var v int32
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
case 8:
var v int64
err = tmpMetricsMap.Update(unsafe.Pointer(&k), unsafe.Pointer(&v), 0)
default:
log.Error().Msgf("unsupported key type int64 and value size - %d", tmpMetricsMap.ValueSize())
}
}
if err != nil {
return fmt.Errorf("update hash map element failed for map name %s error %v", tmpMap.Name, err)
}
}
return nil
}
// IsLoaded - Method verifies whether bpf program is loaded or not
// Here it checks whether prog ID is valid and active
func (b *BPF) IsLoaded() bool {
if b.ProgID == 0 {
return false
}
ebpfProg, err := ebpf.NewProgramFromID(b.ProgID)
if err != nil {
log.Debug().Msgf("IsLoaded - %s is not loaded or invalid program id %d", b.Program.Name, uint32(b.ProgID))
return false
}
defer ebpfProg.Close()
return true
}
func (b *BPF) StartUserProgram(ifaceName, direction string, chain bool) error {
cmd := filepath.Join(b.FilePath, b.Program.CmdStart)
// Validate
if err := assertExecutable(cmd); err != nil {
return fmt.Errorf("no executable permissions on %s - error %w", b.Program.CmdStart, err)
}
args := make([]string, 0, len(b.Program.StartArgs)<<1)
if len(ifaceName) > 0 {
args = append(args, "--iface="+ifaceName) // attaching to interface
}
if len(direction) > 0 {
args = append(args, "--direction="+direction) // direction xdpingress or ingress or egress
}
version := utils.ReplaceDotsWithUnderscores(b.Program.Version)
if len(b.Program.Version) > 0 {
args = append(args, "--version="+version) // version xdpingress or ingress or egress
}
if chain && b.ProgMapCollection == nil {
// chaining from user program
if len(b.PrevMapNamePath) > 1 {
args = append(args, "--map-name="+b.PrevMapNamePath)
}
}
if len(b.HostConfig.BPFLogDir) > 1 {
args = append(args, "--log-dir="+b.HostConfig.BPFLogDir)
}
if len(b.Program.RulesFile) > 1 && len(b.Program.Rules) > 1 {
fileName, err := b.createUpdateRulesFile(direction)
if err == nil {
args = append(args, "--rules-file="+fileName)
}
}
for k, val := range b.Program.StartArgs {
if v, ok := val.(string); !ok {
err := fmt.Errorf("start args is not a string for the bpf program %s", b.Program.Name)
log.Error().Err(err).Msgf("failed to convert start args value into string for program %s", b.Program.Name)
return err
} else {
args = append(args, "--"+k+"="+v)
}
}
log.Info().Msgf("BPF Program start command : %s %v", cmd, args)
b.Cmd = execCommand(cmd, args...)
if err := b.Cmd.Start(); err != nil {
log.Info().Err(err).Msgf("user program failed - %s", b.Program.Name)
return fmt.Errorf("failed to start : %s %v with err: %w", cmd, args, err)
}
if !b.Program.UserProgramDaemon {
log.Info().Msgf("no user program - %s No Pid", b.Program.Name)
if err := b.Cmd.Wait(); err != nil {
log.Warn().Msgf("failed at wait - %s err %s", b.Program.Name, err.Error())
}
b.Cmd = nil
} else {
if err := b.SetPrLimits(); err != nil {
log.Warn().Err(err).Msg("failed to set resource limits")
}
log.Info().Msgf("BPF program - %s User program Process id %d started", b.Program.Name, b.Cmd.Process.Pid)
}
return nil
}
// CreatePinDirectories - This method creates directory for ebpf objects
// TC maps are pinned to directory /sys/fs/bpf/tc/globals/<ifaceName>
// XDP maps are pinned to directory /sys/fs/bpf/<ifaceName>
// links are pinned to directory /sys/fs/bpf/links/<ifaceName>/<progName>/<progVersion>
// Program are pinned to directory /sys/fs/bpf/progs/<ifaceName>/<progName>/<progVersion>
func (b *BPF) CreatePinDirectories(ifaceName, progName, progVersion string) error {
var mapPathDir string
if b.Program.ProgType == models.XDPType {
mapPathDir = filepath.Join(b.HostConfig.BpfMapDefaultPath, ifaceName, progName, progVersion)
} else if b.Program.ProgType == models.TCType {
mapPathDir = filepath.Join(b.HostConfig.BpfMapDefaultPath, models.TCMapPinPath, ifaceName, progName, progVersion)
}
// Create map dir for XDP and TC programs only
if len(mapPathDir) > 0 {
// codeQL Check
if strings.Contains(mapPathDir, "..") {
return fmt.Errorf("%s contains relative path is not supported - %s", mapPathDir, b.Program.Name)
}
if err := os.MkdirAll(mapPathDir, 0750); err != nil {
return fmt.Errorf("%s failed to create map dir path of %s program %s with err : %w", mapPathDir, b.Program.ProgType, b.Program.Name, err)
}
}
linksPathDir := filepath.Join(b.HostConfig.BpfMapDefaultPath, "links", ifaceName, progName, progVersion)
if strings.Contains(linksPathDir, "..") {
return fmt.Errorf("%s contains relative path is not supported - %s", linksPathDir, b.Program.Name)
}
if err := os.MkdirAll(linksPathDir, 0750); err != nil {
return fmt.Errorf("%s failed to create map dir path of %s program %s with err : %w", linksPathDir, b.Program.ProgType, b.Program.Name, err)
}
ProgPathDir := filepath.Join(b.HostConfig.BpfMapDefaultPath, "progs", ifaceName, progName, progVersion)
if strings.Contains(ProgPathDir, "..") {
return fmt.Errorf("%s contains relative path is not supported - %s", ProgPathDir, b.Program.Name)
}
if err := os.MkdirAll(ProgPathDir, 0750); err != nil {
return fmt.Errorf("%s failed to create map dir path of %s program %s with err : %w", ProgPathDir, b.Program.ProgType, b.Program.Name, err)
}
return nil
}
// AttachBPFProgram - method to attach bpf program to interface
func (b *BPF) AttachBPFProgram(ifaceName, direction string) error {
version := utils.ReplaceDotsWithUnderscores(b.Program.Version)
if b.Program.ProgType == models.XDPType {
if err := b.LoadXDPAttachProgram(ifaceName); err != nil {
return fmt.Errorf("failed to attach xdp program %s to inferface %s with err: %w", b.Program.Name, ifaceName, err)
}
// pin the program also
progPinPath := utils.ProgPinPath(b.HostConfig.BpfMapDefaultPath, ifaceName, b.Program.Name, version, b.Program.EntryFunctionName, b.Program.ProgType)
if err := b.ProgMapCollection.Programs[b.Program.EntryFunctionName].Pin(progPinPath); err != nil {
return err
}
} else if b.Program.ProgType == models.TCType {
if utils.CheckTCXSupport() {
if err := b.LoadTCXAttachProgram(ifaceName, direction); err != nil {
return fmt.Errorf("failed to attach tcx program %s to interface %s direction %s with err %w", b.Program.Name, ifaceName, direction, err)
}
} else {
if err := b.LoadTCAttachProgram(ifaceName, direction); err != nil {
return fmt.Errorf("failed to attach tc program %s to interface %s direction %s with err %w", b.Program.Name, ifaceName, direction, err)
}
}
// pin the program also
progPinPath := utils.ProgPinPath(b.HostConfig.BpfMapDefaultPath, ifaceName, b.Program.Name, version, b.Program.EntryFunctionName, b.Program.ProgType)
if err := b.ProgMapCollection.Programs[b.Program.EntryFunctionName].Pin(progPinPath); err != nil {
return err
}
}
return nil
}
// PinBpfMaps - Pinning tc and xdp maps
func (b *BPF) PinBpfMaps(ifaceName string) error {
version := utils.ReplaceDotsWithUnderscores(b.Program.Version)
for k, v := range b.ProgMapCollection.Maps {
var mapFilename string
// ebpf programs temporary storage created by eBPF program skip it
if k == ".bss" {
continue
}
if b.Program.ProgType == models.TCType {
mapFilename = filepath.Join(b.HostConfig.BpfMapDefaultPath, models.TCMapPinPath, ifaceName, b.Program.Name, version, k)
} else {
mapFilename = filepath.Join(b.HostConfig.BpfMapDefaultPath, ifaceName, b.Program.Name, version, k)
}
// In case one of the program pins the map then other program will skip
if !fileExists(mapFilename) {
if err := v.Pin(mapFilename); err != nil {
return fmt.Errorf("eBPF program %s map %s:failed to pin the map err - %w", b.Program.Name, mapFilename, err)
}
}
}
return nil
}
// UpdateProgramMap - Store the program map reference
func (b *BPF) UpdateProgramMap(ifaceName string) error {
// Verify chaining map is provided
if len(b.Program.MapName) == 0 {
return fmt.Errorf("program map name is missing for %s program %s", b.Program.ProgType, b.Program.Name)
}
bpfRootMap := b.ProgMapCollection.Maps[b.Program.MapName]
ebpfInfo, err := bpfRootMap.Info()
if err != nil {
return fmt.Errorf("fetching map info failed for %s program %s to interface %s : %w", b.Program.ProgType, b.Program.Name, ifaceName, err)
}
var ok bool
b.ProgMapID, ok = ebpfInfo.ID()
if !ok {
return fmt.Errorf("fetching map id failed for %s program %s to interface %s : %w", b.Program.ProgType, b.Program.Name, ifaceName, err)
}
return nil
}
// LoadBPFProgramChain - Load the BPF program and chain it.
func (b *BPF) LoadBPFProgramChain(ifaceName, direction string) error {
if err := b.LoadBPFProgram(ifaceName); err != nil {
return err
}
version := utils.ReplaceDotsWithUnderscores(b.Program.Version)
// pin the program also
progPinPath := utils.ProgPinPath(b.HostConfig.BpfMapDefaultPath, ifaceName, b.Program.Name, version, b.Program.EntryFunctionName, b.Program.ProgType)
if err := b.ProgMapCollection.Programs[b.Program.EntryFunctionName].Pin(progPinPath); err != nil {
return err
}
// Update program map id
if err := b.UpdateProgramMap(ifaceName); err != nil {
return err
}
// Link this program into previous program map
ebpfMap, err := ebpf.NewMapFromID(b.PrevProgMapID)
if err != nil {
return fmt.Errorf("unable to access pinned previous prog map %s %w", b.PrevMapNamePath, err)
}
defer ebpfMap.Close()
bpfProg := b.ProgMapCollection.Programs[b.Program.EntryFunctionName]
if bpfProg == nil {
return fmt.Errorf("%s entry function is not found in the loaded object file of the program %s", b.Program.EntryFunctionName, b.Program.Name)
}
key := 0
fd := bpfProg.FD()
log.Info().Msgf("previous program map path %s FD %d", b.PrevMapNamePath, fd)
if err = ebpfMap.Update(unsafe.Pointer(&key), unsafe.Pointer(&fd), 0); err != nil {
return fmt.Errorf("unable to update prog next map %s %v", b.Program.MapName, err)
}
log.Info().Msgf("eBPF program %s loaded on interface %s direction %s successfully", b.Program.Name, ifaceName, direction)
return nil
}
func (b *BPF) StopUserProgram(ifaceName, direction string) error {
// Stop User Programs if any
if len(b.Program.CmdStop) < 1 && b.Program.UserProgramDaemon {
// Loaded using user program
if err := b.ProcessTerminate(); err != nil {
return fmt.Errorf("BPFProgram %s process terminate failed with error: %w", b.Program.Name, err)
}
if b.Cmd != nil {
if err := b.Cmd.Wait(); err != nil {
log.Error().Err(err).Msgf("cmd wait at stopping bpf program %s errored", b.Program.Name)
}
b.Cmd = nil
}
} else if len(b.Program.CmdStop) > 0 && b.Program.UserProgramDaemon {
cmd := filepath.Join(b.FilePath, b.Program.CmdStop)
if err := assertExecutable(cmd); err != nil {
return fmt.Errorf("no executable permissions on %s - error %w", b.Program.CmdStop, err)
}
args := make([]string, 0, len(b.Program.StopArgs)<<1)
if len(ifaceName) > 0 {
args = append(args, "--iface="+ifaceName) // detaching from iface
}
if len(direction) > 0 {
args = append(args, "--direction="+direction) // xdpingress or ingress or egress
}
for k, val := range b.Program.StopArgs {
if v, ok := val.(string); !ok {
err := fmt.Errorf("stop args is not a string for the bpf program %s", b.Program.Name)
log.Error().Err(err).Msgf("failed to convert stop args value into string for program %s", b.Program.Name)
return err
} else {
args = append(args, "--"+k+" ="+v)
}
}
log.Info().Msgf("bpf user program stop command : %s %v", cmd, args)
prog := execCommand(cmd, args...)
if err := prog.Run(); err != nil {
log.Warn().Err(err).Msgf("l3afd : Failed to stop the user program %s", b.Program.CmdStop)
}
b.Cmd = nil
}
return nil
}
// DownloadArtifact will download artifact from provided urlpath and store it in buffer
func DownloadArtifact(urlpath string, timeout time.Duration, buf *bytes.Buffer) error {
URL, err := url.Parse(urlpath)
if err != nil {
return fmt.Errorf("unknown url format : %w", err)
}
switch URL.Scheme {
case models.HttpScheme, models.HttpsScheme:
{
timeOut := time.Duration(timeout) * time.Second
var netTransport = &http.Transport{
ResponseHeaderTimeout: timeOut,
}
client := http.Client{Transport: netTransport, Timeout: timeOut}
// Get the data
resp, err := client.Get(URL.String())
if err != nil {
return fmt.Errorf("download failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("get request returned unexpected status code: %d (%s), %d was expected\n\tResponse Body: %s", resp.StatusCode, http.StatusText(resp.StatusCode), http.StatusOK, buf.Bytes())
}
buf.ReadFrom(resp.Body)
return nil
}
case models.FileScheme:
{
if fileExists(URL.Path) {
f, err := os.Open(URL.Path)
if err != nil {
return fmt.Errorf("opening err : %w", err)
}
buf.ReadFrom(f)
f.Close()
} else {
return fmt.Errorf("artifact is not found")
}
return nil
}
default:
return fmt.Errorf("unknown url scheme")
}
}
// ExtractArtifact will extract artifact to given tempDir
func ExtractArtifact(artifactName string, buf *bytes.Buffer, tempDir string) error {
switch artifact := artifactName; {
case strings.HasSuffix(artifact, ".zip"):
{
c := bytes.NewReader(buf.Bytes())
zipReader, err := zip.NewReader(c, int64(c.Len()))
if err != nil {
return fmt.Errorf("failed to create zip reader: %w", err)
}
for _, file := range zipReader.File {
zippedFile, err := file.Open()
if err != nil {
return fmt.Errorf("unzip failed: %w", err)
}
defer zippedFile.Close()
extractedFilePath, err := ValidatePath(file.Name, tempDir)
if err != nil {
return err
}
if file.FileInfo().IsDir() {
os.MkdirAll(extractedFilePath, file.Mode())
} else {
outputFile, err := os.OpenFile(
extractedFilePath,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
file.Mode(),
)
if err != nil {
return fmt.Errorf("unzip failed to create file: %w", err)
}
defer outputFile.Close()
buf := copyBufPool.Get().(*bytes.Buffer)
_, err = io.CopyBuffer(outputFile, zippedFile, buf.Bytes())
if err != nil {
return fmt.Errorf("GetArtifacts failed to copy files: %w", err)
}
copyBufPool.Put(buf)
}
}
return nil
}
case strings.HasSuffix(artifact, ".tar.gz"):
{
archive, err := gzip.NewReader(buf)
if err != nil {
return fmt.Errorf("failed to create Gzip reader: %w", err)
}
defer archive.Close()
tarReader := tar.NewReader(archive)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
} else if err != nil {
return fmt.Errorf("untar failed: %w", err)
}
fPath, err := ValidatePath(header.Name, tempDir)
if err != nil {
return err
}
info := header.FileInfo()
if info.IsDir() {
if err = os.MkdirAll(fPath, info.Mode()); err != nil {
return fmt.Errorf("untar failed to create directories: %w", err)
}
continue
}
file, err := os.OpenFile(fPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
if err != nil {
return fmt.Errorf("untar failed to create file: %w", err)
}
defer file.Close()
buf := copyBufPool.Get().(*bytes.Buffer)
_, err = io.CopyBuffer(file, tarReader, buf.Bytes())
if err != nil {
return fmt.Errorf("GetArtifacts failed to copy files: %w", err)
}
copyBufPool.Put(buf)
}
return nil
}
default:
return fmt.Errorf("unknown artifact format")
}
}
// ValidatePath will validate any illegal file path
func ValidatePath(filePath string, destination string) (string, error) {
destpath := filepath.Join(destination, filePath)
if strings.Contains(filePath, "..") {
return "", fmt.Errorf(" file contains filepath (%s) that includes (..)", filePath)
}
if !strings.HasPrefix(destpath, filepath.Clean(destination)+string(os.PathSeparator)) {
return "", fmt.Errorf("%s: illegal file path", filePath)
}
return destpath, nil
}
================================================
FILE: bpfprogs/bpfCfgs_internal.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
//
//go:build !configs
// +build !configs
// This file is used for walmart internal to run BPF specific configs.
// We will be removing this file in future.
package bpfprogs
import (
"github.com/rs/zerolog/log"
)
func (b *BPF) RunBPFConfigs() error {
log.Warn().Msg("Implement custom BPF specific configs")
return nil
}
================================================
FILE: bpfprogs/bpf_test.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package bpfprogs
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"context"
"io"
"net/http"
"os"
"os/exec"
"reflect"
"runtime"
"strings"
"testing"
"github.com/l3af-project/l3afd/v2/config"
"github.com/l3af-project/l3afd/v2/mocks"
"github.com/l3af-project/l3afd/v2/models"
"github.com/l3af-project/l3afd/v2/stats"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/zerolog/log"
"go.uber.org/mock/gomock"
)
var mockedExitStatus = 1
var mockPid = 77
func fakeExecCommand(command string, args ...string) *exec.Cmd {
cs := []string{"-test.run=TestHelperProcess", "--", command}
cs = append(cs, args...)
cmd := exec.Command(os.Args[0], cs...)
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
cmd.Process = &os.Process{Pid: mockPid}
return cmd
}
func TestHelperProcess(t *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
os.Exit(mockedExitStatus)
}
func TestNewBpfProgram(t *testing.T) {
type args struct {
program models.BPFProgram
logDir string
chain bool
direction string
ctx context.Context
datacenter string
hostConfig *config.Config
}
execCommand = fakeExecCommand
defer func() { execCommand = exec.Command }()
tests := []struct {
name string
args args
want *BPF
}{
{name: "GoodInput",
args: args{
program: models.BPFProgram{
Name: "nfprogram",
Artifact: "foo.tar.gz",
CmdStart: "foo",
CmdStop: "",
CmdConfig: "",
Version: "1.0",
UserProgramDaemon: true,
IsPlugin: false,
AdminStatus: "enabled",
ProgType: "tc",
},
logDir: "",
chain: false,
direction: "ingress",
datacenter: "localdc",
hostConfig: &config.Config{
BPFLogDir: "",
DataCenter: "localdc",
},
},
want: &BPF{
Program: models.BPFProgram{
Name: "nfprogram",
Artifact: "foo.tar.gz",
CmdStart: "foo",
CmdStop: "",
CmdConfig: "",
CmdUpdate: "",
Version: "1.0",
UserProgramDaemon: true,
IsPlugin: false,
AdminStatus: "enabled",
ProgType: "tc",
},
Cmd: nil,
FilePath: "",
BpfMaps: make(map[string]BPFMap, 0),
MetricsBpfMaps: make(map[string]*MetricsBPFMap, 0),
Ctx: nil,
Done: nil,
HostConfig: &config.Config{
BPFLogDir: "",
DataCenter: "localdc",
},
},
},
{name: "EmptyBPFProgram",
args: args{
program: models.BPFProgram{},
hostConfig: &config.Config{},
},
want: &BPF{
Program: models.BPFProgram{},
Cmd: nil,
FilePath: "",
BpfMaps: make(map[string]BPFMap, 0),
MetricsBpfMaps: make(map[string]*MetricsBPFMap, 0),
HostConfig: &config.Config{},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewBpfProgram(tt.args.ctx, tt.args.program, tt.args.hostConfig, ifaceName); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewBpfProgram() = %#v, want %#v", got, tt.want)
}
})
}
}
func TestBPF_Stop(t *testing.T) {
type fields struct {
Program models.BPFProgram
Cmd *exec.Cmd
FilePath string
RestartCount int
Direction string
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{name: "NilCmd",
fields: fields{
Program: models.BPFProgram{
Name: "nfprogram",
Artifact: "foo.tar.gz",
CmdStart: "foo",
CmdStop: "",
Version: "1.0",
UserProgramDaemon: true,
AdminStatus: "enabled",
},
Cmd: nil,
FilePath: "/tmp/dummy/dummy",
RestartCount: 3,
},
wantErr: true,
},
{name: "WithStopCmd",
fields: fields{
Program: models.BPFProgram{
Name: "nfprogram",
Artifact: "foo.tar.gz",
CmdStart: "foo",
CmdStop: "foo",
Version: "1.0",
UserProgramDaemon: true,
AdminStatus: "enabled",
},
Cmd: fakeExecCommand("/tmp/dummy/foo"),
FilePath: "/tmp/dummy/dummy",
RestartCount: 3,
},
wantErr: true,
},
{name: "AnyBinaryFile",
fields: fields{
Program: models.BPFProgram{
Name: "nfprogram",
Artifact: "data.tar.gz",
CmdStart: GetTestExecutableName(),
CmdStop: GetTestExecutableName(),
UserProgramDaemon: false,
AdminStatus: "enabled",
},
Cmd: fakeExecCommand(GetTestExecutablePathName()),
FilePath: GetTestExecutablePath(),
RestartCount: 3,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &BPF{
Program: tt.fields.Program,
Cmd: tt.fields.Cmd,
FilePath: tt.fields.FilePath,
RestartCount: tt.fields.RestartCount,
}
// Create the metric with expected labels
stats.BPFRunning = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "l3afd_BPFRunning",
Help: "Indicates whether eBPF program is running",
},
[]string{"program", "version", "direction", "iface"},
)
if err := prometheus.Register(stats.BPFRunning); err != nil {
log.Warn().Err(err).Msg("Failed to register BPFRunning metrics")
}
// Simulate program load
stats.BPFRunning.With(prometheus.Labels{
"program": b.Program.Name,
"version": b.Program.Version,
"direction": "ingress",
"iface": "eth0",
}).Set(1)
stats.BPFStartTime = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "l3afd",
Name: "BPFStartTime",
Help: "This value indicates start time of the BPF program since unix epoch in seconds",
},
[]string{"host", "ebpf_program", "direction", "interface_name"},
)
if err := prometheus.Register(stats.BPFStartTime); err != nil {
log.Warn().Err(err).Msg("Failed to register BPFStartTime metrics")
}
if err := b.Stop(ifaceName, "", models.IngressType, false); (err != nil) != tt.wantErr {
t.Errorf("BPF.Stop() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestBPF_Start(t *testing.T) {
type fields struct {
Program models.BPFProgram
Cmd *exec.Cmd
FilePath string
RestartCount int
ifaceName string
hostConfig *config.Config
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{name: "NoFilePath",
fields: fields{
Program: models.BPFProgram{},
Cmd: nil,
FilePath: "",
RestartCount: 0,
hostConfig: &config.Config{
BPFLogDir: "",
},
},
wantErr: true,
},
{name: "AnyBinary",
fields: fields{
Program: models.BPFProgram{
Name: "nfprogram",
Artifact: "data.tar.gz",
CmdStart: GetTestExecutableName(),
CmdStop: GetTestExecutableName(),
UserProgramDaemon: true,
AdminStatus: "enabled",
},
Cmd: nil,
FilePath: GetTestExecutablePath(),
RestartCount: 0,
hostConfig: &config.Config{
BPFLogDir: "",
},
},
wantErr: false,
},
{name: "UserProgramFalse",
fields: fields{
Program: models.BPFProgram{
Name: "nfprogram",
Artifact: "data.tar.gz",
CmdStart: GetTestExecutableName(),
CmdStop: GetTestExecutableName(),
UserProgramDaemon: false,
AdminStatus: "enabled",
},
Cmd: fakeExecCommand(GetTestExecutablePathName()),
FilePath: GetTestExecutablePath(),
RestartCount: 0,
hostConfig: &config.Config{
BPFLogDir: "",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &BPF{
Program: tt.fields.Program,
Cmd: tt.fields.Cmd,
FilePath: tt.fields.FilePath,
RestartCount: tt.fields.RestartCount,
HostConfig: tt.fields.hostConfig,
}
if err := b.Start(tt.fields.ifaceName, "", models.IngressType, true); (err != nil) != tt.wantErr {
t.Errorf("BPF.Start() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestBPF_isRunning(t *testing.T) {
type fields struct {
Program models.BPFProgram
Cmd *exec.Cmd
FilePath string
RestartCount int
CmdStatus string
}
tests := []struct {
name string
fields fields
want bool
wantErr bool
}{
{
name: "NoPID",
fields: fields{
Program: models.BPFProgram{},
Cmd: nil,
FilePath: "",
RestartCount: 0,
},
want: false,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &BPF{
Program: tt.fields.Program,
Cmd: tt.fields.Cmd,
FilePath: tt.fields.FilePath,
RestartCount: tt.fields.RestartCount,
}
userProg, bpfProg, err := b.isRunning()
if (err != nil) != tt.wantErr && (userProg == tt.want || bpfProg != tt.want) {
t.Errorf("BPF.isRunning() user prog = %v, bpf prog = %v, error = %v, wantErr %v ", userProg, bpfProg, err, tt.wantErr)
}
})
}
}
// RoundTripFunc .
type RoundTripFunc func(req *http.Request) *http.Response
// RoundTrip .
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req), nil
}
// NewTestClient returns *http.Client with Transport replaced to avoid making real calls
func NewTestClient(fn RoundTripFunc) *http.Client {
return &http.Client{
Transport: RoundTripFunc(fn),
}
}
func TestBPF_GetArtifacts(t *testing.T) {
type fields struct {
Program models.BPFProgram
Cmd *exec.Cmd
FilePath string
RestartCount int
Client *http.Client
}
type args struct {
conf *config.Config
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{name: "EmptyArtifact",
fields: fields{
Program: models.BPFProgram{},
Cmd: nil,
FilePath: "",
RestartCount: 0,
},
args: args{conf: &config.Config{BPFDir: "/tmp"}},
wantErr: true,
},
{name: "DummyArtifact",
fields: fields{
Program: models.BPFProgram{
Name: "dummy",
Version: "1",
Artifact: "dummy.tar.gz",
},
Cmd: nil,
FilePath: "",
RestartCount: 0,
Client: NewTestClient(func(r *http.Request) *http.Response {
buf := new(bytes.Buffer)
writer := gzip.NewWriter(buf)
defer writer.Close()
tarWriter := tar.NewWriter(writer)
defer tarWriter.Close()
header := new(tar.Header)
header.Name = "random"
header.Mode = 0777
tarWriter.WriteHeader(header)
tarWriter.Write([]byte("random things"))
return &http.Response{
StatusCode: 200,
Body: io.NopCloser(buf),
Header: make(http.Header),
}
}),
},
args: args{conf: &config.Config{BPFDir: "/tmp",
EBPFRepoURL: "https://l3af.io/"}},
wantErr: true,
},
{
name: "Unknown_url_with_http_scheme",
fields: fields{
Program: models.BPFProgram{
EPRURL: "http://www.example.com",
},
Cmd: nil,
FilePath: "",
RestartCount: 0,
},
args: args{
conf: &config.Config{
BPFDir: "/tmp",
EBPFRepoURL: "https://l3af.io/",
},
},
wantErr: true,
},
{
name: "Unknown_url_with_file_scheme",
fields: fields{
Program: models.BPFProgram{
EPRURL: "file:///Users/random/dummy.tar.gz",
},
Cmd: nil,
FilePath: "",
RestartCount: 0,
},
args: args{
conf: &config.Config{
BPFDir: "/tmp",
EBPFRepoURL: "https://l3af.io/",
},
},
wantErr: true,
},
{
name: "Unknown_scheme",
fields: fields{
Program: models.BPFProgram{
EPRURL: "ftp://ftp.foo.org/dummy.tar.gz",
},
Cmd: nil,
FilePath: "",
RestartCount: 0,
},
args: args{
conf: &config.Config{
BPFDir: "/tmp",
EBPFRepoURL: "https://l3af.io/",
},
},
wantErr: true,
},
{
name: "ZipReaderFail",
fields: fields{
Program: models.BPFProgram{
Name: "testebpfprogram",
Version: "1.0",
Artifact: "testebpfprogram.zip",
},
Cmd: nil,
FilePath: "",
RestartCount: 0,
Client: NewTestClient(func(r *http.Request) *http.Response {
buf := new(bytes.Buffer)
writer := zip.NewWriter(buf)
f, _ := writer.Create("testebpfprogram")
data := strings.NewReader("this is just a test ebpf program")
io.Copy(f, data)
writer.Close()
return &http.Response{
StatusCode: 200,
Body: io.NopCloser(buf),
Header: make(http.Header),
}
}),
},
args: args{
conf: &config.Config{
BPFDir: "/tmp",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &BPF{
Program: tt.fields.Program,
Cmd: tt.fields.Cmd,
FilePath: tt.fields.FilePath,
RestartCount: tt.fields.RestartCount,
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := mocks.NewMockplatformInterface(ctrl)
if runtime.GOOS == "windows" {
m.EXPECT().GetPlatform().Return("windows", nil).AnyTimes()
} else {
m.EXPECT().GetPlatform().Return("focal", nil).AnyTimes()
}
err := b.GetArtifacts(tt.args.conf)
if (err != nil) != tt.wantErr {
t.Errorf("BPF.download() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestBPF_SetPrLimits(t *testing.T) {
type fields struct {
Program models.BPFProgram
Cmd *exec.Cmd
// Pid int
FilePath string
RestartCount int
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{name: "DefaultLimitsWithNoCmd",
fields: fields{
Program: models.BPFProgram{},
Cmd: nil,
FilePath: "",
RestartCount: 0,
},
wantErr: true,
},
{name: "ValidLimitsWithNoCmd",
fields: fields{
Program: models.BPFProgram{
CPU: 100,
Memory: 1024,
},
Cmd: fakeExecCommand("/foo/foo"),
FilePath: "",
RestartCount: 0,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &BPF{
Program: tt.fields.Program,
Cmd: tt.fields.Cmd,
// Pid: tt.fields.Pid,
FilePath: tt.fields.FilePath,
RestartCount: tt.fields.RestartCount,
}
if err := b.SetPrLimits(); (err != nil) != tt.wantErr {
t.Errorf("BPF.SetPrLimits() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_assertExecute(t *testing.T) {
type args struct {
filepath string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "InvalidFilepath",
args: args{
filepath: "/tmp/dummyfile",
},
wantErr: true,
},
{
name: "ValidFilepath",
args: args{
filepath: GetTestExecutablePathName(),
},
wantErr: false,
},
{
name: "ValidFilepathWihoutExecute",
args: args{
filepath: GetTestNonexecutablePathName(),
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := assertExecutable(tt.args.filepath); (err != nil) != tt.wantErr {
t.Errorf("assertExecute() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_fileExists(t *testing.T) {
tests := []struct {
name string
fileName string
exist bool
}{
{
name: "invalidfilename",
fileName: "blahblah",
exist: false,
},
}
for _, tt := range tests {
if fileExists(tt.fileName) != tt.exist {
t.Errorf("Invalid filename")
}
}
}
func Test_StopExternalRunningProcess(t *testing.T) {
tests := []struct {
name string
processName string
wantErr bool
}{
{
name: "emptyProcessName",
processName: "",
wantErr: true,
},
}
for _, tt := range tests {
err := StopExternalRunningProcess(tt.processName)
if (err != nil) != tt.wantErr {
t.Errorf("Error During execution StopExternalRunningProcess : %v", err)
}
}
}
func Test_createUpdateRulesFile(t *testing.T) {
type fields struct {
Program models.BPFProgram
Cmd *exec.Cmd
// Pid int
FilePath string
RestartCount int
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{
name: "emptyRuleFileName",
fields: fields{
Program: models.BPFProgram{
RulesFile: "",
},
Cmd: nil,
FilePath: "",
RestartCount: 0,
},
wantErr: true,
},
{
name: "invalidPath",
fields: fields{
Program: models.BPFProgram{
RulesFile: "bad",
},
Cmd: nil,
FilePath: "/dummy/fpp",
RestartCount: 0,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &BPF{
Program: tt.fields.Program,
Cmd: tt.fields.Cmd,
FilePath: tt.fields.FilePath,
RestartCount: tt.fields.RestartCount,
}
_, err := b.createUpdateRulesFile("ingress")
if (err != nil) != tt.wantErr {
t.Errorf("createUpdateRulesFile() error : %v", err)
}
})
}
}
func Test_PutNextProgFDFromID(t *testing.T) {
type fields struct {
Program models.BPFProgram
Cmd *exec.Cmd
FilePath string
RestartCount int
hostConfig *config.Config
}
tests := []struct {
name string
fields fields
wantErr bool
progId int
hostConfig *config.Config
}{
{
name: "emptyMapName",
fields: fields{
Program: models.BPFProgram{
MapName: "",
},
hostConfig: &config.Config{
BpfMapDefaultPath: "/sys/fs/bpf",
},
},
wantErr: false,
progId: 1,
},
{
name: "invalidMapName",
fields: fields{
Program: models.BPFProgram{
MapName: "invalidname",
},
hostConfig: &config.Config{
BpfMapDefaultPath: "/sys/fs/bpf",
},
},
wantErr: true,
progId: 1,
},
{
name: "invalidProgID",
fields: fields{
Program: models.BPFProgram{
Name: "ratelimiting",
SeqID: 1,
Artifact: "l3af_ratelimiting.tar.gz",
MapName: "xdp_rl_ingress_next_prog",
CmdStart: "ratelimiting",
Version: "latest",
UserProgramDaemon: true,
AdminStatus: "enabled",
ProgType: "xdp",
CfgVersion: 1,
},
hostConfig: &config.Config{
BpfMapDefaultPath: "/sys/fs/bpf",
},
},
progId: -1,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &BPF{
Program: tt.fields.Program,
Cmd: tt.fields.Cmd,
FilePath: tt.fields.FilePath,
RestartCount: tt.fields.RestartCount,
HostConfig: tt.fields.hostConfig,
}
err := b.PutNextProgFDFromID(tt.progId)
if (err != nil) != tt.wantErr {
t.Errorf("PutNextProgFDFromID() error : %v", err)
}
})
}
}
func Test_VerifyPinnedProgMapExists(t *testing.T) {
type fields struct {
Program models.BPFProgram
Cmd *exec.Cmd
FilePath string
RestartCount int
hostConfig *config.Config
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{
name: "invalidMapName",
fields: fields{
Program: models.BPFProgram{
MapName: "invalid",
},
hostConfig: &config.Config{
BpfMapDefaultPath: "/sys/fs/bpf",
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &BPF{
Program: tt.fields.Program,
Cmd: tt.fields.Cmd,
FilePath: tt.fields.FilePath,
RestartCount: tt.fields.RestartCount,
HostConfig: tt.fields.hostConfig,
}
err := b.VerifyPinnedProgMap(true, true)
if (err != nil) != tt.wantErr {
t.Errorf("VerifyPinnedMapExists() error : %v", err)
}
})
}
}
func Test_VerifyProcessObject(t *testing.T) {
type fields struct {
Program models.BPFProgram
Cmd *exec.Cmd
FilePath string
RestartCount int
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{
name: "nilCmd",
fields: fields{
Program: models.BPFProgram{},
Cmd: nil,
FilePath: "",
RestartCount: 0,
},
wantErr: true,
},
{
name: "nillCmdProcess",
fields: fields{
Program: models.BPFProgram{},
Cmd: &exec.Cmd{
Process: nil,
},
FilePath: "",
RestartCount: 0,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &BPF{
Program: tt.fields.Program,
Cmd: tt.fields.Cmd,
FilePath: tt.fields.FilePath,
RestartCount: tt.fields.RestartCount,
}
err := b.VerifyProcessObject()
if (err != nil) != tt.wantErr {
t.Errorf("VerifyProcessObject() error : %v", err)
}
})
}
}
func Test_VerifyPinnedProgMapVanish(t *testing.T) {
type fields struct {
Program models.BPFProgram
Cmd *exec.Cmd
FilePath string
RestartCount int
hostConfig *config.Config
}
tests := []struct {
name string
fields fields
wantErr bool
}{
{
name: "emptyMapName",
fields: fields{
Program: models.BPFProgram{
MapName: "",
},
hostConfig: &config.Config{
BpfMapDefaultPath: "/sys/fs/bpf",
},
},
wantErr: false,
},
{
name: "invalidProgType",
fields: fields{
Program: models.BPFProgram{
MapName: "tc/globals/something",
ProgType: models.TCType,
},
hostConfig: &config.Config{
BpfMapDefaultPath: "/sys/fs/bpf",
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &BPF{
Program: tt.fields.Program,
Cmd: tt.fields.Cmd,
FilePath: tt.fields.FilePath,
RestartCount: tt.fields.RestartCount,
HostConfig: &config.Config{
BpfMapDefaultPath: tt.fields.hostConfig.BpfMapDefaultPath,
},
}
err := b.VerifyPinnedProgMap(true, false)
if (err != nil) != tt.wantErr {
t.Errorf("VerifyPinnedMapVanish() error : %v", err)
}
})
}
}
================================================
FILE: bpfprogs/bpf_test_unix.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
//
//go:build !WINDOWS
// +build !WINDOWS
package bpfprogs
import (
"fmt"
"os"
)
func GetTestNonexecutablePathName() string {
return "/var/log/syslog"
}
func GetTestExecutablePathName() string {
return "/bin/date"
}
func GetTestExecutablePath() string {
return "/bin"
}
func GetTestExecutableName() string {
return "date"
}
// assertExecutable checks for executable permissions
func assertExecutable(fPath string) error {
info, err := os.Stat(fPath)
if err != nil {
return fmt.Errorf("could not stat file: %s with error: %w", fPath, err)
}
if (info.Mode()&os.ModePerm)&os.FileMode(executePerm) == 0 {
return fmt.Errorf("file: %s, is not executable", fPath)
}
return nil
}
================================================
FILE: bpfprogs/bpf_test_windows.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
//
//go:build WINDOWS
// +build WINDOWS
package bpfprogs
import (
"fmt"
"os"
"strings"
)
func GetTestNonexecutablePathName() string {
return "c:/windows/system32/drivers/etc/host"
}
func GetTestExecutablePathName() string {
return "c:/windows/system32/net.exe"
}
func GetTestExecutablePath() string {
return "c:/windows/system32"
}
func GetTestExecutableName() string {
return "net.exe"
}
// assertExecutable checks for executable permissions
func assertExecutable(fPath string) error {
_, err := os.Stat(fPath)
if err != nil {
return fmt.Errorf("could not stat file: %s with error: %v", fPath, err)
}
// info.Mode() does not return the correct permissions on Windows,
// it always has the 'x' permissions clear, so instead use the file suffix.
if !strings.HasSuffix(fPath, ".exe") {
return fmt.Errorf("file: %s, is not executable", fPath)
}
return nil
}
================================================
FILE: bpfprogs/bpf_unix.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
//
//go:build !WINDOWS
// +build !WINDOWS
// Package bpfprogs provides primitives for l3afd's network function configs.
package bpfprogs
import (
"bytes"
"errors"
"fmt"
"net"
"os"
"strings"
"syscall"
"unsafe"
"github.com/l3af-project/l3afd/v2/models"
"github.com/l3af-project/l3afd/v2/utils"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
"github.com/florianl/go-tc"
"github.com/florianl/go-tc/core"
"github.com/rs/zerolog/log"
"github.com/safchain/ethtool"
"golang.org/x/sys/unix"
)
// DisableLRO - XDP programs are failing when LRO is enabled, to fix this we use to manually disable.
// # ethtool -K ens7 lro off
// # ethtool -k ens7 | grep large-receive-offload
// large-receive-offload: off
func DisableLRO(ifaceName string) error {
ethHandle, err := ethtool.NewEthtool()
if err != nil {
err = fmt.Errorf("ethtool failed to get the handle %w", err)
log.Error().Err(err).Msg("")
return err
}
defer ethHandle.Close()
config := make(map[string]bool, 1)
config["rx-lro"] = false
if err := ethHandle.Change(ifaceName, config); err != nil {
err = fmt.Errorf("ethtool failed to disable LRO on %s with err %w", ifaceName, err)
log.Error().Err(err).Msg("")
return err
}
return nil
}
// prLimit set the memory and cpu limits for the bpf program
func prLimit(pid int, limit uintptr, rlimit *unix.Rlimit) error {
_, _, errno := unix.RawSyscall6(unix.SYS_PRLIMIT64,
uintptr(pid),
limit,
uintptr(unsafe.Pointer(rlimit)),
0, 0, 0)
if errno != 0 {
log.Error().Msgf("Failed to set prlimit for process %d and errorno %d", pid, errno)
return errors.New("failed to set prlimit")
}
return nil
}
// Set process resource limits only non-zero value
func (b *BPF) SetPrLimits() error {
var rlimit unix.Rlimit
if b.Cmd == nil {
return errors.New("no Process to set limits")
}
if b.Program.Memory != 0 {
rlimit.Cur = uint64(b.Program.Memory)
rlimit.Max = uint64(b.Program.Memory)
if err := prLimit(b.Cmd.Process.Pid, unix.RLIMIT_AS, &rlimit); err != nil {
log.Error().Err(err).Msgf("Failed to set Memory limits - %s", b.Program.Name)
}
}
if b.Program.CPU != 0 {
rlimit.Cur = uint64(b.Program.CPU)
rlimit.Max = uint64(b.Program.CPU)
if err := prLimit(b.Cmd.Process.Pid, unix.RLIMIT_CPU, &rlimit); err != nil {
log.Error().Err(err).Msgf("Failed to set CPU limits - %s", b.Program.Name)
}
}
return nil
}
// ProcessTerminate - Send sigterm to the process
func (b *BPF) ProcessTerminate() error {
if err := b.Cmd.Process.Signal(syscall.SIGTERM); err != nil {
return fmt.Errorf("BPFProgram %s SIGTERM failed with error: %w", b.Program.Name, err)
}
return nil
}
// VerifyNMountBPFFS - Mounting bpf filesystem
func VerifyNMountBPFFS() error {
dstPath := "/sys/fs/bpf"
srcPath := "bpffs"
fstype := "bpf"
flags := 0
mnts, err := os.ReadFile("/proc/mounts")
if err != nil {
return fmt.Errorf("failed to read procfs: %w", err)
}
if !strings.Contains(string(mnts), dstPath) {
log.Warn().Msg("bpf filesystem is not mounted going to mount")
if err = syscall.Mount(srcPath, dstPath, fstype, uintptr(flags), ""); err != nil {
return fmt.Errorf("unable to mount %s at %s: %w", srcPath, dstPath, err)
}
}
return VerifyNMountTraceFS()
}
// VerifyNMounTraceFS - Mounting trace filesystem
func VerifyNMountTraceFS() error {
dstPath := "/sys/kernel/debug/tracing"
srcPath := "tracefs"
fstype := "tracefs"
flags := syscall.MS_NODEV | syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_RELATIME
mnts, err := os.ReadFile("/proc/self/mounts")
if err != nil {
return fmt.Errorf("failed to read procfs: %w", err)
}
if !strings.Contains(string(mnts), dstPath) {
log.Warn().Msgf(" %s filesystem is not mounted going to mount", dstPath)
if _, err = os.Stat(dstPath); err != nil {
log.Warn().Msgf(" %s directory doesn't exists, creating", dstPath)
if err := os.Mkdir(dstPath, 0700); err != nil {
return fmt.Errorf("unable to create mount point %s : %w", dstPath, err)
}
}
if err = syscall.Mount(srcPath, dstPath, fstype, uintptr(flags), ""); err != nil {
return fmt.Errorf("unable to mount %s at %s: %w", srcPath, dstPath, err)
}
}
return nil
}
// This method get the Linux distribution Codename. This logic works on ubuntu
// Here assumption is all edge nodes are running with lsb modules.
// It returns empty string in case of error
func GetPlatform() (string, error) {
linuxDistrib := execCommand("lsb_release", "-cs")
var out bytes.Buffer
linuxDistrib.Stdout = &out
if err := linuxDistrib.Run(); err != nil {
return "", fmt.Errorf("l3afd/nf : Failed to run command with error: %w", err)
}
return strings.TrimSpace(out.String()), nil
}
func IsProcessRunning(pid int, name string) (bool, error) {
procState, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
if err != nil {
return false, fmt.Errorf("BPF Program not running %s because of error: %w", name, err)
}
var u1, u2, state string
_, err = fmt.Sscanf(string(procState), "%s %s %s", &u1, &u2, &state)
if err != nil {
return false, fmt.Errorf("failed to scan proc state with error: %w", err)
}
if state == "Z" {
return false, fmt.Errorf("process %d in Zombie state", pid)
}
return true, nil
}
// VerifyNCreateTCDirs - Creating BPF sudo FS for pinning TC maps
func VerifyNCreateTCDirs() error {
path := "/sys/fs/bpf/tc/globals"
if _, err := os.Stat(path); err == nil {
log.Debug().Msgf(" %s tc directory exists", path)
return nil
}
log.Info().Msgf(" %s tc directory doesn't exists, creating", path)
if err := os.MkdirAll(path, 0700); err != nil {
return fmt.Errorf("unable to create directories to pin tc maps %s : %w", path, err)
}
return nil
}
// LoadTCAttachProgram - Load and attach tc root program filters or any tc program when chaining is disabled
func (b *BPF) LoadTCAttachProgram(ifaceName, direction string) error {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
log.Error().Err(err).Msgf("LoadTCAttachProgram - look up network iface %q", ifaceName)
return err
}
if err := b.LoadBPFProgram(ifaceName); err != nil {
return err
}
// verify and add attribute clsact
tcgo, err := tc.Open(&tc.Config{})
if err != nil {
return fmt.Errorf("could not open rtnetlink socket for interface %s : %w", ifaceName, err)
}
clsactFound := false
htbFound := false
ingressFound := false
var htbHandle uint32
var ingressHandle uint32
var parentHandle uint32
// get all the qdiscs from all interfaces
qdiscs, err := tcgo.Qdisc().Get()
if err != nil {
return fmt.Errorf("could not get qdiscs for interface %s : %w", ifaceName, err)
}
for _, qdisc := range qdiscs {
iface, err := net.InterfaceByIndex(int(qdisc.Ifindex))
if err != nil {
return fmt.Errorf("could not get interface %s from id %d: %w", ifaceName, qdisc.Ifindex, err)
}
if iface.Name == ifaceName {
switch qdisc.Kind {
case "clsact":
clsactFound = true
case "htb":
htbFound = true
htbHandle = qdisc.Msg.Handle
case "ingress":
ingressFound = true
ingressHandle = qdisc.Msg.Handle
default:
log.Info().Msgf("Un-supported qdisc kind for interface %s : %s ", ifaceName, qdisc.Kind)
}
}
}
bpfRootProg := b.ProgMapCollection.Programs[b.Program.EntryFunctionName]
var parent uint32
var filter tc.Object
progFD := uint32(bpfRootProg.FD())
// Netlink attribute used in the Linux kernel
bpfFlag := uint32(tc.BpfActDirect)
if clsactFound {
if direction == models.IngressType {
parent = tc.HandleMinIngress
} else if direction == models.EgressType {
parent = tc.HandleMinEgress
}
filter = tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(iface.Index),
Handle: 0,
Parent: core.BuildHandle(tc.HandleRoot, parent),
Info: 0x300,
},
Attribute: tc.Attribute{
Kind: "bpf",
BPF: &tc.Bpf{
FD: &progFD,
Flags: &bpfFlag,
},
},
}
} else if !clsactFound && !ingressFound && !htbFound {
qdisc := tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(iface.Index),
Handle: core.BuildHandle(tc.HandleRoot, 0x0000),
Parent: tc.HandleIngress,
Info: 0,
},
Attribute: tc.Attribute{
Kind: "clsact",
},
}
if err := tcgo.Qdisc().Add(&qdisc); err != nil {
log.Info().Msgf("could not assign clsact to %s : %v, its already exists", ifaceName, err)
}
if direction == models.IngressType {
parent = tc.HandleMinIngress
} else if direction == models.EgressType {
parent = tc.HandleMinEgress
}
filter = tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(iface.Index),
Handle: 0,
Parent: core.BuildHandle(tc.HandleRoot, parent),
Info: 0x300,
},
Attribute: tc.Attribute{
Kind: "bpf",
BPF: &tc.Bpf{
FD: &progFD,
Flags: &bpfFlag,
},
},
}
} else if !clsactFound && ingressFound && htbFound {
if direction == models.IngressType {
parentHandle = ingressHandle
} else if direction == models.EgressType {
parentHandle = htbHandle
}
// parentNew needs to handle of HTB and ingress 1:, and ffff:
filter = tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(iface.Index),
Handle: 0,
Parent: parentHandle,
Info: 0x300,
},
Attribute: tc.Attribute{
Kind: "bpf",
BPF: &tc.Bpf{
FD: &progFD,
Flags: &bpfFlag,
},
},
}
} else {
log.Info().Msgf("Unable to create qdisc object for interface %s", ifaceName)
}
// Storing Filter handle
filterHandle := tcgo.Filter()
// Attaching / Adding as filter
if err := filterHandle.Add(&filter); err != nil {
return fmt.Errorf("could not attach filter to interface %s for eBPF program %s : %w", ifaceName, b.Program.Name, err)
}
if b.HostConfig.BpfChainingEnabled {
if err = b.UpdateProgramMap(ifaceName); err != nil {
return err
}
}
return nil
}
// UnloadTCProgram - Remove TC filters
func (b *BPF) UnloadTCProgram(ifaceName, direction string) error {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
log.Error().Err(err).Msgf("UnloadTCProgram - look up network iface %q", ifaceName)
return err
}
tcgo, err := tc.Open(&tc.Config{})
if err != nil {
log.Error().Err(err).Msgf("UnloadTCProgram - Unable to tc.Open(&tc.Config{}): %q", ifaceName)
return err
}
clsactFound := false
htbFound := false
ingressFound := false
var htbHandle uint32
var ingressHandle uint32
var parentHandle uint32
// get all the qdiscs from all interfaces
qdiscs, err := tcgo.Qdisc().Get()
if err != nil {
log.Warn().Msgf("Could not get filters for interface \"%s\" direction %s ", ifaceName, direction)
return fmt.Errorf("could not get filters for interface %s : %w", ifaceName, err)
}
for _, qdisc := range qdiscs {
iface, err := net.InterfaceByIndex(int(qdisc.Ifindex))
if err != nil {
return fmt.Errorf("could not get interface %s from id %d: %v", ifaceName, qdisc.Ifindex, err)
}
if iface.Name == ifaceName {
switch qdisc.Kind {
case "clsact":
clsactFound = true
case "htb":
htbFound = true
htbHandle = qdisc.Msg.Handle
case "ingress":
ingressFound = true
ingressHandle = qdisc.Msg.Handle
default:
log.Info().Msgf("qdisc kind for %s : %v", ifaceName, qdisc.Kind)
}
}
}
bpfRootProg := b.ProgMapCollection.Programs[b.Program.EntryFunctionName]
// Storing Filter handle
filterHandle := tcgo.Filter()
var parent uint32
var filter tc.Object
if clsactFound && !ingressFound && !htbFound {
if direction == models.IngressType {
parent = tc.HandleMinIngress
} else if direction == models.EgressType {
parent = tc.HandleMinEgress
}
tcfilts, err := filterHandle.Get(&tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(iface.Index),
Handle: 0x0,
Parent: core.BuildHandle(tc.HandleRoot, parent),
})
if err != nil {
log.Warn().Msgf("Could not get filters for interface \"%s\" direction %s ", ifaceName, direction)
return fmt.Errorf("could not get filters for interface %s : %v", ifaceName, err)
}
progFD := uint32(bpfRootProg.FD())
// Netlink attribute used in the Linux kernel
bpfFlag := uint32(tc.BpfActDirect)
if len(tcfilts) > 0 {
filter = tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(iface.Index),
Handle: 0,
Parent: core.BuildHandle(tc.HandleRoot, parent),
Info: tcfilts[0].Msg.Info,
},
Attribute: tc.Attribute{
Kind: "bpf",
BPF: &tc.Bpf{
FD: &progFD,
Flags: &bpfFlag,
},
},
}
} else {
log.Warn().Msgf("unload TC program clasct filters not found for interface %s direction %s", ifaceName, direction)
}
} else if !clsactFound && ingressFound && htbFound {
if direction == models.EgressType {
parentHandle = htbHandle
// _ = pa("parentNew...1 ", parentNew)
} else if direction == models.IngressType {
parentHandle = ingressHandle
}
tcfilts, err := filterHandle.Get(&tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(iface.Index),
Handle: 0x0,
Parent: parentHandle,
})
if err != nil {
log.Warn().Msgf("Could not get filters for interface \"%s\" direction %s ", ifaceName, direction)
return fmt.Errorf("could not get filters for interface %s : %v", ifaceName, err)
}
progFD := uint32(bpfRootProg.FD())
// Netlink attribute used in the Linux kernel
bpfFlag := uint32(tc.BpfActDirect)
var tcFilterIndex int
for i, tcfilt := range tcfilts {
// finding the Info field of the relevant BPF filter among all set filters for that qdisc
if tcfilt.Attribute.Kind == "bpf" {
tcFilterIndex = i
}
}
// Add a check for if tcFilterIndex out of bounds
if len(tcfilts) > 0 {
filter = tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(iface.Index),
Handle: 0,
Parent: parentHandle,
Info: tcfilts[tcFilterIndex].Msg.Info,
},
Attribute: tc.Attribute{
Kind: "bpf",
BPF: &tc.Bpf{
FD: &progFD,
Flags: &bpfFlag,
},
},
}
} else {
log.Warn().Msgf("unload TC program ingress or htp filters not found for interface %s direction %s", ifaceName, direction)
}
}
// Detaching / Deleting filter
if err := filterHandle.Delete(&filter); err != nil {
return fmt.Errorf("could not dettach tc filter for interface %s : Direction: %v, parentHandle: %v, Error:%w", ifaceName, direction, parentHandle, err)
}
return nil
}
// LoadTCXAttachProgram - Load and attach xdp root program or any xdp program when chaining is disabled
func (b *BPF) LoadTCXAttachProgram(ifaceName, direction string) error {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
log.Error().Err(err).Msgf("LoadXDPAttachProgram -look up network iface %q", ifaceName)
return err
}
if err := b.LoadBPFProgram(ifaceName); err != nil {
return err
}
var attachType ebpf.AttachType
if direction == models.IngressType {
attachType = ebpf.AttachTCXIngress
} else {
attachType = ebpf.AttachTCXEgress
}
b.Link, err = link.AttachTCX(link.TCXOptions{
Program: b.ProgMapCollection.Programs[b.Program.EntryFunctionName],
Interface: iface.Index,
Attach: attachType,
})
if err != nil {
return fmt.Errorf("could not attach tc program %s to interface %s direction %s : %w", b.Program.Name, ifaceName, direction, err)
}
version := utils.ReplaceDotsWithUnderscores(b.Program.Version)
// Pin the Link
linkPinPath := utils.TCLinkPinPath(b.HostConfig.BpfMapDefaultPath, ifaceName, b.Program.Name, version, b.Program.ProgType, direction)
if err := b.Link.Pin(linkPinPath); err != nil {
return fmt.Errorf("tcx program pinning failed program %s direction %s interface %s : %w", b.Program.Name, direction, ifaceName, err)
}
if b.HostConfig.BpfChainingEnabled {
if err = b.UpdateProgramMap(ifaceName); err != nil {
return err
}
}
return nil
}
// LoadXDPAttachProgram - Load and attach xdp root program or any xdp program when chaining is disabled
// This method has been moved to linux specific till cilium windows library implements link.AttachXDP
func (b *BPF) LoadXDPAttachProgram(ifaceName string) error {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
log.Error().Err(err).Msgf("LoadXDPAttachProgram -look up network iface %q", ifaceName)
return err
}
if err := b.LoadBPFProgram(ifaceName); err != nil {
return err
}
b.Link, err = link.AttachXDP(link.XDPOptions{
Program: b.ProgMapCollection.Programs[b.Program.EntryFunctionName],
Interface: iface.Index,
})
if err != nil {
return fmt.Errorf("could not attach xdp program %s to interface %s : %w", b.Program.Name, ifaceName, err)
}
version := utils.ReplaceDotsWithUnderscores(b.Program.Version)
// Pin the Link
linkPinPath := utils.LinkPinPath(b.HostConfig.BpfMapDefaultPath, ifaceName, b.Program.Name, version, b.Program.ProgType)
if err := b.Link.Pin(linkPinPath); err != nil {
return fmt.Errorf("xdp program pinning failed program %s interface %s : %w", b.Program.Name, ifaceName, err)
}
if b.HostConfig.BpfChainingEnabled {
if err = b.UpdateProgramMap(ifaceName); err != nil {
return err
}
}
return nil
}
================================================
FILE: bpfprogs/bpf_windows.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
//
//go:build WINDOWS
// +build WINDOWS
// Package bpfprogs provides primitives for l3afd's network function configs.
package bpfprogs
import (
"errors"
"fmt"
"os"
"github.com/cilium/ebpf"
)
// DisableLRO - XDP programs are failing when Large Receive Offload is enabled, to fix this we use to manually disable.
func DisableLRO(ifaceName string) error {
return nil
}
// Set process resource limits only non-zero value
func (b *BPF) SetPrLimits() error {
if b.Cmd == nil {
return errors.New("no Process to set limits")
}
return nil
}
// VerifyNMountBPFFS - Mounting bpf filesystem
func VerifyNMountBPFFS() error {
return nil
}
func GetPlatform() (string, error) {
return "Windows", nil
}
func IsProcessRunning(pid int, name string) (bool, error) {
_, err := os.FindProcess(pid)
if err != nil {
return false, fmt.Errorf("BPF Program not running %s because of error: %w", name, err)
}
return true, nil
}
// ProcessTerminate - Kills the process
func (b *BPF) ProcessTerminate() error {
if err := b.Cmd.Process.Kill(); err != nil {
return fmt.Errorf("BPFProgram %s kill failed with error: %w", b.Program.Name, err)
}
return nil
}
// VerifyNCreateTCDirs - Creating BPF sudo FS for pinning TC maps
func VerifyNCreateTCDirs() error {
return nil
}
// LoadTCAttachProgram - not implemented in windows
func (b *BPF) LoadTCAttachProgram(ifaceName, direction string) error {
// not implement nothing todo
return fmt.Errorf("LoadTCAttachProgram - TC programs Unsupported on windows")
}
// LoadTCXAttachProgram - not implemented in windows
func (b *BPF) LoadTCXAttachProgram(ifaceName, direction string) error {
// not implement nothing todo
return fmt.Errorf("LoadTCXAttachProgram - TC programs Unsupported on windows")
}
// UnloadTCProgram - Remove TC filters
func (b *BPF) UnloadTCProgram(ifaceName, direction string) error {
// not implement nothing todo
return fmt.Errorf("UnloadTCProgram - TC programs Unsupported on windows")
}
// LoadXDPAttachProgram - Attaches XDP program to interface
func (b *BPF) LoadXDPAttachProgram(ifaceName string) error {
// not implement nothing todo
return fmt.Errorf("LoadXDPAttachProgram - AttachXDP method is Unsupported on windows")
}
// LoadBPFProgramProbeType - Loads Probe type bpf program
func (b *BPF) LoadBPFProgramProbeTypes(objSpec *ebpf.CollectionSpec) error {
// not implement nothing todo
return fmt.Errorf("LoadBPFProgramProbeTypes - Probes are Unsupported on windows")
}
================================================
FILE: bpfprogs/bpfdebug.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package bpfprogs
import (
"encoding/json"
"errors"
"net"
"net/http"
"strings"
"github.com/l3af-project/l3afd/v2/models"
"github.com/rs/zerolog/log"
)
var bpfcfgs *NFConfigs
func SetupBPFDebug(ebpfChainDebugAddr string, BPFConfigs *NFConfigs) {
bpfcfgs = BPFConfigs
go func() {
if _, ok := models.AllNetListeners.Load("debug_http"); !ok {
tcpAddr, err := net.ResolveTCPAddr("tcp", ebpfChainDebugAddr)
if err != nil {
log.Fatal().Err(err).Msgf("unable to resolve tcpaddr %v ", ebpfChainDebugAddr)
return
}
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
log.Fatal().Err(err).Msgf("unable to create tcp listener")
}
models.AllNetListeners.Store("debug_http", listener)
}
http.HandleFunc("/bpfs/", ViewHandler)
// We just need to start a server.
log.Info().Msg("Starting BPF debug server")
val, _ := models.AllNetListeners.Load("debug_http")
l, _ := val.(*net.TCPListener)
if err := http.Serve(l, nil); !errors.Is(err, http.ErrServerClosed) {
log.Fatal().Err(err).Msg("failed to start BPF chain debug server")
}
}()
}
func ViewHandler(w http.ResponseWriter, r *http.Request) {
iface := strings.TrimPrefix(r.URL.Path, "/bpfs/")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(bpfcfgs.BPFDetails(iface)); err != nil {
log.Err(err).Msgf("unable to serialize json")
}
}
================================================
FILE: bpfprogs/bpfmap.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package bpfprogs
import (
"container/ring"
"errors"
"fmt"
"math"
"unsafe"
"github.com/cilium/ebpf"
"github.com/l3af-project/l3afd/v2/models"
"github.com/rs/zerolog/log"
)
type BPFMap struct {
Name string
MapID ebpf.MapID
Type ebpf.MapType
// BPFProg reference in case of stale map id
BPFProg *BPF `json:"-"`
}
// This stores Metrics map details.
type MetricsBPFMap struct {
BPFMap
Key int
Values *ring.Ring
Aggregator string
LastValue float64
}
// The RemoveMissingKeys function is used to delete missing entries of eBPF maps, which are used by eBPF Programs.
func (b *BPFMap) RemoveMissingKeys(args []models.KeyValue) error {
ebpfMap, err := ebpf.NewMapFromID(b.MapID)
if err != nil {
return fmt.Errorf("access new map from ID failed %w", err)
}
defer ebpfMap.Close()
KeyValueMap := make(map[int]bool, len(args))
for _, k := range args {
KeyValueMap[k.Key] = true
}
var key, nextKey int
for {
err := ebpfMap.NextKey(unsafe.Pointer(&key), unsafe.Pointer(&nextKey))
if err != nil {
if errors.Is(err, ebpf.ErrKeyNotExist) {
break
} else {
return fmt.Errorf("get next key failed with error %w, mapid %d", err, b.MapID)
}
}
key = nextKey
_, IsKeyExists := KeyValueMap[key]
if !IsKeyExists {
log.Info().Msgf("removing key %v because it is missing\n", key)
if err := ebpfMap.Delete(unsafe.Pointer(&key)); err != nil {
return fmt.Errorf("delete key failed with error %w, mapid %d", err, b.MapID)
}
}
}
return nil
}
// The update function is used to update eBPF maps, which are used by eBPF programs.
func (b *BPFMap) Update(key, value int) error {
log.Debug().Msgf("update map name %s ID %d", b.Name, b.MapID)
ebpfMap, err := ebpf.NewMapFromID(b.MapID)
if err != nil {
return fmt.Errorf("access new map from ID failed %w", err)
}
defer ebpfMap.Close()
log.Info().Msgf("updating map %s key %d mapid %d", b.Name, key, b.MapID)
if err := ebpfMap.Update(unsafe.Pointer(&key), unsafe.Pointer(&value), 0); err != nil {
return fmt.Errorf("update hash map element failed for key %d error %w", key, err)
}
return nil
}
// Get value of the map for given key
// There are 2 aggregators are supported here
// max-rate - this calculates delta requests / sec and stores absolute value.
// avg - stores the values in the circular queue
// We can implement more aggregate function as needed.
func (b *MetricsBPFMap) GetValue() float64 {
ebpfMap, err := ebpf.NewMapFromID(b.MapID)
if err != nil {
// We have observed in smaller configuration VM's, if we restart BPF's
// Stale mapID's are reported, in such cases re-checking map id
log.Warn().Err(err).Msgf("GetValue : NewMapFromID failed ID %d, re-looking up of map id", b.MapID)
tmpBPF, err := b.BPFProg.GetBPFMap(b.Name)
if err != nil {
log.Warn().Err(err).Msgf("GetValue: Update new map ID %d", tmpBPF.MapID)
return 0
}
log.Info().Msgf("GetValue: Update new map ID %d", tmpBPF.MapID)
b.MapID = tmpBPF.MapID
ebpfMap, err = ebpf.NewMapFromID(b.MapID)
if err != nil {
log.Warn().Err(err).Msgf("GetValue : retry of NewMapFromID failed ID %d", b.MapID)
return 0
}
}
defer ebpfMap.Close()
var value int64
if err = ebpfMap.Lookup(unsafe.Pointer(&b.Key), unsafe.Pointer(&value)); err != nil {
log.Warn().Err(err).Msgf("GetValue Lookup failed : Name %s ID %d", b.Name, b.MapID)
return 0
}
var retVal float64
switch b.Aggregator {
case "scalar":
retVal = float64(value)
case "max-rate":
b.Values = b.Values.Next()
b.Values.Value = math.Abs(float64(float64(value) - b.LastValue))
b.LastValue = float64(value)
retVal = b.MaxValue()
case "avg":
b.Values.Value = value
b.Values = b.Values.Next()
retVal = b.AvgValue()
default:
log.Warn().Msgf("unsupported aggregator %s and value %d", b.Aggregator, value)
}
return retVal
}
// This method finds the max value in the circular list
func (b *MetricsBPFMap) MaxValue() float64 {
tmp := b.Values
var max float64
for i := 0; i < b.Values.Len(); i++ {
if tmp.Value != nil {
val := tmp.Value.(float64)
if max < val {
max = val
}
}
tmp = tmp.Next()
}
return max
}
// This method calculates the average
func (b *MetricsBPFMap) AvgValue() float64 {
tmp := b.Values.Next()
var sum float64
var n float64 = 0.0
for i := 0; i < b.Values.Len(); i++ {
if tmp.Value != nil {
sum = sum + tmp.Value.(float64)
n = n + 1
}
tmp = tmp.Next()
}
return sum / n
}
================================================
FILE: bpfprogs/bpfmap_test.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package bpfprogs
import (
"container/ring"
"reflect"
"testing"
)
var TestValues *ring.Ring = ring.New(10)
func SetupTestValues() {
a := [10]float64{8, 10, 6, 23, 4, 53, 32, 8, 2, 7}
v := TestValues
for i := 0; i < TestValues.Len(); i++ {
v.Value = a[i]
v = v.Next()
}
}
func TestMetricsBPFMapMaxValue(t *testing.T) {
type args struct {
key int
Values *ring.Ring
aggregator string
}
SetupTestValues()
tests := []struct {
name string
args args
want float64
wantErr bool
}{
{
name: "max-rate",
args: args{key: 0, Values: TestValues, aggregator: "max-rate"},
want: 53,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
metricsMap := &MetricsBPFMap{
Values: TestValues,
Key: 0,
Aggregator: tt.args.aggregator,
LastValue: 0,
}
got := (metricsMap.MaxValue())
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("MaxValue() = %v, want %v", got, tt.want)
}
})
}
}
func TestMetricsBPFMapAvgValue(t *testing.T) {
type args struct {
key int
Values *ring.Ring
aggregator string
}
SetupTestValues()
tests := []struct {
name string
args args
want float64
wantErr bool
}{
{
name: "avg",
args: args{key: 0, Values: TestValues, aggregator: "avg"},
want: 15.3,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
metricsMap := &MetricsBPFMap{
Values: TestValues,
Key: 0,
Aggregator: tt.args.aggregator,
LastValue: 0,
}
got := (metricsMap.AvgValue())
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("AvgValue() = %v, want %v", got, tt.want)
}
})
}
}
================================================
FILE: bpfprogs/bpfmetrics.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
// Package bpfprogs provides primitives for NF process monitoring.
package bpfprogs
import (
"container/list"
"time"
"github.com/l3af-project/l3afd/v2/models"
"github.com/rs/zerolog/log"
)
type BpfMetrics struct {
Chain bool
Intervals int
}
func NewpBpfMetrics(chain bool, interval int) *BpfMetrics {
m := &BpfMetrics{
Chain: chain,
Intervals: interval,
}
return m
}
func (c *BpfMetrics) BpfMetricsStart(xdpProgs, ingressTCProgs, egressTCProgs map[string]*list.List, probes *list.List, ifaces *map[string]string) {
go c.BpfMetricsWorker(xdpProgs, ifaces)
go c.BpfMetricsWorker(ingressTCProgs, ifaces)
go c.BpfMetricsWorker(egressTCProgs, ifaces)
go c.BpfMetricsProbeWorker(probes)
}
func (c *BpfMetrics) BpfMetricsWorker(bpfProgs map[string]*list.List, ifaces *map[string]string) {
for range time.NewTicker(1 * time.Second).C {
for ifaceName, bpfList := range bpfProgs {
if bpfList == nil { // no bpf programs are running
continue
}
for e := bpfList.Front(); e != nil; e = e.Next() {
bpf := e.Value.(*BPF)
if c.Chain && bpf.Program.SeqID == 0 { // do not monitor root program
continue
}
if bpf.Program.AdminStatus == models.Disabled {
continue
}
if err := bpf.MonitorMaps(ifaceName, (*ifaces)[ifaceName], c.Intervals); err != nil {
log.Debug().Err(err).Msgf("pMonitor monitor maps failed - %s", bpf.Program.Name)
}
}
}
}
}
func (c *BpfMetrics) BpfMetricsProbeWorker(bpfProgs *list.List) {
for range time.NewTicker(1 * time.Second).C {
if bpfProgs == nil {
time.Sleep(time.Second)
continue
}
for e := bpfProgs.Front(); e != nil; e = e.Next() {
bpf := e.Value.(*BPF)
if bpf.Program.AdminStatus == models.Disabled {
continue
}
if err := bpf.MonitorMaps("", "", c.Intervals); err != nil {
log.Debug().Err(err).Msgf("pMonitor probe monitor maps failed - %s", bpf.Program.Name)
}
}
}
}
================================================
FILE: bpfprogs/bpfmetrics_test.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
package bpfprogs
import (
"container/list"
"reflect"
"testing"
)
func TestNewpKFMetrics(t *testing.T) {
type args struct {
chain bool
interval int
}
tests := []struct {
name string
args args
want *BpfMetrics
wantErr bool
}{
{
name: "EmptypCheck",
args: args{chain: false, interval: 0},
want: &BpfMetrics{Chain: false, Intervals: 0},
wantErr: false,
},
{
name: "ValidpCheck",
args: args{chain: true, interval: 10},
want: &BpfMetrics{Chain: true, Intervals: 10},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewpBpfMetrics(tt.args.chain, tt.args.interval)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewKFMetrics() = %v, want %v", got, tt.want)
}
})
}
}
func Test_BPFMetrics_Start(t *testing.T) {
type fields struct {
Chain bool
Interval int
}
type args struct {
IngressXDPbpfProgs map[string]*list.List
IngressTCbpfProgs map[string]*list.List
EgressTCbpfProgs map[string]*list.List
Probes *list.List
Ifaces map[string]string
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "EmptyBPF",
fields: fields{Chain: true, Interval: 10},
args: args{IngressXDPbpfProgs: make(map[string]*list.List),
IngressTCbpfProgs: make(map[string]*list.List),
EgressTCbpfProgs: make(map[string]*list.List),
Ifaces: map[string]string{},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &BpfMetrics{
Chain: tt.fields.Chain,
Intervals: tt.fields.Interval,
}
c.BpfMetricsStart(tt.args.IngressXDPbpfProgs, tt.args.IngressTCbpfProgs, tt.args.EgressTCbpfProgs, tt.args.Probes, &tt.args.Ifaces)
})
}
}
================================================
FILE: bpfprogs/nfconfig.go
================================================
// Copyright Contributors to the L3AF Project.
// SPDX-License-Identifier: Apache-2.0
// Package bpfprogs provides primitives for l3afd's network function configs.
package bpfprogs
import (
"container/list"
"context"
"encoding/json"
"errors"
"fmt"
"net"
"os"
"reflect"
"slices"
"sort"
"strings"
"sync"
"time"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
"github.com/l3af-project/l3afd/v2/config"
"github.com/l3af-project/l3afd/v2/models"
"github.com/l3af-project/l3afd/v2/stats"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/zerolog/log"
)
type NFConfigs struct {
Ctx context.Context
HostName string
HostInterfaces map[string]bool
// configs sync.Map // key: string, val: *models.L3afDNFConfigDetail
// These holds bpf programs in the list
// map keys are network iface names index's are seq_id, position in the chain
// root element will be root program
IngressXDPBpfs map[string]*list.List
IngressTCBpfs map[string]*list.List
EgressTCBpfs map[string]*list.List
ProbesBpfs list.List
HostConfig *config.Config
ProcessMon *PCheck
BpfMetricsMon *BpfMetrics
// keep track of interfaces
Ifaces map[string]string
Mu *sync.Mutex
}
func NewNFConfigs(ctx context.Context, host string, hostConf *config.Config, pMon *PCheck, metricsMon *BpfMetrics) (*NFConfigs, error) {
nfConfigs := &NFConfigs{
Ctx: ctx,
HostName: host,
HostConfig: hostConf,
IngressXDPBpfs: make(map[string]*list.List),
IngressTCBpfs: make(map[string]*list.List),
EgressTCBpfs: make(map[string]*list.List),
Mu: new(sync.Mutex),
Ifaces: make(map[string]string),
}
var err error
if nfConfigs.HostInterfaces, err = getHostInterfaces(); err != nil {
errOut := fmt.Errorf("%s failed to get network interfaces %w", host, err)
log.Error().Err(errOut)
return nil, errOut
}
nfConfigs.ProcessMon = pMon
nfConfigs.ProcessMon.PCheckStart(nfConfigs.IngressXDPBpfs, nfConfigs.In
gitextract_j3_x9dxv/ ├── .github/ │ ├── dependabot.yml │ ├── release.yml │ └── workflows/ │ ├── ci-build-windows.yaml │ ├── ci-build.yaml │ ├── ci-e2e.yaml │ ├── codeql.yaml │ └── scorecards-analysis.yml ├── .gitignore ├── CMakeLists.txt ├── CODEOWNERS ├── LICENSE ├── Makefile ├── README.md ├── apis/ │ ├── configwatch.go │ ├── configwatch_test.go │ ├── handlers/ │ │ ├── addprog.go │ │ ├── addprog_test.go │ │ ├── deleteprog.go │ │ ├── deleteprog_test.go │ │ ├── getconfig.go │ │ ├── getconfig_test.go │ │ ├── restart_linux.go │ │ ├── restart_linux_test.go │ │ ├── restart_windows.go │ │ ├── restart_windows_test.go │ │ ├── updateconfig.go │ │ └── updateconfig_test.go │ └── routes.go ├── bpfprogs/ │ ├── bpf.go │ ├── bpfCfgs_internal.go │ ├── bpf_test.go │ ├── bpf_test_unix.go │ ├── bpf_test_windows.go │ ├── bpf_unix.go │ ├── bpf_windows.go │ ├── bpfdebug.go │ ├── bpfmap.go │ ├── bpfmap_test.go │ ├── bpfmetrics.go │ ├── bpfmetrics_test.go │ ├── nfconfig.go │ ├── nfconfig_test.go │ ├── probes.go │ ├── probes_test.go │ ├── processCheck.go │ └── processCheck_test.go ├── build-docker/ │ ├── Dockerfile │ └── start.sh ├── config/ │ ├── config.go │ ├── config_loader.go │ └── l3afd.cfg ├── config.yml ├── docs/ │ ├── CONTRIBUTING.md │ ├── api/ │ │ └── README.md │ ├── configdoc.md │ ├── docs.go │ ├── graceful-restart-guide.md │ ├── prod-deploy-guide.md │ ├── swagger.json │ ├── swagger.md │ └── swagger.yaml ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── mocks/ │ └── mocked_interfaces.go ├── models/ │ └── l3afd.go ├── pidfile/ │ └── pidfile.go ├── register_internal.go ├── restart/ │ ├── restart.go │ └── restart_test.go ├── routes/ │ ├── route.go │ └── router.go ├── signals/ │ ├── signal_unix.go │ └── signal_windows.go ├── stats/ │ └── metrics.go ├── testdata/ │ ├── Test_l3af-config.json │ └── l3afd.cdb ├── utils/ │ └── utils.go └── version.go
SYMBOL INDEX (351 symbols across 49 files)
FILE: apis/configwatch.go
type Server (line 39) | type Server struct
method GracefulStop (line 165) | func (s *Server) GracefulStop(shutdownTimeout time.Duration) error {
method getClientValidator (line 216) | func (s *Server) getClientValidator(helloInfo *tls.ClientHelloInfo) fu...
function StartConfigWatcher (line 52) | func StartConfigWatcher(ctx context.Context, hostname, daemonName string...
function isLoopback (line 181) | func isLoopback(addr string) bool {
function MonitorTLS (line 196) | func MonitorTLS(start time.Time, expiry time.Time, conf *config.Config) {
function toLowerCaseASCII (line 263) | func toLowerCaseASCII(in string) string {
function validHostname (line 295) | func validHostname(host string, isPattern bool) bool {
function matchExactly (line 340) | func matchExactly(hostA, hostB string) bool {
function matchHostnamesWithRegexp (line 349) | func matchHostnamesWithRegexp(dnsName, sanMatchRule string) bool {
FILE: apis/configwatch_test.go
function TestMatchHostnamesWithRegexp (line 11) | func TestMatchHostnamesWithRegexp(t *testing.T) {
function TestMatchExactly (line 111) | func TestMatchExactly(t *testing.T) {
function TestToLowerCaseASCII (line 145) | func TestToLowerCaseASCII(t *testing.T) {
FILE: apis/handlers/addprog.go
function AddEbpfPrograms (line 28) | func AddEbpfPrograms(ctx context.Context, bpfcfg *bpfprogs.NFConfigs) ht...
function IncWriteReq (line 79) | func IncWriteReq() {
function DecWriteReq (line 84) | func DecWriteReq() {
FILE: apis/handlers/addprog_test.go
constant dummypayload (line 16) | dummypayload string = `[
function Test_addprog (line 32) | func Test_addprog(t *testing.T) {
FILE: apis/handlers/deleteprog.go
function DeleteEbpfPrograms (line 28) | func DeleteEbpfPrograms(ctx context.Context, bpfcfg *bpfprogs.NFConfigs)...
FILE: apis/handlers/deleteprog_test.go
constant payloadfordelete (line 16) | payloadfordelete string = `[
function Test_DeleteEbpfPrograms (line 30) | func Test_DeleteEbpfPrograms(t *testing.T) {
FILE: apis/handlers/getconfig.go
function InitConfigs (line 17) | func InitConfigs(cfgs *bpfprogs.NFConfigs) error {
function GetConfig (line 30) | func GetConfig(w http.ResponseWriter, r *http.Request) {
function GetConfigAll (line 69) | func GetConfigAll(w http.ResponseWriter, r *http.Request) {
FILE: apis/handlers/getconfig_test.go
function Test_GetConfig (line 14) | func Test_GetConfig(t *testing.T) {
function Test_GetConfigAll (line 53) | func Test_GetConfigAll(t *testing.T) {
FILE: apis/handlers/restart_linux.go
function HandleRestart (line 38) | func HandleRestart(bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc {
FILE: apis/handlers/restart_linux_test.go
function Test_HandleRestart (line 13) | func Test_HandleRestart(t *testing.T) {
FILE: apis/handlers/restart_windows.go
function HandleRestart (line 22) | func HandleRestart(bpfcfg *bpfprogs.NFConfigs) http.HandlerFunc {
FILE: apis/handlers/restart_windows_test.go
function Test_HandleRestart (line 11) | func Test_HandleRestart(t *testing.T) {
FILE: apis/handlers/updateconfig.go
function UpdateConfig (line 28) | func UpdateConfig(ctx context.Context, bpfcfg *bpfprogs.NFConfigs) http....
FILE: apis/handlers/updateconfig_test.go
function Test_UpdateConfig (line 16) | func Test_UpdateConfig(t *testing.T) {
FILE: apis/routes.go
function apiRoutes (line 14) | func apiRoutes(ctx context.Context, bpfcfg *bpfprogs.NFConfigs) []routes...
FILE: bpfprogs/bpf.go
constant executePerm (line 48) | executePerm uint32 = 0111
constant bpfStatus (line 49) | bpfStatus string = "RUNNING"
type BPF (line 52) | type BPF struct
method Stop (line 255) | func (b *BPF) Stop(ifaceName, ipv4_address, direction string, chain bo...
method Start (line 366) | func (b *BPF) Start(ifaceName, ipv4_address, direction string, chain b...
method UpdateBPFMaps (line 471) | func (b *BPF) UpdateBPFMaps(ifaceName, ipv4_address, direction string)...
method UpdateArgs (line 493) | func (b *BPF) UpdateArgs(ifaceName, ipv4_address, direction string) er...
method isRunning (line 545) | func (b *BPF) isRunning() (bool, bool, error) {
method VerifyAndGetArtifacts (line 601) | func (b *BPF) VerifyAndGetArtifacts(conf *config.Config) error {
method GetArtifacts (line 613) | func (b *BPF) GetArtifacts(conf *config.Config) error {
method createUpdateRulesFile (line 657) | func (b *BPF) createUpdateRulesFile(direction string) (string, error) {
method AddBPFMap (line 683) | func (b *BPF) AddBPFMap(mapName string) error {
method GetBPFMap (line 694) | func (b *BPF) GetBPFMap(mapName string) (*BPFMap, error) {
method AddMetricsBPFMap (line 726) | func (b *BPF) AddMetricsBPFMap(mapName, aggregator string, key, sample...
method MonitorMaps (line 746) | func (b *BPF) MonitorMaps(ifaceName, ipv4_address string, intervals in...
method PutNextProgFDFromID (line 764) | func (b *BPF) PutNextProgFDFromID(progID int) error {
method GetProgID (line 791) | func (b *BPF) GetProgID() (ebpf.ProgramID, error) {
method RemoveNextProgFD (line 820) | func (b *BPF) RemoveNextProgFD() error {
method RemovePrevProgFD (line 840) | func (b *BPF) RemovePrevProgFD() error {
method VerifyPinnedProgMap (line 856) | func (b *BPF) VerifyPinnedProgMap(chain, exists bool) error {
method VerifyProcessObject (line 896) | func (b *BPF) VerifyProcessObject() error {
method VerifyMetricsMapsVanish (line 917) | func (b *BPF) VerifyMetricsMapsVanish() error {
method UnloadProgram (line 942) | func (b *BPF) UnloadProgram(ifaceName, direction string) error {
method RemovePinnedFiles (line 983) | func (b *BPF) RemovePinnedFiles(ifaceName string) error {
method RemoveRootProgMapFile (line 1019) | func (b *BPF) RemoveRootProgMapFile(ifacename string) error {
method VerifyCleanupMaps (line 1047) | func (b *BPF) VerifyCleanupMaps(chain bool) error {
method LoadBPFProgram (line 1064) | func (b *BPF) LoadBPFProgram(ifaceName string) error {
method InitialiseMetricMaps (line 1141) | func (b *BPF) InitialiseMetricMaps() error {
method IsLoaded (line 1237) | func (b *BPF) IsLoaded() bool {
method StartUserProgram (line 1250) | func (b *BPF) StartUserProgram(ifaceName, direction string, chain bool...
method CreatePinDirectories (line 1325) | func (b *BPF) CreatePinDirectories(ifaceName, progName, progVersion st...
method AttachBPFProgram (line 1362) | func (b *BPF) AttachBPFProgram(ifaceName, direction string) error {
method PinBpfMaps (line 1394) | func (b *BPF) PinBpfMaps(ifaceName string) error {
method UpdateProgramMap (line 1418) | func (b *BPF) UpdateProgramMap(ifaceName string) error {
method LoadBPFProgramChain (line 1441) | func (b *BPF) LoadBPFProgramChain(ifaceName, direction string) error {
method StopUserProgram (line 1481) | func (b *BPF) StopUserProgram(ifaceName, direction string) error {
function NewBpfProgram (line 72) | func NewBpfProgram(ctx context.Context, program models.BPFProgram, conf ...
function LoadRootProgram (line 105) | func LoadRootProgram(ifaceName string, direction string, progType string...
function StopExternalRunningProcess (line 216) | func StopExternalRunningProcess(processName string) error {
function fileExists (line 674) | func fileExists(filename string) bool {
function DownloadArtifact (line 1528) | func DownloadArtifact(urlpath string, timeout time.Duration, buf *bytes....
function ExtractArtifact (line 1574) | func ExtractArtifact(artifactName string, buf *bytes.Buffer, tempDir str...
function ValidatePath (line 1671) | func ValidatePath(filePath string, destination string) (string, error) {
FILE: bpfprogs/bpfCfgs_internal.go
method RunBPFConfigs (line 16) | func (b *BPF) RunBPFConfigs() error {
FILE: bpfprogs/bpf_test.go
function fakeExecCommand (line 34) | func fakeExecCommand(command string, args ...string) *exec.Cmd {
function TestHelperProcess (line 43) | func TestHelperProcess(t *testing.T) {
function TestNewBpfProgram (line 50) | func TestNewBpfProgram(t *testing.T) {
function TestBPF_Stop (line 140) | func TestBPF_Stop(t *testing.T) {
function TestBPF_Start (line 254) | func TestBPF_Start(t *testing.T) {
function TestBPF_isRunning (line 335) | func TestBPF_isRunning(t *testing.T) {
type RoundTripFunc (line 378) | type RoundTripFunc
method RoundTrip (line 381) | func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, e...
function NewTestClient (line 386) | func NewTestClient(fn RoundTripFunc) *http.Client {
function TestBPF_GetArtifacts (line 391) | func TestBPF_GetArtifacts(t *testing.T) {
function TestBPF_SetPrLimits (line 562) | func TestBPF_SetPrLimits(t *testing.T) {
function Test_assertExecute (line 613) | func Test_assertExecute(t *testing.T) {
function Test_fileExists (line 653) | func Test_fileExists(t *testing.T) {
function Test_StopExternalRunningProcess (line 672) | func Test_StopExternalRunningProcess(t *testing.T) {
function Test_createUpdateRulesFile (line 692) | func Test_createUpdateRulesFile(t *testing.T) {
function Test_PutNextProgFDFromID (line 746) | func Test_PutNextProgFDFromID(t *testing.T) {
function Test_VerifyPinnedProgMapExists (line 827) | func Test_VerifyPinnedProgMapExists(t *testing.T) {
function Test_VerifyProcessObject (line 869) | func Test_VerifyProcessObject(t *testing.T) {
function Test_VerifyPinnedProgMapVanish (line 920) | func Test_VerifyPinnedProgMapVanish(t *testing.T) {
FILE: bpfprogs/bpf_test_unix.go
function GetTestNonexecutablePathName (line 14) | func GetTestNonexecutablePathName() string {
function GetTestExecutablePathName (line 18) | func GetTestExecutablePathName() string {
function GetTestExecutablePath (line 22) | func GetTestExecutablePath() string {
function GetTestExecutableName (line 26) | func GetTestExecutableName() string {
function assertExecutable (line 31) | func assertExecutable(fPath string) error {
FILE: bpfprogs/bpf_test_windows.go
function GetTestNonexecutablePathName (line 15) | func GetTestNonexecutablePathName() string {
function GetTestExecutablePathName (line 19) | func GetTestExecutablePathName() string {
function GetTestExecutablePath (line 23) | func GetTestExecutablePath() string {
function GetTestExecutableName (line 27) | func GetTestExecutableName() string {
function assertExecutable (line 32) | func assertExecutable(fPath string) error {
FILE: bpfprogs/bpf_unix.go
function DisableLRO (line 36) | func DisableLRO(ifaceName string) error {
function prLimit (line 57) | func prLimit(pid int, limit uintptr, rlimit *unix.Rlimit) error {
method SetPrLimits (line 73) | func (b *BPF) SetPrLimits() error {
method ProcessTerminate (line 101) | func (b *BPF) ProcessTerminate() error {
function VerifyNMountBPFFS (line 109) | func VerifyNMountBPFFS() error {
function VerifyNMountTraceFS (line 131) | func VerifyNMountTraceFS() error {
function GetPlatform (line 161) | func GetPlatform() (string, error) {
function IsProcessRunning (line 174) | func IsProcessRunning(pid int, name string) (bool, error) {
function VerifyNCreateTCDirs (line 192) | func VerifyNCreateTCDirs() error {
method LoadTCAttachProgram (line 207) | func (b *BPF) LoadTCAttachProgram(ifaceName, direction string) error {
method UnloadTCProgram (line 368) | func (b *BPF) UnloadTCProgram(ifaceName, direction string) error {
method LoadTCXAttachProgram (line 525) | func (b *BPF) LoadTCXAttachProgram(ifaceName, direction string) error {
method LoadXDPAttachProgram (line 571) | func (b *BPF) LoadXDPAttachProgram(ifaceName string) error {
FILE: bpfprogs/bpf_windows.go
function DisableLRO (line 19) | func DisableLRO(ifaceName string) error {
method SetPrLimits (line 24) | func (b *BPF) SetPrLimits() error {
function VerifyNMountBPFFS (line 32) | func VerifyNMountBPFFS() error {
function GetPlatform (line 36) | func GetPlatform() (string, error) {
function IsProcessRunning (line 40) | func IsProcessRunning(pid int, name string) (bool, error) {
method ProcessTerminate (line 49) | func (b *BPF) ProcessTerminate() error {
function VerifyNCreateTCDirs (line 57) | func VerifyNCreateTCDirs() error {
method LoadTCAttachProgram (line 62) | func (b *BPF) LoadTCAttachProgram(ifaceName, direction string) error {
method LoadTCXAttachProgram (line 68) | func (b *BPF) LoadTCXAttachProgram(ifaceName, direction string) error {
method UnloadTCProgram (line 74) | func (b *BPF) UnloadTCProgram(ifaceName, direction string) error {
method LoadXDPAttachProgram (line 80) | func (b *BPF) LoadXDPAttachProgram(ifaceName string) error {
method LoadBPFProgramProbeTypes (line 86) | func (b *BPF) LoadBPFProgramProbeTypes(objSpec *ebpf.CollectionSpec) err...
FILE: bpfprogs/bpfdebug.go
function SetupBPFDebug (line 19) | func SetupBPFDebug(ebpfChainDebugAddr string, BPFConfigs *NFConfigs) {
function ViewHandler (line 45) | func ViewHandler(w http.ResponseWriter, r *http.Request) {
FILE: bpfprogs/bpfmap.go
type BPFMap (line 18) | type BPFMap struct
method RemoveMissingKeys (line 37) | func (b *BPFMap) RemoveMissingKeys(args []models.KeyValue) error {
method Update (line 70) | func (b *BPFMap) Update(key, value int) error {
type MetricsBPFMap (line 28) | type MetricsBPFMap struct
method GetValue (line 90) | func (b *MetricsBPFMap) GetValue() float64 {
method MaxValue (line 137) | func (b *MetricsBPFMap) MaxValue() float64 {
method AvgValue (line 153) | func (b *MetricsBPFMap) AvgValue() float64 {
FILE: bpfprogs/bpfmap_test.go
function SetupTestValues (line 14) | func SetupTestValues() {
function TestMetricsBPFMapMaxValue (line 22) | func TestMetricsBPFMapMaxValue(t *testing.T) {
function TestMetricsBPFMapAvgValue (line 58) | func TestMetricsBPFMapAvgValue(t *testing.T) {
FILE: bpfprogs/bpfmetrics.go
type BpfMetrics (line 16) | type BpfMetrics struct
method BpfMetricsStart (line 29) | func (c *BpfMetrics) BpfMetricsStart(xdpProgs, ingressTCProgs, egressT...
method BpfMetricsWorker (line 36) | func (c *BpfMetrics) BpfMetricsWorker(bpfProgs map[string]*list.List, ...
method BpfMetricsProbeWorker (line 58) | func (c *BpfMetrics) BpfMetricsProbeWorker(bpfProgs *list.List) {
function NewpBpfMetrics (line 21) | func NewpBpfMetrics(chain bool, interval int) *BpfMetrics {
FILE: bpfprogs/bpfmetrics_test.go
function TestNewpKFMetrics (line 12) | func TestNewpKFMetrics(t *testing.T) {
function Test_BPFMetrics_Start (line 46) | func Test_BPFMetrics_Start(t *testing.T) {
FILE: bpfprogs/nfconfig.go
type NFConfigs (line 33) | type NFConfigs struct
method Close (line 83) | func (c *NFConfigs) Close(ctx context.Context) error {
method VerifyAndStartXDPRootProgram (line 152) | func (c *NFConfigs) VerifyAndStartXDPRootProgram(ifaceName, direction ...
method VerifyAndStartTCRootProgram (line 179) | func (c *NFConfigs) VerifyAndStartTCRootProgram(ifaceName, direction s...
method PushBackAndStartBPF (line 216) | func (c *NFConfigs) PushBackAndStartBPF(bpfProg *models.BPFProgram, if...
method DownloadAndStartBPFProgram (line 240) | func (c *NFConfigs) DownloadAndStartBPFProgram(element *list.Element, ...
method StopNRemoveAllBPFPrograms (line 266) | func (c *NFConfigs) StopNRemoveAllBPFPrograms(ifaceName, direction str...
method StopNRemoveAllBPFProbePrograms (line 303) | func (c *NFConfigs) StopNRemoveAllBPFProbePrograms() error {
method VerifyNUpdateBPFProgram (line 325) | func (c *NFConfigs) VerifyNUpdateBPFProgram(bpfProg *models.BPFProgram...
method MoveToLocation (line 466) | func (c *NFConfigs) MoveToLocation(element *list.Element, bpfList *lis...
method InsertAndStartBPFProgram (line 540) | func (c *NFConfigs) InsertAndStartBPFProgram(bpfProg *models.BPFProgra...
method StopRootProgram (line 596) | func (c *NFConfigs) StopRootProgram(ifaceName, direction string) error {
method LinkBPFPrograms (line 638) | func (c *NFConfigs) LinkBPFPrograms(leftBPF, rightBPF *BPF) error {
method BPFDetails (line 650) | func (c *NFConfigs) BPFDetails(iface string) []*BPF {
method Deploy (line 678) | func (c *NFConfigs) Deploy(ifaceName, HostName string, bpfProgs *model...
method DeployeBPFPrograms (line 776) | func (c *NFConfigs) DeployeBPFPrograms(bpfProgs []models.L3afBPFProgra...
method SaveConfigsToConfigStore (line 800) | func (c *NFConfigs) SaveConfigsToConfigStore() error {
method EBPFPrograms (line 832) | func (c *NFConfigs) EBPFPrograms(iface string) models.L3afBPFPrograms {
method EBPFProgramsAll (line 880) | func (c *NFConfigs) EBPFProgramsAll() []models.L3afBPFPrograms {
method RemoveMissingNetIfacesNBPFProgsInConfig (line 892) | func (c *NFConfigs) RemoveMissingNetIfacesNBPFProgsInConfig(bpfProgCfg...
method RemoveMissingBPFProgramsInConfig (line 952) | func (c *NFConfigs) RemoveMissingBPFProgramsInConfig(bpfProg models.L3...
method AddAndStartBPF (line 1032) | func (c *NFConfigs) AddAndStartBPF(bpfProg *models.BPFProgram, ifaceNa...
method AddProgramWithoutChaining (line 1093) | func (c *NFConfigs) AddProgramWithoutChaining(ifaceName string, bpfPro...
method AddProgramsOnInterface (line 1151) | func (c *NFConfigs) AddProgramsOnInterface(ifaceName, HostName string,...
method AddeBPFPrograms (line 1241) | func (c *NFConfigs) AddeBPFPrograms(bpfProgs []models.L3afBPFPrograms)...
method CleanupProgramsOnInterface (line 1270) | func (c *NFConfigs) CleanupProgramsOnInterface(ifaceName string) {
method DeleteProgramsOnInterface (line 1289) | func (c *NFConfigs) DeleteProgramsOnInterface(ifaceName, HostName stri...
method DeleteProgramsOnInterfaceHelper (line 1390) | func (c *NFConfigs) DeleteProgramsOnInterfaceHelper(e *list.Element, i...
method DeleteEbpfPrograms (line 1427) | func (c *NFConfigs) DeleteEbpfPrograms(bpfProgs []models.L3afBPFProgra...
method AddProbePrograms (line 1461) | func (c *NFConfigs) AddProbePrograms(HostName string, bpfProgs []*mode...
method PushBackAndStartProbe (line 1482) | func (c *NFConfigs) PushBackAndStartProbe(bpfProg *models.BPFProgram) ...
method DownloadAndStartProbes (line 1493) | func (c *NFConfigs) DownloadAndStartProbes(element *list.Element) error {
method GetL3AFHOSTDATA (line 1571) | func (c *NFConfigs) GetL3AFHOSTDATA() models.L3AFALLHOSTDATA {
method StartAllUserProgramsAndProbes (line 1641) | func (c *NFConfigs) StartAllUserProgramsAndProbes() error {
method StopAllProbesAndUserPrograms (line 1779) | func (c *NFConfigs) StopAllProbesAndUserPrograms() error {
function NewNFConfigs (line 56) | func NewNFConfigs(ctx context.Context, host string, hostConf *config.Con...
function getHostInterfaces (line 1016) | func getHostInterfaces() (map[string]bool, error) {
function BinarySearch (line 1444) | func BinarySearch(names []string, target string) bool {
function SerialzeProgram (line 1512) | func SerialzeProgram(e *list.Element) *models.L3AFMetaData {
FILE: bpfprogs/nfconfig_test.go
function setupDBTest (line 37) | func setupDBTest() {
function setupValidBPF (line 49) | func setupValidBPF() {
function setupBPFProgramData (line 70) | func setupBPFProgramData() {
function setupBPFProgramVersionChange (line 91) | func setupBPFProgramVersionChange() {
function setupBPFProgramStatusChange (line 110) | func setupBPFProgramStatusChange() {
function TestNewNFConfigs (line 131) | func TestNewNFConfigs(t *testing.T) {
function TestNFConfigs_Deploy (line 181) | func TestNFConfigs_Deploy(t *testing.T) {
function TestNFConfigs_Close (line 371) | func TestNFConfigs_Close(t *testing.T) {
function Test_getHostInterfaces (line 420) | func Test_getHostInterfaces(t *testing.T) {
function Test_BinarySearch (line 439) | func Test_BinarySearch(t *testing.T) {
function Test_AddProgramsOnInterface (line 467) | func Test_AddProgramsOnInterface(t *testing.T) {
function TestAddeBPFPrograms (line 587) | func TestAddeBPFPrograms(t *testing.T) {
function TestDeleteProgramsOnInterface (line 741) | func TestDeleteProgramsOnInterface(t *testing.T) {
function TestDeleteEbpfPrograms (line 837) | func TestDeleteEbpfPrograms(t *testing.T) {
function TestAddAndStartBPF (line 969) | func TestAddAndStartBPF(t *testing.T) {
function TestAddProgramWithoutChaining (line 1023) | func TestAddProgramWithoutChaining(t *testing.T) {
FILE: bpfprogs/probes.go
method LoadBPFProgramProbeType (line 21) | func (b *BPF) LoadBPFProgramProbeType(prog *ebpf.Program, sectionName st...
method LoadBPFProgramProbeTypes (line 48) | func (b *BPF) LoadBPFProgramProbeTypes(objSpec *ebpf.CollectionSpec) err...
function GetProgramSectionDetails (line 74) | func GetProgramSectionDetails(sectionName string) (string, string, strin...
method AttachProbePerfEvent (line 93) | func (b *BPF) AttachProbePerfEvent(hookName, progType string, prog *ebpf...
method AttachUProbePerfEvent (line 123) | func (b *BPF) AttachUProbePerfEvent(hookName string, prog *ebpf.Program)...
method AttachURetProbePerfEvent (line 140) | func (b *BPF) AttachURetProbePerfEvent(hookName string, prog *ebpf.Progr...
function getSymbolName (line 156) | func getSymbolName(funcNames []string) string {
FILE: bpfprogs/probes_test.go
function TestGetProgramSectionDetails (line 13) | func TestGetProgramSectionDetails(t *testing.T) {
FILE: bpfprogs/processCheck.go
type PCheck (line 16) | type PCheck struct
method PCheckStart (line 31) | func (c *PCheck) PCheckStart(xdpProgs, ingressTCProgs, egressTCProgs m...
method pMonitorWorker (line 38) | func (c *PCheck) pMonitorWorker(bpfProgs map[string]*list.List, direct...
method pMonitorProbeWorker (line 93) | func (c *PCheck) pMonitorProbeWorker(bpfProgs *list.List) {
function NewPCheck (line 22) | func NewPCheck(rc int, chain bool, interval time.Duration) *PCheck {
FILE: bpfprogs/processCheck_test.go
function TestNewpCheck (line 13) | func TestNewpCheck(t *testing.T) {
function Test_pCheck_pCheckStart (line 48) | func Test_pCheck_pCheckStart(t *testing.T) {
FILE: config/config.go
constant ENV_PROD (line 18) | ENV_PROD = "PROD"
type Config (line 21) | type Config struct
function ReadConfig (line 101) | func ReadConfig(configPath string) (*Config, error) {
function loadTLSVersion (line 172) | func loadTLSVersion(cfgRdr *config.Config, fieldName string) (uint16, er...
function loadXDPRootPackageName (line 186) | func loadXDPRootPackageName(cfgRdr *config.Config) string {
function loadXDPRootArtifact (line 194) | func loadXDPRootArtifact(cfgRdr *config.Config) string {
function loadXDPRootIngressMapName (line 202) | func loadXDPRootIngressMapName(cfgRdr *config.Config) string {
function loadXDPRootCommand (line 210) | func loadXDPRootCommand(cfgRdr *config.Config) string {
function loadXDPRootVersion (line 218) | func loadXDPRootVersion(cfgRdr *config.Config) string {
function loadTCRootPackageName (line 226) | func loadTCRootPackageName(cfgRdr *config.Config) string {
function loadTCRootArtifact (line 234) | func loadTCRootArtifact(cfgRdr *config.Config) string {
function loadTCRootIngressMapName (line 242) | func loadTCRootIngressMapName(cfgRdr *config.Config) string {
function loadTCRootEgressMapName (line 250) | func loadTCRootEgressMapName(cfgRdr *config.Config) string {
function loadTCRootCommand (line 258) | func loadTCRootCommand(cfgRdr *config.Config) string {
function loadTCRootVersion (line 266) | func loadTCRootVersion(cfgRdr *config.Config) string {
FILE: config/config_loader.go
constant cfgFatalMsg (line 17) | cfgFatalMsg = "Could not read %s value %q from group %q in config file"
constant cfgOptionalMsg (line 18) | cfgOptionalMsg = "Using default value %v after failure to read group:%s;...
function LoadConfigString (line 32) | func LoadConfigString(confReader *config.Config, group, field string) st...
function LoadOptionalConfigString (line 40) | func LoadOptionalConfigString(confReader *config.Config, group, field, d...
function LoadOptionalConfigStringEncKey (line 45) | func LoadOptionalConfigStringEncKey(confReader *config.Config, group, fi...
function LoadConfigStringEncKey (line 55) | func LoadConfigStringEncKey(confReader *config.Config, group, field stri...
function loadConfigStringEncKey (line 67) | func loadConfigStringEncKey(confReader *config.Config, group, field stri...
function LoadConfigBool (line 76) | func LoadConfigBool(confReader *config.Config, group, field string) bool {
function LoadOptionalConfigBool (line 84) | func LoadOptionalConfigBool(confReader *config.Config, group, field stri...
function LoadConfigInt (line 93) | func LoadConfigInt(confReader *config.Config, group, field string) int {
function LoadOptionalConfigInt (line 101) | func LoadOptionalConfigInt(confReader *config.Config, group, field strin...
function LoadConfigFloat (line 110) | func LoadConfigFloat(confReader *config.Config, group, field string) flo...
function LoadOptionalConfigFloat (line 118) | func LoadOptionalConfigFloat(confReader *config.Config, group, field str...
function LoadConfigDuration (line 127) | func LoadConfigDuration(confReader *config.Config, group, field string) ...
function LoadOptionalConfigDuration (line 135) | func LoadOptionalConfigDuration(confReader *config.Config, group, field ...
function LoadConfigURL (line 149) | func LoadConfigURL(confReader *config.Config, group, field string) *url....
function LoadOptionalConfigURL (line 157) | func LoadOptionalConfigURL(confReader *config.Config, group, field strin...
function LoadConfigStringCSV (line 175) | func LoadConfigStringCSV(confReader *config.Config, group, field string)...
function LoadOptionalConfigStringCSV (line 187) | func LoadOptionalConfigStringCSV(confReader *config.Config, group, field...
FILE: docs/docs.go
constant docTemplate (line 6) | docTemplate = `{
function init (line 517) | func init() {
FILE: main.go
constant daemonName (line 38) | daemonName = "l3afd"
function setupLogging (line 42) | func setupLogging(conf *config.Config) {
function saveLogsToFile (line 69) | func saveLogsToFile(conf *config.Config) {
function main (line 88) | func main() {
function SetupNFConfigs (line 164) | func SetupNFConfigs(ctx context.Context, conf *config.Config) (*bpfprogs...
function checkKernelVersion (line 186) | func checkKernelVersion(conf *config.Config) error {
function ReadConfigsFromConfigStore (line 218) | func ReadConfigsFromConfigStore(conf *config.Config) ([]models.L3afBPFPr...
function setupForRestartOuter (line 248) | func setupForRestartOuter(ctx context.Context, conf *config.Config) error {
function setupForRestart (line 267) | func setupForRestart(ctx context.Context, conf *config.Config) error {
function sendState (line 340) | func sendState(s string) {
function populateVersions (line 368) | func populateVersions(conf *config.Config) {
FILE: main_test.go
function TestTestConfigValid (line 13) | func TestTestConfigValid(t *testing.T) {
FILE: mocks/mocked_interfaces.go
type MockplatformInterface (line 14) | type MockplatformInterface struct
method EXPECT (line 32) | func (m *MockplatformInterface) EXPECT() *MockplatformInterfaceMockRec...
method GetPlatform (line 37) | func (m *MockplatformInterface) GetPlatform() (string, error) {
type MockplatformInterfaceMockRecorder (line 20) | type MockplatformInterfaceMockRecorder struct
method GetPlatform (line 46) | func (mr *MockplatformInterfaceMockRecorder) GetPlatform() *gomock.Call {
function NewMockplatformInterface (line 25) | func NewMockplatformInterface(ctrl *gomock.Controller) *MockplatformInte...
FILE: models/l3afd.go
constant Enabled (line 12) | Enabled = "enabled"
constant Disabled (line 13) | Disabled = "disabled"
constant StartType (line 15) | StartType = "start"
constant StopType (line 16) | StopType = "stop"
constant UpdateType (line 17) | UpdateType = "update"
constant XDPType (line 19) | XDPType = "xdp"
constant TCType (line 20) | TCType = "tc"
constant IngressType (line 22) | IngressType = "ingress"
constant EgressType (line 23) | EgressType = "egress"
constant XDPIngressType (line 24) | XDPIngressType = "xdpingress"
constant TCMapPinPath (line 25) | TCMapPinPath = "tc/globals"
constant KProbe (line 27) | KProbe = "kprobe"
constant TracePoint (line 28) | TracePoint = "tracepoint"
constant KRetProbe (line 29) | KRetProbe = "kretprobe"
constant UProbe (line 30) | UProbe = "uprobe"
constant URetProbe (line 31) | URetProbe = "uretprobe"
type L3afDNFArgs (line 34) | type L3afDNFArgs
type BPFProgram (line 37) | type BPFProgram struct
type L3afDNFMetricsMap (line 72) | type L3afDNFMetricsMap struct
type KeyValue (line 79) | type KeyValue struct
type L3afDMapArg (line 85) | type L3afDMapArg struct
type L3afBPFPrograms (line 91) | type L3afBPFPrograms struct
type BPFPrograms (line 99) | type BPFPrograms struct
type L3afBPFProgramNames (line 107) | type L3afBPFProgramNames struct
type BPFProgramNames (line 115) | type BPFProgramNames struct
type MetaColl (line 122) | type MetaColl struct
type MetaMetricsBPFMap (line 127) | type MetaMetricsBPFMap struct
type Label (line 135) | type Label struct
type MetricVec (line 140) | type MetricVec struct
type L3AFMetaData (line 147) | type L3AFMetaData struct
type L3AFALLHOSTDATA (line 162) | type L3AFALLHOSTDATA struct
type RestartConfig (line 173) | type RestartConfig struct
constant HttpScheme (line 185) | HttpScheme string = "http"
constant HttpsScheme (line 186) | HttpsScheme string = "https"
constant FileScheme (line 187) | FileScheme string = "file"
constant StatusFailed (line 188) | StatusFailed string = "Failed"
constant StatusReady (line 189) | StatusReady string = "Ready"
constant HostSock (line 192) | HostSock string = "/tmp/l3afd.sock"
constant StateSock (line 193) | StateSock string = "/tmp/l3afstate.sock"
constant L3AFDRestartArtifactName (line 194) | L3AFDRestartArtifactName string = "l3afd.tar.gz"
FILE: pidfile/pidfile.go
function CheckPIDConflict (line 17) | func CheckPIDConflict(pidFilename string) error {
function CreatePID (line 79) | func CreatePID(pidFilename string) error {
function RemovePID (line 88) | func RemovePID(pidFilename string) error {
function SetupGracefulShutdown (line 96) | func SetupGracefulShutdown(shutdownHandler func() error, shutdownHandler...
FILE: register_internal.go
function registerL3afD (line 18) | func registerL3afD(conf *config.Config) error {
FILE: restart/restart.go
function convertBPFMap (line 33) | func convertBPFMap(in []string, g *bpfprogs.BPF, output *map[string]bpfp...
function getCollection (line 63) | func getCollection(input models.MetaColl, output **ebpf.Collection, b *b...
function getMetricsMaps (line 90) | func getMetricsMaps(input map[string]models.MetaMetricsBPFMap, b *bpfpro...
function deserializeProgram (line 133) | func deserializeProgram(ctx context.Context, r *models.L3AFMetaData, hos...
function getValueofLabel (line 180) | func getValueofLabel(l string, t []models.Label) string {
function getCountVecByMetricName (line 190) | func getCountVecByMetricName(name string) *prometheus.CounterVec {
function getGaugeVecByMetricName (line 206) | func getGaugeVecByMetricName(name string) *prometheus.GaugeVec {
function Convert (line 220) | func Convert(ctx context.Context, t models.L3AFALLHOSTDATA, hostconfig *...
function SetMetrics (line 278) | func SetMetrics(t models.L3AFALLHOSTDATA) {
function GetNetListener (line 299) | func GetNetListener(fd int, fname string) (*net.TCPListener, error) {
function AddSymlink (line 314) | func AddSymlink(sPath, symlink string) error {
function RemoveSymlink (line 320) | func RemoveSymlink(symlink string) error {
function ReadSymlink (line 326) | func ReadSymlink(symlink string) (string, error) {
function GetNewVersion (line 335) | func GetNewVersion(artifactName, oldVersion, newVersion string, conf *co...
function RollBackSymlink (line 389) | func RollBackSymlink(oldCfgPath, oldBinPath string, oldVersion, newVersi...
FILE: restart/restart_test.go
function TestGetValueofLabel (line 12) | func TestGetValueofLabel(t *testing.T) {
FILE: routes/route.go
type Route (line 9) | type Route struct
FILE: routes/router.go
function NewRouter (line 12) | func NewRouter(routes []Route) *chi.Mux {
FILE: stats/metrics.go
function SetupMetrics (line 29) | func SetupMetrics(hostname, daemonName, metricsAddr string) {
function Add (line 157) | func Add(value float64, counterVec *prometheus.CounterVec, ebpfProgram, ...
function Set (line 179) | func Set(value float64, gaugeVec *prometheus.GaugeVec, ebpfProgram, dire...
function SetValue (line 202) | func SetValue(value float64, gaugeVec *prometheus.GaugeVec, ebpfProgram,...
function SetWithVersion (line 224) | func SetWithVersion(value float64, gaugeVec *prometheus.GaugeVec, ebpfPr...
FILE: utils/utils.go
function GetKernelVersion (line 16) | func GetKernelVersion() (string, error) {
function CheckTCXSupport (line 31) | func CheckTCXSupport() bool {
function ReplaceDotsWithUnderscores (line 66) | func ReplaceDotsWithUnderscores(version string) string {
function LinkPinPath (line 71) | func LinkPinPath(bpfMapDefaultPath, ifaceName, programName, version, pro...
function TCLinkPinPath (line 76) | func TCLinkPinPath(bpfMapDefaultPath, ifaceName, programName, version, p...
function ProgPinPath (line 81) | func ProgPinPath(bpfMapDefaultPath, ifaceName, programName, version, ent...
FILE: version.go
function initVersion (line 25) | func initVersion() {
function VersionInfo (line 33) | func VersionInfo() string {
function ShortVersion (line 55) | func ShortVersion() string {
Condensed preview — 80 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (530K chars).
[
{
"path": ".github/dependabot.yml",
"chars": 602,
"preview": "# Copyright Contributors to the L3AF Project.\n# SPDX-License-Identifier: Apache-2.0\n#\n# For documentation on the format "
},
{
"path": ".github/release.yml",
"chars": 410,
"preview": "# .github/release.yml\n---\nchangelog:\n exclude:\n labels:\n - ignore-for-release\n categories:\n - title: Breaki"
},
{
"path": ".github/workflows/ci-build-windows.yaml",
"chars": 2048,
"preview": "# Copyright Contributors to the L3AF Project.\n# SPDX-License-Identifier: Apache-2.0\n#\n# For documentation on the github "
},
{
"path": ".github/workflows/ci-build.yaml",
"chars": 3689,
"preview": "# Copyright Contributors to the L3AF Project.\n# SPDX-License-Identifier: Apache-2.0\n#\n# For documentation on the github "
},
{
"path": ".github/workflows/ci-e2e.yaml",
"chars": 1541,
"preview": "# Copyright Contributors to the L3AF Project.\n# SPDX-License-Identifier: Apache-2.0\n#\n# For documentation on the github "
},
{
"path": ".github/workflows/codeql.yaml",
"chars": 1361,
"preview": "# Copyright Contributors to the L3AF Project.\n# SPDX-License-Identifier: Apache-2.0\n#\n# For documentation on the github "
},
{
"path": ".github/workflows/scorecards-analysis.yml",
"chars": 2895,
"preview": "# Copyright Contributors to the L3AF Project.\n# SPDX-License-Identifier: Apache-2.0\n#\n# For documentation on the github "
},
{
"path": ".gitignore",
"chars": 15,
"preview": ".idea/\nvendor/\n"
},
{
"path": "CMakeLists.txt",
"chars": 1559,
"preview": "cmake_minimum_required(VERSION 3.10)\nproject(l3afd)\n\nadd_custom_target(swagger ALL\n DEPENDS ${CMAKE_SOU"
},
{
"path": "CODEOWNERS",
"chars": 79,
"preview": "# Default Code Owners\n\n* @sanfern @charleskbliu0 @jniesz @dalalkaran @pmoroney\n"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Makefile",
"chars": 613,
"preview": ".PHONY: all\n\nexport GOPATH := $(HOME)/go\nall: swagger build\n\nswagger:\n\t@mkdir $(GOPATH) || true \n\t@go install github.com"
},
{
"path": "README.md",
"chars": 3103,
"preview": "# L3AFD: Lightweight eBPF Daemon\n\n- [Report pote"
},
{
"path": "docs/api/README.md",
"chars": 14359,
"preview": "# L3AFD API Documentation\n\n# Update API\n\nSee [payload.json](https://github.com/l3af-project/l3af-arch/blob/main/dev_envi"
},
{
"path": "docs/configdoc.md",
"chars": 13035,
"preview": "# L3AFD Config Options Documentation\n\nSee [l3afd.cfg](https://github.com/l3af-project/l3afd/blob/main/config/l3afd.cfg) "
},
{
"path": "docs/docs.go",
"chars": 17867,
"preview": "// Package docs Code generated by swaggo/swag. DO NOT EDIT\npackage docs\n\nimport \"github.com/swaggo/swag\"\n\nconst docTempl"
},
{
"path": "docs/graceful-restart-guide.md",
"chars": 2309,
"preview": "# Guide to L3AFD Graceful Restart\n\n## Prerequisites\nTo begin, ensure that you have a specific folder where the `l3afd` b"
},
{
"path": "docs/prod-deploy-guide.md",
"chars": 2207,
"preview": "# Guide to use L3AF in production environments\n\n## Installing l3afd\n\nDownload the latest build artifacts for the last st"
},
{
"path": "docs/swagger.json",
"chars": 17184,
"preview": "{\n \"swagger\": \"2.0\",\n \"info\": {\n \"description\": \"Configuration APIs to deploy and get the details of the eB"
},
{
"path": "docs/swagger.md",
"chars": 1209,
"preview": "## Swaggo setup\n\nOur first task is to install the libraries we are dependent on. Run the following commands from the com"
},
{
"path": "docs/swagger.yaml",
"chars": 8964,
"preview": "basePath: /\ndefinitions:\n models.BPFProgram:\n properties:\n admin_status:\n description: Program admin sta"
},
{
"path": "go.mod",
"chars": 1818,
"preview": "module github.com/l3af-project/l3afd/v2\n\ngo 1.26.0\n\nrequire (\n\tgithub.com/cilium/ebpf v0.21.0\n\tgithub.com/go-chi/chi/v5 "
},
{
"path": "go.sum",
"chars": 23814,
"preview": "github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=\ngithub.com/KyleBanks/depth v1.2.1/go.m"
},
{
"path": "main.go",
"chars": 11278,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"context\""
},
{
"path": "main_test.go",
"chars": 460,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n\npackage main\n\nimport (\n\t\"os\"\n\t\"te"
},
{
"path": "mocks/mocked_interfaces.go",
"chars": 1500,
"preview": "// Code generated by MockGen. DO NOT EDIT.\n// Source: mock_interfaces.go\n\n// Package mocks is a generated GoMock package"
},
{
"path": "models/l3afd.go",
"chars": 7650,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n\npackage models\n\nimport (\n\t\"sync\"\n"
},
{
"path": "pidfile/pidfile.go",
"chars": 5292,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n\npackage pidfile\n\nimport (\n\t\"fmt\"\n"
},
{
"path": "register_internal.go",
"chars": 483,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n//\n//go:build !admind\n// +build !a"
},
{
"path": "restart/restart.go",
"chars": 13319,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package restart provides primi"
},
{
"path": "restart/restart_test.go",
"chars": 442,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n\npackage restart\n\nimport (\n\t\"testi"
},
{
"path": "routes/route.go",
"chars": 288,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n\npackage routes\n\nimport \"net/http\""
},
{
"path": "routes/router.go",
"chars": 465,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n\npackage routes\n\nimport (\n\tchi \"gi"
},
{
"path": "signals/signal_unix.go",
"chars": 293,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n//\n//go:build !WINDOWS\n// +build !"
},
{
"path": "signals/signal_windows.go",
"chars": 210,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n//\n//go:build WINDOWS\n// +build WI"
},
{
"path": "stats/metrics.go",
"chars": 8038,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n\npackage stats\n\nimport (\n\t\"errors\""
},
{
"path": "testdata/Test_l3af-config.json",
"chars": 5,
"preview": "null\n"
},
{
"path": "utils/utils.go",
"chars": 2444,
"preview": "// Copyright Contributors to the L3AF Project.\n// SPDX-License-Identifier: Apache-2.0\n\n// Package utils provides helper "
},
{
"path": "version.go",
"chars": 1466,
"preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"time\"\n)\n\nvar (\n\t// Version binary version number\n\tVersion strin"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the l3af-project/l3afd GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 80 files (472.1 KB), approximately 138.2k tokens, and a symbol index with 351 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.