├── .github ├── .codecov.yml ├── dependabot.yml └── workflows │ └── pr-tests.yaml ├── .gitignore ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── THIRD-PARTY ├── go.mod ├── go.sum ├── pkg ├── cache │ ├── cache.go │ └── cache_test.go ├── constants │ └── constants.go ├── elfparser │ ├── elf.go │ ├── elf_test.go │ ├── generate_mocks.go │ └── mocks │ │ └── elfparser_mocks.go ├── events │ ├── events.go │ ├── generate_mocks.go │ ├── mocks │ │ └── events_mocks.go │ ├── poll │ │ └── epoll.go │ └── ringbuffer.go ├── kprobe │ ├── kprobe.go │ └── kprobe_test.go ├── logger │ ├── logger.go │ └── logger_test.go ├── maps │ ├── generate_mocks.go │ ├── loader.go │ └── mocks │ │ └── ebpf_mocks.go ├── progs │ ├── generate_mocks.go │ ├── loader.go │ └── mocks │ │ └── ebpf_mocks.go ├── tc │ ├── generate_mocks.go │ ├── mocks │ │ └── tc_mocks.go │ ├── tc.go │ └── tc_test.go ├── tracepoint │ └── tracepoint.go ├── utils │ └── utils.go └── xdp │ ├── generate_mocks.go │ ├── mocks │ └── xdp_mocks.go │ ├── xdp.go │ └── xdp_test.go ├── test-data ├── invalid_map.bpf.c ├── recoverydata.bpf.c ├── ring_buffer.bpf.c ├── tc.bpf.c ├── tc.ingress.bpf.c ├── test-kprobe.bpf.c ├── test.map.bpf.c ├── test_license.bpf.c └── xdp.bpf.c └── test ├── Makefile ├── c ├── test-map.bpf.c ├── test-tracepoint.bpf.c ├── test-v6.bpf.c └── test.bpf.c ├── go.mod ├── go.sum └── main.go /.github/.codecov.yml: -------------------------------------------------------------------------------- 1 | # To validate: 2 | # cat codecov.yml | curl --data-binary @- https://codecov.io/validate 3 | 4 | codecov: 5 | # Avoid "Missing base report" 6 | # https://docs.codecov.io/docs/comparing-commits 7 | allow_coverage_offsets: true 8 | notify: 9 | require_ci_to_pass: yes 10 | 11 | coverage: 12 | precision: 2 13 | round: down 14 | range: "50...75" 15 | 16 | status: 17 | project: 18 | default: 19 | threshold: 1 20 | unittest: 21 | threshold: 1 22 | only_pulls: true 23 | flags: 24 | - "unittest" 25 | # Disable patch since it is noisy and not correct 26 | patch: 27 | default: 28 | enabled: no 29 | if_not_found: success 30 | 31 | comment: false 32 | 33 | ignore: 34 | - "test/**/*" 35 | - "test-data/**/*" 36 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # See https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#package-ecosystem 2 | version: 2 3 | updates: 4 | - package-ecosystem: "gomod" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | -------------------------------------------------------------------------------- /.github/workflows/pr-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Automatic Pull Request test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | - "release*" 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | unit-test: 14 | name: Unit test 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout latest commit in the PR 18 | uses: actions/checkout@v3 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: "1.23" 23 | - name: Set up tools 24 | run: | 25 | go install golang.org/x/lint/golint@latest 26 | go install golang.org/x/tools/cmd/goimports@latest 27 | - name: Run code checks 28 | run: | 29 | make check-format 30 | make vet 31 | - name: Set up BPF 32 | run: | 33 | sudo apt-get install libbpf-dev 34 | - name: Build 35 | run: make build-linux 36 | - name: Unit test 37 | run: sudo make unit-test 38 | - name: Upload code coverage 39 | uses: codecov/codecov-action@v3 40 | functional-test: 41 | name: Functional test 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Checkout latest commit in the PR 45 | uses: actions/checkout@v3 46 | - name: Set up Go 47 | uses: actions/setup-go@v3 48 | with: 49 | go-version: "1.23" 50 | - name: Set up BPF 51 | run: | 52 | sudo apt-get install libbpf-dev 53 | - name: Build 54 | run: make build-linux 55 | - name: Functional test 56 | run: cd test && sudo make run-test 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bpf-sdk 2 | .idea 3 | coverage.txt 4 | test-data/vmlinux.h 5 | test-data/*.elf 6 | test/c/vmlinux.h 7 | test/c/*.elf 8 | test/main 9 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @aws/eks-networking 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build-linux 2 | 3 | export GOPROXY = direct 4 | 5 | CURDIR := $(abspath .) 6 | TESTDATADIR := $(CURDIR)/test-data 7 | BPFTOOL := bpftool 8 | CLANG := clang 9 | LOGFILE_PATH ?= stdout 10 | 11 | UNAME_ARCH = $(shell uname -m) 12 | ARCH = $(lastword $(subst :, ,$(filter $(UNAME_ARCH):%,x86_64:x86 aarch64:arm64))) 13 | 14 | 15 | BUILD_MODE ?= -buildmode=pie 16 | build-linux: BUILD_FLAGS = $(BUILD_MODE) -ldflags '-s -w' 17 | build-linux: ## Build all packages in /pkg. 18 | find ./pkg -type d -exec sh -c 'echo "Compiling package in: {}" && cd {} && go build' \; 19 | 20 | format: ## Format all Go source code files. 21 | @command -v goimports >/dev/null || { echo "ERROR: goimports not installed"; exit 1; } 22 | @exit $(shell find ./* \ 23 | -type f \ 24 | -name '*.go' \ 25 | -print0 | sort -z | xargs -0 -- goimports $(or $(FORMAT_FLAGS),-w) | wc -l | bc) 26 | 27 | # ALLPKGS is the set of packages provided in source. 28 | ALLPKGS = $(shell go list ./...) 29 | 30 | # Check formatting of source code files without modification. 31 | check-format: FORMAT_FLAGS = -l 32 | check-format: format 33 | 34 | # Run go vet on source code. 35 | vet: ## Run go vet on source code. 36 | go vet $(ALLPKGS) 37 | 38 | 39 | # Build BPF 40 | CLANG_INCLUDE := -I../../.. 41 | BPF_CFLAGS := -g -O2 -Wall -fpie -target bpf -DCORE -D__BPF_TRACING__ -D__TARGET_ARCH_$(ARCH) 42 | TARGETS := \ 43 | $(TESTDATADIR)/tc.ingress \ 44 | $(TESTDATADIR)/tc \ 45 | $(TESTDATADIR)/test.map \ 46 | $(TESTDATADIR)/test_license \ 47 | $(TESTDATADIR)/invalid_map \ 48 | $(TESTDATADIR)/recoverydata \ 49 | $(TESTDATADIR)/test-kprobe \ 50 | $(TESTDATADIR)/xdp \ 51 | $(TESTDATADIR)/ring_buffer 52 | 53 | %.bpf.elf: %.bpf.c 54 | $(CLANG) $(CLANG_INCLUDE) $(BPF_CFLAGS) -c $< -o $@ 55 | 56 | ## check if the vmlinux exists in /sys/kernel/btf directory 57 | VMLINUX_BTF ?= $(wildcard /sys/kernel/btf/vmlinux) 58 | ifeq ($(VMLINUX_BTF),) 59 | $(error Cannot find a vmlinux) 60 | endif 61 | 62 | $(TESTDATADIR)/vmlinux.h: 63 | $(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@ 64 | 65 | ##@ Run Unit Tests 66 | # Run unit tests 67 | unit-test: $(TESTDATADIR)/vmlinux.h 68 | unit-test: $(addsuffix .bpf.elf,$(TARGETS)) 69 | unit-test: export AWS_EBPF_SDK_LOG_FILE=$(LOGFILE_PATH) 70 | unit-test: ## Run unit tests 71 | go test -v -coverprofile=coverage.txt -covermode=atomic ./pkg/... 72 | 73 | .PHONY: clean 74 | clean: 75 | -@rm -f $(TESTDATADIR)/vmlinux.h 76 | -@rm -f $(TESTDATADIR)/*.elf 77 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aws-ebpf-sdk-go 2 | 3 | Golang based SDK for kernel eBPF operations i.e, load/attach/detach eBPF programs and create/delete/update maps. SDK relies on Unix bpf() system calls. 4 | 5 | SDK currently supports - 6 | 7 | 1. eBPF program types - 8 | a. Traffic Classifiers 9 | b. XDP 10 | c. Kprobes/Kretprobes 11 | d. Tracepoint probes 12 | 2. Ring buffer (would need kernel 5.10+) 13 | 14 | SDK currently do not support - 15 | 16 | 1. Map in Map 17 | 2. Perf buffer 18 | 19 | Contributions welcome! 20 | 21 | Note: This is the first version of SDK and interface is subject to change so kindly review the release notes before upgrading. 22 | 23 | # Getting started 24 | 25 | ## How to build SDK? 26 | 27 | Run `make build-linux` - this will build the sdk binary. 28 | 29 | ## How to build elf file? 30 | 31 | ``` 32 | clang -I../../.. -O2 -target bpf -c -o 33 | ``` 34 | 35 | ## How to use the SDK? 36 | 37 | **Note:** SDK expects the BPF File System (/sys/fs/bpf) to be mounted. 38 | 39 | In your application, 40 | 41 | 1. Get the latest SDK - 42 | 43 | ``` 44 | GOPROXY=direct go get github.com/aws/aws-ebpf-sdk-go 45 | ``` 46 | 47 | 2. Import the elfparser - 48 | 49 | ``` 50 | goebpfelfparser "github.com/aws/aws-ebpf-sdk-go/pkg/elfparser" 51 | ``` 52 | 53 | 3. Load the elf - 54 | 55 | ``` 56 | goebpfelfparser.LoadBpfFile(, ) 57 | ``` 58 | 59 | On a successful load, SDK returns - 60 | 61 | 1. loaded programs (includes associated maps) 62 | 63 | ``` 64 | This is indexed by the pinpath - 65 | 66 | type BpfData struct { 67 | Program ebpf_progs.BpfProgram // Return the program 68 | Maps map[string]ebpf_maps.BpfMap // List of associated maps 69 | } 70 | ``` 71 | 72 | 2. All maps in the elf file 73 | ``` 74 | This is indexed by the map name - 75 | 76 | type BpfMap struct { 77 | MapFD uint32 78 | MapID uint32 79 | MapMetaData CreateEBPFMapInput 80 | } 81 | ``` 82 | 83 | Application can specify custom pinpath while loading the elf file. 84 | 85 | Maps and Programs pinpath location is not customizable with the current version of SDK and will be installed under the below locations by default - 86 | 87 | Program PinPath - "/sys/fs/bpf/globals/aws/programs/" 88 | 89 | Map PinPath - "/sys/fs/bpf/globals/aws/maps/" 90 | 91 | Map defintion should follow the below definition else the SDK will fail to create the map. 92 | 93 | ``` 94 | struct bpf_map_def_pvt { 95 | __u32 type; 96 | __u32 key_size; 97 | __u32 value_size; 98 | __u32 max_entries; 99 | __u32 map_flags; 100 | __u32 pinning; 101 | __u32 inner_map_fd; 102 | }; 103 | ``` 104 | 105 | ## How to debug SDK issues? 106 | 107 | SDK logs are located here `/var/log/aws-routed-eni/ebpf-sdk.log`. 108 | 109 | ## How to run unit-test 110 | 111 | Run `sudo make unit-test` 112 | 113 | Note: you would need to run this on you linux system 114 | 115 | ## How to run functional tests 116 | 117 | Go to - 118 | 119 | ``` 120 | cd test/ 121 | sudo make run-test 122 | ``` 123 | 124 | ## Security 125 | 126 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 127 | 128 | If you think you’ve found a potential security issue, please do not post it in the Issues. Instead, please follow the 129 | instructions [here](https://aws.amazon.com/security/vulnerability-reporting/) or [email AWS security directly](mailto:aws-security@amazon.com). 130 | 131 | ## License 132 | 133 | This project is licensed under the Apache-2.0 License. 134 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aws/aws-ebpf-sdk-go 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.7 6 | 7 | require ( 8 | github.com/golang/mock v1.6.0 9 | github.com/stretchr/testify v1.10.0 10 | github.com/vishvananda/netlink v1.3.0 11 | go.uber.org/zap v1.27.0 12 | golang.org/x/sys v0.30.0 13 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 14 | ) 15 | 16 | require ( 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | github.com/vishvananda/netns v0.0.4 // indirect 20 | go.uber.org/multierr v1.10.0 // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 4 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 8 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= 10 | github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= 11 | github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= 12 | github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 13 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 14 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 15 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 16 | go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= 17 | go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 18 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 19 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 20 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 21 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 22 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 23 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 24 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 25 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 26 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 27 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 28 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 29 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 31 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 32 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 33 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 36 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 37 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 38 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 39 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 40 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 41 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 42 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 43 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 44 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 45 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 46 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 47 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 48 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 49 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 50 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 51 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 52 | -------------------------------------------------------------------------------- /pkg/cache/cache.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package cache 16 | 17 | import ( 18 | "sync" 19 | 20 | "github.com/aws/aws-ebpf-sdk-go/pkg/logger" 21 | ) 22 | 23 | var sdkCache GlobalCache 24 | var log = logger.Get() 25 | 26 | // Adding a struct if in future we need a cleanup routine 27 | type CacheValue struct { 28 | mapFD int 29 | } 30 | 31 | type GlobalCache interface { 32 | Set(key string, value int) 33 | Get(key string) (int, bool) 34 | Delete(key string) 35 | } 36 | 37 | type GlobalCacheMap struct { 38 | globalMap *sync.Map 39 | } 40 | 41 | func (c *GlobalCacheMap) Set(key string, value int) { 42 | c.globalMap.Store(key, CacheValue{mapFD: value}) 43 | } 44 | 45 | func (c *GlobalCacheMap) Get(key string) (int, bool) { 46 | entry, found := c.globalMap.Load(key) 47 | if !found { 48 | return -1, false 49 | } 50 | cacheEntry := entry.(CacheValue) 51 | return cacheEntry.mapFD, true 52 | } 53 | 54 | func (c *GlobalCacheMap) Delete(key string) { 55 | c.globalMap.Delete(key) 56 | } 57 | 58 | func Get() GlobalCache { 59 | if sdkCache == nil { 60 | sdkCache = New() 61 | log.Info("Initialized new SDK cache as an existing instance was not found") 62 | } 63 | return sdkCache 64 | } 65 | 66 | func New() GlobalCache { 67 | sdkCache = &GlobalCacheMap{ 68 | globalMap: new(sync.Map), 69 | } 70 | return sdkCache 71 | } 72 | -------------------------------------------------------------------------------- /pkg/cache/cache_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package cache 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestCache(t *testing.T) { 24 | SdkCache := New() 25 | 26 | value, _ := SdkCache.Get("invalidKey") 27 | assert.Equal(t, -1, value) 28 | 29 | SdkCache.Set("ingress-map", 10) 30 | 31 | value, _ = SdkCache.Get("ingress-map") 32 | assert.Equal(t, 10, value) 33 | 34 | SdkCache.Set("ingress-map", 11) 35 | 36 | value, _ = SdkCache.Get("ingress-map") 37 | assert.Equal(t, 11, value) 38 | 39 | SdkCache.Set("egress-map", 12) 40 | 41 | SdkCache.Delete("egress-map") 42 | 43 | value, _ = SdkCache.Get("egress-map") 44 | assert.Equal(t, -1, value) 45 | 46 | tempSdkCache := Get() 47 | value, _ = tempSdkCache.Get("ingress-map") 48 | assert.Equal(t, 11, value) 49 | 50 | } 51 | 52 | func TestCacheGetSameInstance(t *testing.T) { 53 | cache1 := Get() 54 | cache2 := Get() 55 | 56 | assert.True(t, cache1 == cache2) 57 | } 58 | 59 | func TestCacheNewAndGetSameInstance(t *testing.T) { 60 | cache1 := New() 61 | cache2 := Get() 62 | 63 | assert.True(t, cache1 == cache2) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/constants/constants.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | type EBPFMapType uint32 4 | 5 | // Currently synced with 5.10.188 - https://elixir.bootlin.com/linux/v5.10.188/source/include/uapi/linux/bpf.h 6 | const ( 7 | // BPF map type constants. Must match enum bpf_map_type from linux/bpf.h 8 | BPF_MAP_TYPE_UNSPEC EBPFMapType = iota 9 | BPF_MAP_TYPE_HASH 10 | BPF_MAP_TYPE_ARRAY 11 | BPF_MAP_TYPE_PROG_ARRAY 12 | BPF_MAP_TYPE_PERF_EVENT_ARRAY 13 | BPF_MAP_TYPE_PERCPU_HASH 14 | BPF_MAP_TYPE_PERCPU_ARRAY 15 | BPF_MAP_TYPE_STACK_TRACE 16 | BPF_MAP_TYPE_CGROUP_ARRAY 17 | BPF_MAP_TYPE_LRU_HASH 18 | BPF_MAP_TYPE_LRU_PERCPU_HASH 19 | BPF_MAP_TYPE_LPM_TRIE 20 | BPF_MAP_TYPE_ARRAY_OF_MAPS 21 | BPF_MAP_TYPE_HASH_OF_MAPS 22 | BPF_MAP_TYPE_DEVMAP 23 | BPF_MAP_TYPE_SOCKMAP 24 | BPF_MAP_TYPE_CPUMAP 25 | BPF_MAP_TYPE_XSKMAP 26 | BPF_MAP_TYPE_SOCKHASH 27 | BPF_MAP_TYPE_CGROUP_STORAGE 28 | BPF_MAP_TYPE_REUSEPORT_SOCKARRAY 29 | BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE 30 | BPF_MAP_TYPE_QUEUE 31 | BPF_MAP_TYPE_STACK 32 | BPF_MAP_TYPE_SK_STORAGE 33 | BPF_MAP_TYPE_DEVMAP_HASH 34 | BPF_MAP_TYPE_STRUCT_OPS 35 | BPF_MAP_TYPE_RINGBUF 36 | BPF_MAP_TYPE_INODE_STORAGE 37 | ) 38 | 39 | func (mapType EBPFMapType) Index() uint32 { 40 | return uint32(mapType) 41 | } 42 | 43 | type EBPFCmdType uint32 44 | 45 | const ( 46 | // BPF syscall command constants. Must match enum bpf_cmd from linux/bpf.h 47 | BPF_MAP_CREATE EBPFCmdType = iota 48 | BPF_MAP_LOOKUP_ELEM 49 | BPF_MAP_UPDATE_ELEM 50 | BPF_MAP_DELETE_ELEM 51 | BPF_MAP_GET_NEXT_KEY 52 | BPF_PROG_LOAD 53 | BPF_OBJ_PIN 54 | BPF_OBJ_GET 55 | BPF_PROG_ATTACH 56 | BPF_PROG_DETACH 57 | BPF_PROG_TEST_RUN 58 | BPF_PROG_GET_NEXT_ID 59 | BPF_MAP_GET_NEXT_ID 60 | BPF_PROG_GET_FD_BY_ID 61 | BPF_MAP_GET_FD_BY_ID 62 | BPF_OBJ_GET_INFO_BY_FD 63 | ) 64 | 65 | type EBPFMapUpdateType uint32 66 | 67 | const ( 68 | // Flags for BPF_MAP_UPDATE_ELEM. Must match values from linux/bpf.h 69 | BPF_ANY EBPFMapUpdateType = iota 70 | BPF_NOEXIST 71 | BPF_EXIST 72 | ) 73 | 74 | type EBPFPinType uint32 75 | 76 | const ( 77 | // BPF MAP pinning 78 | PIN_NONE EBPFPinType = iota 79 | PIN_OBJECT_NS 80 | PIN_GLOBAL_NS 81 | PIN_CUSTOM_NS 82 | ) 83 | 84 | func (pinType EBPFPinType) Index() uint32 { 85 | return uint32(pinType) 86 | } 87 | 88 | const ( 89 | BPF_F_NO_PREALLOC = 1 << 0 90 | BPF_F_NO_COMMON_LRU = 1 << 1 91 | 92 | BPF_DIR_MNT = "/sys/fs/bpf/" 93 | BPF_DIR_GLOBALS = "globals" 94 | BPF_FS_MAGIC = 0xcafe4a11 95 | 96 | BPFObjNameLen = 16 97 | BPFProgInfoAlign = 8 98 | BPFTagSize = 8 99 | 100 | /* 101 | * C struct of bpf_ins is 8 bytes because of this - 102 | * struct bpf_insn { 103 | * __u8 code; 104 | * __u8 dst_reg:4; 105 | * __u8 src_reg:4; 106 | * __s16 off; 107 | * __s32 imm; 108 | * }; 109 | * while go struct will return 9 since we dont have bit fields hence we dec(1). 110 | */ 111 | PROG_BPF_FS = "/sys/fs/bpf/globals/aws/programs/" 112 | MAP_BPF_FS = "/sys/fs/bpf/globals/aws/maps/" 113 | 114 | TRACEPOINT_EVENTS = "/sys/kernel/debug/tracing/events" 115 | 116 | KPROBE_SYS_EVENTS = "/sys/kernel/debug/tracing/kprobe_events" 117 | KPROBE_SYS_DEBUG = "/sys/kernel/debug/tracing/events/kprobes" 118 | KRETPROBE_SYS_DEBUG = "/sys/kernel/debug/tracing/events/kretprobes" 119 | 120 | QDISC_HANDLE = 0xffff 121 | DEFAULT_BPF_FILTER_HANDLE = 0x1 122 | ) 123 | 124 | type XDPattachType int 125 | 126 | const ( 127 | XDP_ATTACH_MODE_NONE = 1 << iota 128 | XDP_ATTACH_MODE_SKB 129 | XDP_ATTACH_MODE_DRV 130 | XDP_ATTACH_MODE_HW 131 | ) 132 | -------------------------------------------------------------------------------- /pkg/elfparser/generate_mocks.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | package elfparser 14 | 15 | //go:generate go run github.com/golang/mock/mockgen -destination mocks/elfparser_mocks.go . BpfSDKClient 16 | -------------------------------------------------------------------------------- /pkg/elfparser/mocks/elfparser_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/aws/aws-ebpf-sdk-go/pkg/elfparser (interfaces: BpfSDKClient) 3 | 4 | // Package mock_elfparser is a generated GoMock package. 5 | package mock_elfparser 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | elfparser "github.com/aws/aws-ebpf-sdk-go/pkg/elfparser" 11 | maps "github.com/aws/aws-ebpf-sdk-go/pkg/maps" 12 | gomock "github.com/golang/mock/gomock" 13 | ) 14 | 15 | // MockBpfSDKClient is a mock of BpfSDKClient interface. 16 | type MockBpfSDKClient struct { 17 | ctrl *gomock.Controller 18 | recorder *MockBpfSDKClientMockRecorder 19 | } 20 | 21 | // MockBpfSDKClientMockRecorder is the mock recorder for MockBpfSDKClient. 22 | type MockBpfSDKClientMockRecorder struct { 23 | mock *MockBpfSDKClient 24 | } 25 | 26 | // NewMockBpfSDKClient creates a new mock instance. 27 | func NewMockBpfSDKClient(ctrl *gomock.Controller) *MockBpfSDKClient { 28 | mock := &MockBpfSDKClient{ctrl: ctrl} 29 | mock.recorder = &MockBpfSDKClientMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockBpfSDKClient) EXPECT() *MockBpfSDKClientMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // GetAllBpfProgramsAndMaps mocks base method. 39 | func (m *MockBpfSDKClient) GetAllBpfProgramsAndMaps() (map[string]elfparser.BpfData, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "GetAllBpfProgramsAndMaps") 42 | ret0, _ := ret[0].(map[string]elfparser.BpfData) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // GetAllBpfProgramsAndMaps indicates an expected call of GetAllBpfProgramsAndMaps. 48 | func (mr *MockBpfSDKClientMockRecorder) GetAllBpfProgramsAndMaps() *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllBpfProgramsAndMaps", reflect.TypeOf((*MockBpfSDKClient)(nil).GetAllBpfProgramsAndMaps)) 51 | } 52 | 53 | // IncreaseRlimit mocks base method. 54 | func (m *MockBpfSDKClient) IncreaseRlimit() error { 55 | m.ctrl.T.Helper() 56 | ret := m.ctrl.Call(m, "IncreaseRlimit") 57 | ret0, _ := ret[0].(error) 58 | return ret0 59 | } 60 | 61 | // IncreaseRlimit indicates an expected call of IncreaseRlimit. 62 | func (mr *MockBpfSDKClientMockRecorder) IncreaseRlimit() *gomock.Call { 63 | mr.mock.ctrl.T.Helper() 64 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IncreaseRlimit", reflect.TypeOf((*MockBpfSDKClient)(nil).IncreaseRlimit)) 65 | } 66 | 67 | // LoadBpfFile mocks base method. 68 | func (m *MockBpfSDKClient) LoadBpfFile(arg0, arg1 string) (map[string]elfparser.BpfData, map[string]maps.BpfMap, error) { 69 | m.ctrl.T.Helper() 70 | ret := m.ctrl.Call(m, "LoadBpfFile", arg0, arg1) 71 | ret0, _ := ret[0].(map[string]elfparser.BpfData) 72 | ret1, _ := ret[1].(map[string]maps.BpfMap) 73 | ret2, _ := ret[2].(error) 74 | return ret0, ret1, ret2 75 | } 76 | 77 | // LoadBpfFile indicates an expected call of LoadBpfFile. 78 | func (mr *MockBpfSDKClientMockRecorder) LoadBpfFile(arg0, arg1 interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBpfFile", reflect.TypeOf((*MockBpfSDKClient)(nil).LoadBpfFile), arg0, arg1) 81 | } 82 | 83 | // LoadBpfFileWithCustomData mocks base method. 84 | func (m *MockBpfSDKClient) LoadBpfFileWithCustomData(arg0 elfparser.BpfCustomData) (map[string]elfparser.BpfData, map[string]maps.BpfMap, error) { 85 | m.ctrl.T.Helper() 86 | ret := m.ctrl.Call(m, "LoadBpfFileWithCustomData", arg0) 87 | ret0, _ := ret[0].(map[string]elfparser.BpfData) 88 | ret1, _ := ret[1].(map[string]maps.BpfMap) 89 | ret2, _ := ret[2].(error) 90 | return ret0, ret1, ret2 91 | } 92 | 93 | // LoadBpfFileWithCustomData indicates an expected call of LoadBpfFileWithCustomData. 94 | func (mr *MockBpfSDKClientMockRecorder) LoadBpfFileWithCustomData(arg0 interface{}) *gomock.Call { 95 | mr.mock.ctrl.T.Helper() 96 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBpfFileWithCustomData", reflect.TypeOf((*MockBpfSDKClient)(nil).LoadBpfFileWithCustomData), arg0) 97 | } 98 | 99 | // RecoverAllBpfProgramsAndMaps mocks base method. 100 | func (m *MockBpfSDKClient) RecoverAllBpfProgramsAndMaps() (map[string]elfparser.BpfData, error) { 101 | m.ctrl.T.Helper() 102 | ret := m.ctrl.Call(m, "RecoverAllBpfProgramsAndMaps") 103 | ret0, _ := ret[0].(map[string]elfparser.BpfData) 104 | ret1, _ := ret[1].(error) 105 | return ret0, ret1 106 | } 107 | 108 | // RecoverAllBpfProgramsAndMaps indicates an expected call of RecoverAllBpfProgramsAndMaps. 109 | func (mr *MockBpfSDKClientMockRecorder) RecoverAllBpfProgramsAndMaps() *gomock.Call { 110 | mr.mock.ctrl.T.Helper() 111 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecoverAllBpfProgramsAndMaps", reflect.TypeOf((*MockBpfSDKClient)(nil).RecoverAllBpfProgramsAndMaps)) 112 | } 113 | 114 | // RecoverGlobalMaps mocks base method. 115 | func (m *MockBpfSDKClient) RecoverGlobalMaps() (map[string]maps.BpfMap, error) { 116 | m.ctrl.T.Helper() 117 | ret := m.ctrl.Call(m, "RecoverGlobalMaps") 118 | ret0, _ := ret[0].(map[string]maps.BpfMap) 119 | ret1, _ := ret[1].(error) 120 | return ret0, ret1 121 | } 122 | 123 | // RecoverGlobalMaps indicates an expected call of RecoverGlobalMaps. 124 | func (mr *MockBpfSDKClientMockRecorder) RecoverGlobalMaps() *gomock.Call { 125 | mr.mock.ctrl.T.Helper() 126 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecoverGlobalMaps", reflect.TypeOf((*MockBpfSDKClient)(nil).RecoverGlobalMaps)) 127 | } 128 | -------------------------------------------------------------------------------- /pkg/events/events.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package events 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "sync" 21 | "syscall" 22 | "unsafe" 23 | 24 | constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" 25 | poller "github.com/aws/aws-ebpf-sdk-go/pkg/events/poll" 26 | "github.com/aws/aws-ebpf-sdk-go/pkg/logger" 27 | ebpf_maps "github.com/aws/aws-ebpf-sdk-go/pkg/maps" 28 | "golang.org/x/sys/unix" 29 | ) 30 | 31 | var log = logger.Get() 32 | 33 | type Events interface { 34 | InitRingBuffer(mapFDlist []int) (map[int]chan []byte, error) 35 | } 36 | 37 | var _ Events = &events{} 38 | 39 | func New() Events { 40 | return &events{ 41 | PageSize: os.Getpagesize(), 42 | RingCnt: 0, 43 | EventFdCnt: 0, 44 | } 45 | 46 | } 47 | 48 | type events struct { 49 | RingBuffers []*RingBuffer 50 | PageSize int 51 | RingCnt int 52 | EventFdCnt int 53 | eventsStopChannel chan struct{} 54 | wg sync.WaitGroup 55 | eventsDataChannel chan []byte 56 | 57 | epoller *poller.EventPoller 58 | } 59 | 60 | func isValidMapFDList(mapFDlist []int) bool { 61 | for _, mapFD := range mapFDlist { 62 | log.Infof("Got map FD %d", mapFD) 63 | if mapFD == -1 { 64 | return false 65 | } 66 | mapInfo, err := ebpf_maps.GetBPFmapInfo(mapFD) 67 | if err != nil { 68 | log.Errorf("failed to get map info") 69 | return false 70 | } 71 | if mapInfo.Type != constdef.BPF_MAP_TYPE_RINGBUF.Index() { 72 | log.Errorf("unsupported map type, should be - BPF_MAP_TYPE_RINGBUF") 73 | return false 74 | } 75 | } 76 | return true 77 | } 78 | 79 | func (ev *events) InitRingBuffer(mapFDlist []int) (map[int]chan []byte, error) { 80 | 81 | // Validate mapFD 82 | if !isValidMapFDList(mapFDlist) { 83 | return nil, fmt.Errorf("mapFDs passed to InitRingBuffer is invalid") 84 | } 85 | 86 | epoll, err := poller.NewEventPoller() 87 | if err != nil { 88 | return nil, fmt.Errorf("failed to create epoll instance: %s", err) 89 | } 90 | ev.epoller = epoll 91 | 92 | ringBufferChanList := make(map[int]chan []byte) 93 | for _, mapFD := range mapFDlist { 94 | 95 | mapInfo, err := ebpf_maps.GetBPFmapInfo(mapFD) 96 | if err != nil { 97 | log.Errorf("failed to get map info for mapFD %d", mapFD) 98 | return nil, fmt.Errorf("failed to map info") 99 | } 100 | 101 | eventsChan, err := ev.setupRingBuffer(mapFD, mapInfo.MaxEntries) 102 | if err != nil { 103 | ev.cleanupRingBuffer() 104 | return nil, fmt.Errorf("failed to add ring buffer: %s", err) 105 | } 106 | 107 | log.Infof("Ringbuffer setup done for %d", mapFD) 108 | ringBufferChanList[mapFD] = eventsChan 109 | } 110 | return ringBufferChanList, nil 111 | } 112 | 113 | func (ev *events) setupRingBuffer(mapFD int, maxEntries uint32) (chan []byte, error) { 114 | ringbuffer := &RingBuffer{ 115 | RingBufferMapFD: mapFD, 116 | Mask: uint64(maxEntries - 1), 117 | } 118 | 119 | // [Consumer page - 4k][Producer page - 4k][Data section - twice the size of max entries] 120 | // Refer kernel code, twice the size of max entries will help in boundary scenarios 121 | // https://github.com/torvalds/linux/blob/master/kernel/bpf/ringbuf.c#L125 122 | 123 | consumer, err := unix.Mmap(mapFD, 0, ev.PageSize, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) 124 | if err != nil { 125 | return nil, fmt.Errorf("failed to create Mmap for consumer -> %d: %s", mapFD, err) 126 | } 127 | 128 | ringbuffer.Consumerpos = unsafe.Pointer(&consumer[0]) 129 | ringbuffer.Consumer = consumer 130 | 131 | mmap_sz := uint32(ev.PageSize) + 2*maxEntries 132 | producer, err := unix.Mmap(mapFD, int64(ev.PageSize), int(mmap_sz), unix.PROT_READ, unix.MAP_SHARED) 133 | if err != nil { 134 | unix.Munmap(producer) 135 | return nil, fmt.Errorf("failed to create Mmap for producer -> %d: %s", mapFD, err) 136 | } 137 | 138 | ringbuffer.Producerpos = unsafe.Pointer(&producer[0]) 139 | ringbuffer.Producer = producer 140 | ringbuffer.Data = unsafe.Pointer(uintptr(unsafe.Pointer(&producer[0])) + uintptr(ev.PageSize)) 141 | 142 | ev.RingBuffers = append(ev.RingBuffers, ringbuffer) 143 | 144 | err = ev.epoller.AddEpollCtl(mapFD, ev.EventFdCnt) 145 | if err != nil { 146 | unix.Munmap(producer) 147 | return nil, fmt.Errorf("failed to Epoll event: %s", err) 148 | } 149 | ev.RingCnt++ 150 | ev.EventFdCnt++ 151 | 152 | //Start channels read 153 | ev.eventsStopChannel = make(chan struct{}) 154 | ev.eventsDataChannel = make(chan []byte) 155 | 156 | ev.wg.Add(1) 157 | go ev.reconcileEventsDataChannel() 158 | return ev.eventsDataChannel, nil 159 | } 160 | 161 | func (ev *events) cleanupRingBuffer() { 162 | 163 | for i := 0; i < ev.RingCnt; i++ { 164 | _ = unix.Munmap(ev.RingBuffers[i].Producer) 165 | _ = unix.Munmap(ev.RingBuffers[i].Consumer) 166 | ev.RingBuffers[i].Producerpos = nil 167 | ev.RingBuffers[i].Consumerpos = nil 168 | } 169 | 170 | if ev.epoller.GetEpollFD() >= 0 { 171 | _ = syscall.Close(ev.epoller.GetEpollFD()) 172 | } 173 | ev.epoller = nil 174 | ev.RingBuffers = nil 175 | return 176 | } 177 | 178 | func (ev *events) reconcileEventsDataChannelHandler(pollerCh <-chan int) { 179 | for { 180 | select { 181 | case bufferPtr, ok := <-pollerCh: 182 | if !ok { 183 | return 184 | } 185 | ev.readRingBuffer(ev.RingBuffers[bufferPtr]) 186 | 187 | case <-ev.eventsStopChannel: 188 | return 189 | } 190 | } 191 | } 192 | 193 | func (ev *events) reconcileEventsDataChannel() { 194 | 195 | pollerCh := ev.epoller.EpollStart() 196 | defer ev.wg.Done() 197 | 198 | go ev.reconcileEventsDataChannelHandler(pollerCh) 199 | 200 | <-ev.eventsStopChannel 201 | } 202 | 203 | // Similar to libbpf poll and process ring 204 | // Ref: https://github.com/torvalds/linux/blob/744a759492b5c57ff24a6e8aabe47b17ad8ee964/tools/lib/bpf/ringbuf.c#L227 205 | func (ev *events) readRingBuffer(eventRing *RingBuffer) { 206 | consPosition := eventRing.getConsumerPosition() 207 | ev.parseBuffer(consPosition, eventRing) 208 | } 209 | 210 | func (ev *events) parseBuffer(consumerPosition uint64, eventRing *RingBuffer) { 211 | var readDone bool 212 | for { 213 | readDone = true 214 | producerPosition := eventRing.getProducerPosition() 215 | for consumerPosition < producerPosition { 216 | 217 | // Get the header - Data points to the DataPage which will be offset by consumerPosition 218 | ringdata := eventRing.ParseRingData(consumerPosition) 219 | 220 | // Check if busy then skip, Might not be committed yet 221 | // There are 2 steps -> reserve and then commit/discard 222 | if ringdata.BusyRecord { 223 | readDone = true 224 | break 225 | } 226 | 227 | readDone = false 228 | 229 | // Update the position to the next record irrespective of discard or commit of data 230 | consumerPosition += uint64(ringdata.RecordLen) 231 | 232 | //Pick the data only if committed 233 | if !ringdata.DiscardRecord { 234 | ev.eventsDataChannel <- ringdata.parseSample() 235 | } 236 | eventRing.setConsumerPosition(consumerPosition) 237 | } 238 | if readDone { 239 | break 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /pkg/events/generate_mocks.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | package events 14 | 15 | //go:generate go run github.com/golang/mock/mockgen -destination mocks/events_mocks.go . Events 16 | -------------------------------------------------------------------------------- /pkg/events/mocks/events_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/aws/aws-ebpf-sdk-go/pkg/events (interfaces: Events) 3 | 4 | // Package mock_events is a generated GoMock package. 5 | package mock_events 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | ) 12 | 13 | // MockEvents is a mock of Events interface. 14 | type MockEvents struct { 15 | ctrl *gomock.Controller 16 | recorder *MockEventsMockRecorder 17 | } 18 | 19 | // MockEventsMockRecorder is the mock recorder for MockEvents. 20 | type MockEventsMockRecorder struct { 21 | mock *MockEvents 22 | } 23 | 24 | // NewMockEvents creates a new mock instance. 25 | func NewMockEvents(ctrl *gomock.Controller) *MockEvents { 26 | mock := &MockEvents{ctrl: ctrl} 27 | mock.recorder = &MockEventsMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockEvents) EXPECT() *MockEventsMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // InitRingBuffer mocks base method. 37 | func (m *MockEvents) InitRingBuffer(arg0 []int) (map[int]chan []byte, error) { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "InitRingBuffer", arg0) 40 | ret0, _ := ret[0].(map[int]chan []byte) 41 | ret1, _ := ret[1].(error) 42 | return ret0, ret1 43 | } 44 | 45 | // InitRingBuffer indicates an expected call of InitRingBuffer. 46 | func (mr *MockEventsMockRecorder) InitRingBuffer(arg0 interface{}) *gomock.Call { 47 | mr.mock.ctrl.T.Helper() 48 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitRingBuffer", reflect.TypeOf((*MockEvents)(nil).InitRingBuffer), arg0) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/events/poll/epoll.go: -------------------------------------------------------------------------------- 1 | package poll 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/aws/aws-ebpf-sdk-go/pkg/logger" 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | var log = logger.Get() 12 | 13 | type EventPoller struct { 14 | fds []uint32 15 | epollFd int 16 | wg sync.WaitGroup 17 | epollEvent []unix.EpollEvent 18 | bufferCnt int 19 | 20 | stopEventPollerChan chan struct{} 21 | fdEventPollerChan chan int 22 | } 23 | 24 | func NewEventPoller() (*EventPoller, error) { 25 | epollFD, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC) 26 | if err != nil { 27 | return nil, fmt.Errorf("failed to create epoll instance: %s", err) 28 | } 29 | e := &EventPoller{ 30 | epollFd: epollFD, 31 | stopEventPollerChan: make(chan struct{}), 32 | fdEventPollerChan: make(chan int), 33 | } 34 | return e, nil 35 | } 36 | 37 | func (e *EventPoller) GetEpollFD() int { 38 | return e.epollFd 39 | } 40 | 41 | func (e *EventPoller) AddEpollCtl(mapFD, eventFD int) error { 42 | epollEvent := unix.EpollEvent{ 43 | Events: unix.EPOLLIN, 44 | Fd: int32(eventFD), 45 | } 46 | 47 | err := unix.EpollCtl(e.epollFd, unix.EPOLL_CTL_ADD, mapFD, &epollEvent) 48 | if err != nil { 49 | return fmt.Errorf("failed to Epoll event: %s", err) 50 | } 51 | e.epollEvent = append(e.epollEvent, epollEvent) 52 | e.bufferCnt++ 53 | return nil 54 | } 55 | 56 | func (e *EventPoller) EpollStart() <-chan int { 57 | e.wg.Add(1) 58 | go e.eventsPoller() 59 | return e.fdEventPollerChan 60 | } 61 | 62 | func (e *EventPoller) getEventFDs(totalEvents int) { 63 | for _, event := range e.epollEvent[:totalEvents] { 64 | select { 65 | case e.fdEventPollerChan <- int(event.Fd): 66 | 67 | case <-e.stopEventPollerChan: 68 | return 69 | } 70 | } 71 | } 72 | func (e *EventPoller) eventsPoller() { 73 | defer e.wg.Done() 74 | for { 75 | select { 76 | case <-e.stopEventPollerChan: 77 | return 78 | default: 79 | break 80 | } 81 | totalEvents := e.poll(e.epollEvent[:e.bufferCnt]) 82 | e.getEventFDs(totalEvents) 83 | } 84 | } 85 | 86 | func (e *EventPoller) poll(events []unix.EpollEvent) int { 87 | timeoutMs := 150 88 | n, err := unix.EpollWait(e.epollFd, events, timeoutMs) 89 | if err != nil { 90 | return 0 91 | } 92 | return n 93 | } 94 | -------------------------------------------------------------------------------- /pkg/events/ringbuffer.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package events 16 | 17 | import ( 18 | "encoding/binary" 19 | "sync/atomic" 20 | "unsafe" 21 | 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | var ringbufHeaderSize = binary.Size(ringbufHeader{}) 26 | 27 | // ringbufHeader from 'struct bpf_ringbuf_hdr' in kernel/bpf/ringbuf.c 28 | type ringbufHeader struct { 29 | Len uint32 30 | PgOff uint32 31 | } 32 | 33 | func memcpy(dst, src unsafe.Pointer, count uintptr) { 34 | for i := uintptr(0); i < count; i++ { 35 | b := *(*byte)(unsafe.Pointer(uintptr(src) + i)) 36 | *(*byte)(unsafe.Pointer(uintptr(dst) + i)) = b 37 | } 38 | } 39 | 40 | type RingBuffer struct { 41 | Consumerpos unsafe.Pointer 42 | Consumer []byte 43 | Producerpos unsafe.Pointer 44 | Producer []byte 45 | Mask uint64 46 | RingBufferMapFD int 47 | Data unsafe.Pointer 48 | } 49 | 50 | func (r *RingBuffer) getConsumerPosition() uint64 { 51 | return atomic.LoadUint64((*uint64)(r.Consumerpos)) 52 | } 53 | 54 | func (r *RingBuffer) setConsumerPosition(newConsumerPosition uint64) { 55 | atomic.StoreUint64((*uint64)(r.Consumerpos), newConsumerPosition) 56 | } 57 | 58 | func (r *RingBuffer) getProducerPosition() uint64 { 59 | return atomic.LoadUint64((*uint64)(r.Producerpos)) 60 | 61 | } 62 | 63 | func (r *RingBuffer) ParseRingData(consumerPosition uint64) *RingData { 64 | updateConsumerPosition := (uintptr(consumerPosition) & uintptr(r.Mask)) 65 | data := (*int32)(unsafe.Pointer(uintptr(r.Data) + updateConsumerPosition)) 66 | 67 | // Single record will have [header,payload] and header maintains [len, pgoff] 68 | // len field in the header, is the u32 data len but kernel overloads this field with busy and discard bit 69 | // BPF_RINGBUF_BUSY_BIT = (1U << 31) 70 | // BPF_RINGBUF_DISCARD_BIT = (1U << 30) [Ref kernel bpf.h] 71 | // If busy bit is set we skip read i.e, not update consumer position and re-read during next poll 72 | // if Discard bit is set we just update consumer position but not read the record. 73 | // We fetch 32 bits value from the data pointer which is the start of the record. 74 | entryLen := atomic.LoadInt32(data) 75 | 76 | // entryLen now is the "len" in ringbuf Header struct. 77 | // But this is overloaded with busy and discard bit so skip it to get actual data/record length 78 | strippedDataLen := ((uint32(entryLen) << 2) >> 2) 79 | 80 | // recordLen will include actual data/record length + header length 81 | recordLen := (strippedDataLen + uint32(ringbufHeaderSize)) 82 | 83 | // round up recordLen to nearest 8-byte alignment which will be the offset for next record start position 84 | // ref to __bpf_ringbuf_reserve 85 | // https://github.com/torvalds/linux/blob/master/kernel/bpf/ringbuf.c#L418 86 | roundedEntryLen := (recordLen + 7) &^ 7 87 | 88 | ringdata := &RingData{ 89 | Data: data, 90 | Len: uint32(entryLen), 91 | DataLen: uint32(strippedDataLen), 92 | RecordLen: uint32(roundedEntryLen), 93 | } 94 | 95 | // Check if busy bit is set 96 | if (ringdata.Len & unix.BPF_RINGBUF_BUSY_BIT) != 0 { 97 | ringdata.BusyRecord = true 98 | } 99 | 100 | // Check if record has to be discarded 101 | if (ringdata.Len & unix.BPF_RINGBUF_DISCARD_BIT) != 0 { 102 | ringdata.DiscardRecord = true 103 | } 104 | return ringdata 105 | } 106 | 107 | type RingData struct { 108 | Data *int32 109 | Len uint32 110 | DataLen uint32 111 | RecordLen uint32 112 | BusyRecord bool 113 | DiscardRecord bool 114 | } 115 | 116 | func (rd *RingData) parseSample() []byte { 117 | readableSample := unsafe.Pointer(uintptr(unsafe.Pointer(rd.Data)) + uintptr(ringbufHeaderSize)) 118 | dataBuf := make([]byte, int(rd.DataLen)) 119 | memcpy(unsafe.Pointer(&dataBuf[0]), readableSample, uintptr(rd.DataLen)) 120 | return dataBuf 121 | } 122 | -------------------------------------------------------------------------------- /pkg/kprobe/kprobe.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package kprobe 16 | 17 | import ( 18 | "fmt" 19 | "io/ioutil" 20 | "os" 21 | "strconv" 22 | "strings" 23 | "syscall" 24 | "unsafe" 25 | 26 | constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" 27 | "github.com/aws/aws-ebpf-sdk-go/pkg/logger" 28 | "golang.org/x/sys/unix" 29 | ) 30 | 31 | var log = logger.Get() 32 | 33 | type BpfKprobe interface { 34 | KprobeAttach() error 35 | KretprobeAttach() error 36 | KprobeDetach() error 37 | KretprobeDetach() error 38 | } 39 | 40 | var _ BpfKprobe = &bpfKprobe{} 41 | 42 | type bpfKprobe struct { 43 | progFD int 44 | eventName string 45 | funcName string 46 | perfFD int 47 | } 48 | 49 | func New(fd int, eName, fName string) BpfKprobe { 50 | return &bpfKprobe{ 51 | progFD: fd, 52 | eventName: eName, 53 | funcName: fName, 54 | } 55 | 56 | } 57 | 58 | func (k *bpfKprobe) SetPerfFD(perfFD int) { 59 | k.perfFD = perfFD 60 | } 61 | 62 | func (k *bpfKprobe) GetPerfFD() int { 63 | return k.perfFD 64 | } 65 | 66 | /* 67 | p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : Set a probe 68 | r[MAXACTIVE][:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] : Set a return probe 69 | -:[GRP/]EVENT 70 | */ 71 | func (k *bpfKprobe) KprobeAttach() error { 72 | 73 | if k.progFD <= 0 { 74 | log.Errorf("invalid BPF prog FD %d", k.progFD) 75 | return fmt.Errorf("Invalid BPF prog FD %d", k.progFD) 76 | 77 | } 78 | 79 | // if event is nil, we pick funcName 80 | if len(k.eventName) == 0 { 81 | k.eventName = k.funcName 82 | } 83 | 84 | // Register the Kprobe event 85 | file, err := os.OpenFile(constdef.KPROBE_SYS_EVENTS, os.O_WRONLY|os.O_APPEND, 0) 86 | if err != nil { 87 | log.Errorf("error opening kprobe_events file: %v", err) 88 | return err 89 | } 90 | defer file.Close() 91 | 92 | eventString := fmt.Sprintf("p:kprobes/%s %s", k.eventName, k.funcName) 93 | _, err = file.WriteString(eventString) 94 | if err != nil { 95 | log.Errorf("error writing to kprobe_events file: %v", err) 96 | return err 97 | } 98 | 99 | //Get the Kprobe ID 100 | kprobeIDpath := fmt.Sprintf("%s/%s/id", constdef.KPROBE_SYS_DEBUG, k.eventName) 101 | data, err := os.ReadFile(kprobeIDpath) 102 | if err != nil { 103 | log.Errorf("unable to read the kprobeID: %v", err) 104 | return err 105 | } 106 | id := strings.TrimSpace(string(data)) 107 | eventID, err := strconv.Atoi(id) 108 | if err != nil { 109 | log.Errorf("invalid ID during parsing: %s - %w", id, err) 110 | return err 111 | } 112 | 113 | log.Infof("Got eventID %d", eventID) 114 | 115 | attr := unix.PerfEventAttr{ 116 | Type: unix.PERF_TYPE_TRACEPOINT, 117 | Sample: 1, 118 | Wakeup: 1, 119 | Config: uint64(eventID), 120 | } 121 | attr.Size = uint32(unsafe.Sizeof(attr)) 122 | 123 | fd, err := unix.PerfEventOpen(&attr, -1, 0, -1, unix.PERF_FLAG_FD_CLOEXEC) 124 | if err != nil { 125 | log.Errorf("failed to open perf event %v", err) 126 | return err 127 | } 128 | 129 | log.Infof("Attach bpf program to perf event Prog FD %d Event FD %d", k.progFD, fd) 130 | 131 | if _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(int(fd)), uintptr(uint(unix.PERF_EVENT_IOC_SET_BPF)), uintptr(k.progFD)); err != 0 { 132 | log.Errorf("error attaching bpf program to perf event: %v", err) 133 | return err 134 | } 135 | 136 | if _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(int(fd)), uintptr(uint(unix.PERF_EVENT_IOC_ENABLE)), 0); err != 0 { 137 | log.Errorf("error enabling perf event: %v", err) 138 | return err 139 | } 140 | 141 | k.SetPerfFD(fd) 142 | 143 | log.Infof("KPROBE Attach done!!! %d", fd) 144 | 145 | return nil 146 | 147 | } 148 | 149 | /* 150 | p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : Set a probe 151 | r[MAXACTIVE][:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] : Set a return probe 152 | -:[GRP/]EVENT 153 | 154 | MAXACTIVE : Maximum number of instances of the specified function that 155 | 156 | can be probed simultaneously, or 0 for the default value 157 | as defined in Documentation/kprobes.txt section 1.3.1. 158 | */ 159 | func (k *bpfKprobe) KretprobeAttach() error { 160 | 161 | if k.progFD <= 0 { 162 | log.Infof("invalid BPF prog FD %d", k.progFD) 163 | return fmt.Errorf("Invalid BPF prog FD %d", k.progFD) 164 | 165 | } 166 | // if event is nil, we pick funcName 167 | if len(k.eventName) == 0 { 168 | k.eventName = k.funcName 169 | } 170 | 171 | // Register the Kprobe event 172 | file, err := os.OpenFile(constdef.KPROBE_SYS_EVENTS, os.O_WRONLY|os.O_APPEND, 0) 173 | if err != nil { 174 | log.Errorf("error opening kprobe_events file: %v", err) 175 | return err 176 | } 177 | defer file.Close() 178 | 179 | eventString := fmt.Sprintf("r4096:kretprobes/%s %s", k.eventName, k.funcName) 180 | _, err = file.WriteString(eventString) 181 | if err != nil { 182 | log.Errorf("error writing to kprobe_events file: %v", err) 183 | return err 184 | } 185 | 186 | //Get the Kprobe ID 187 | kprobeIDpath := fmt.Sprintf("%s/%s/id", constdef.KRETPROBE_SYS_DEBUG, k.eventName) 188 | data, err := os.ReadFile(kprobeIDpath) 189 | if err != nil { 190 | log.Errorf("unable to read the kretprobeID: %v", err) 191 | return err 192 | } 193 | id := strings.TrimSpace(string(data)) 194 | eventID, err := strconv.Atoi(id) 195 | if err != nil { 196 | log.Errorf("invalid ID during parsing: %s - %w", id, err) 197 | return err 198 | } 199 | 200 | log.Infof("Got eventID %d", eventID) 201 | 202 | attr := unix.PerfEventAttr{ 203 | Type: unix.PERF_TYPE_TRACEPOINT, 204 | Sample: 1, 205 | Wakeup: 1, 206 | Config: uint64(eventID), 207 | } 208 | attr.Size = uint32(unsafe.Sizeof(attr)) 209 | 210 | fd, err := unix.PerfEventOpen(&attr, -1, 0, -1, unix.PERF_FLAG_FD_CLOEXEC) 211 | if err != nil { 212 | log.Errorf("failed to open perf event %v", err) 213 | return err 214 | } 215 | 216 | log.Infof("Attach bpf program to perf event Prog FD %d Event FD %d", k.progFD, fd) 217 | 218 | if _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(int(fd)), uintptr(uint(unix.PERF_EVENT_IOC_SET_BPF)), uintptr(k.progFD)); err != 0 { 219 | log.Errorf("error attaching bpf program to perf event: %v", err) 220 | return err 221 | } 222 | 223 | if _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(int(fd)), uintptr(uint(unix.PERF_EVENT_IOC_ENABLE)), 0); err != 0 { 224 | log.Errorf("error enabling perf event: %v", err) 225 | return err 226 | } 227 | 228 | k.SetPerfFD(fd) 229 | 230 | log.Infof("KRETPROBE Attach done!!! %d", fd) 231 | return nil 232 | 233 | } 234 | 235 | func probeDetach(eventName string, perfFD int, kretProbe bool) error { 236 | log.Infof("Calling Detach on %s", eventName) 237 | 238 | if _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(int(perfFD)), uintptr(uint(unix.PERF_EVENT_IOC_DISABLE)), 0); err != 0 { 239 | log.Errorf("error enabling perf event: %v", err) 240 | return err 241 | } 242 | unix.Close(perfFD) 243 | 244 | eventEnable := constdef.KPROBE_SYS_DEBUG + "/" + eventName + "/enable" 245 | if kretProbe { 246 | eventEnable = constdef.KRETPROBE_SYS_DEBUG + "/" + eventName + "/enable" 247 | } 248 | 249 | setEnable := []byte("0") 250 | 251 | err := ioutil.WriteFile(eventEnable, setEnable, os.ModePerm) 252 | if err != nil { 253 | return err 254 | } 255 | 256 | file, err := os.OpenFile(constdef.KPROBE_SYS_EVENTS, os.O_APPEND|os.O_WRONLY, 0) 257 | if err != nil { 258 | log.Errorf("cannot open probe events: %v", err) 259 | return err 260 | } 261 | defer file.Close() 262 | 263 | eventString := fmt.Sprintf("-:%s\n", eventName) 264 | if _, err = file.WriteString(eventString); err != nil { 265 | pathErr, ok := err.(*os.PathError) 266 | if ok && pathErr.Err == syscall.ENOENT { 267 | log.Infof("File is already cleanedup, maybe some other process?") 268 | return nil 269 | } 270 | log.Errorf("cannot update the probe events %v", err) 271 | return err 272 | } 273 | log.Infof("probe Detach done!!!") 274 | return nil 275 | } 276 | 277 | func (k *bpfKprobe) KprobeDetach() error { 278 | log.Infof("Calling Kprobe Detach on %s", k.eventName) 279 | return probeDetach(k.eventName, k.perfFD, false) 280 | } 281 | 282 | func (k *bpfKprobe) KretprobeDetach() error { 283 | log.Infof("Calling Kretprobe Detach on %s", k.eventName) 284 | return probeDetach(k.eventName, k.perfFD, true) 285 | } 286 | -------------------------------------------------------------------------------- /pkg/kprobe/kprobe_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package kprobe 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" 22 | "github.com/aws/aws-ebpf-sdk-go/pkg/elfparser" 23 | mock_ebpf_maps "github.com/aws/aws-ebpf-sdk-go/pkg/maps/mocks" 24 | mock_ebpf_progs "github.com/aws/aws-ebpf-sdk-go/pkg/progs/mocks" 25 | "github.com/aws/aws-ebpf-sdk-go/pkg/utils" 26 | "github.com/golang/mock/gomock" 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | const ( 31 | DUMMY_PROG_NAME = "test" 32 | ) 33 | 34 | type testMocks struct { 35 | path string 36 | ctrl *gomock.Controller 37 | ebpf_progs *mock_ebpf_progs.MockBpfProgAPIs 38 | ebpf_maps *mock_ebpf_maps.MockBpfMapAPIs 39 | } 40 | 41 | func setup(t *testing.T, testPath string) *testMocks { 42 | ctrl := gomock.NewController(t) 43 | return &testMocks{ 44 | path: testPath, 45 | ctrl: ctrl, 46 | ebpf_progs: mock_ebpf_progs.NewMockBpfProgAPIs(ctrl), 47 | ebpf_maps: mock_ebpf_maps.NewMockBpfMapAPIs(ctrl), 48 | } 49 | } 50 | 51 | func TestTCKprobeAttachDetach(t *testing.T) { 52 | if os.Getuid() != 0 { 53 | t.Skip("Test requires root privileges.") 54 | } 55 | 56 | m := setup(t, "../../test-data/test-kprobe.bpf.elf") 57 | defer m.ctrl.Finish() 58 | 59 | utils.Mount_bpf_fs() 60 | defer utils.Unmount_bpf_fs() 61 | 62 | m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() 63 | m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() 64 | m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() 65 | m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() 66 | m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() 67 | m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() 68 | 69 | bpfSDKclient := elfparser.New() 70 | progInfo, _, err := bpfSDKclient.LoadBpfFile(m.path, DUMMY_PROG_NAME) 71 | if err != nil { 72 | assert.NoError(t, err) 73 | } 74 | pinPath := constdef.PROG_BPF_FS + DUMMY_PROG_NAME + "_oom_kill" 75 | 76 | progFD := progInfo[pinPath].Program.ProgFD 77 | funcName := "oom_kill_process" 78 | eventName := funcName + "__goebpf" 79 | 80 | kprobeClient := New(progFD, eventName, funcName) 81 | if err := kprobeClient.KprobeAttach(); err != nil { 82 | assert.NoError(t, err) 83 | } 84 | 85 | if err := kprobeClient.KprobeDetach(); err != nil { 86 | assert.NoError(t, err) 87 | } 88 | } 89 | 90 | func TestTCKretprobeAttachDetach(t *testing.T) { 91 | if os.Getuid() != 0 { 92 | t.Skip("Test requires root privileges.") 93 | } 94 | 95 | m := setup(t, "../../test-data/test-kprobe.bpf.elf") 96 | defer m.ctrl.Finish() 97 | 98 | utils.Mount_bpf_fs() 99 | defer utils.Unmount_bpf_fs() 100 | 101 | m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() 102 | m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() 103 | m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() 104 | m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() 105 | m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() 106 | m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() 107 | 108 | bpfSDKclient := elfparser.New() 109 | progInfo, _, err := bpfSDKclient.LoadBpfFile(m.path, DUMMY_PROG_NAME) 110 | if err != nil { 111 | assert.NoError(t, err) 112 | } 113 | pinPath := constdef.PROG_BPF_FS + DUMMY_PROG_NAME + "_oom_kill" 114 | 115 | progFD := progInfo[pinPath].Program.ProgFD 116 | funcName := "oom_kill_process" 117 | eventName := funcName + "__goebpf" 118 | 119 | kprobeClient := New(progFD, eventName, funcName) 120 | if err := kprobeClient.KretprobeAttach(); err != nil { 121 | assert.NoError(t, err) 122 | } 123 | 124 | if err := kprobeClient.KretprobeDetach(); err != nil { 125 | assert.NoError(t, err) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /pkg/logger/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package logger 16 | 17 | import ( 18 | "os" 19 | "runtime" 20 | "strings" 21 | 22 | "go.uber.org/zap" 23 | "go.uber.org/zap/zapcore" 24 | "gopkg.in/natefinch/lumberjack.v2" 25 | ) 26 | 27 | const ( 28 | defaultLogFilePath = "/var/log/aws-routed-eni/ebpf-sdk.log" 29 | defaultLogLevel = "Debug" 30 | envLogLevel = "AWS_EBPF_SDK_LOGLEVEL" 31 | envLogFilePath = "AWS_EBPF_SDK_LOG_FILE" 32 | ) 33 | 34 | // Log is global variable so that log functions can be directly accessed 35 | var log Logger 36 | 37 | // Fields Type to pass when we want to call WithFields for structured logging 38 | type Fields map[string]interface{} 39 | 40 | // Logger is our contract for the logger 41 | type Logger interface { 42 | Debugf(format string, args ...interface{}) 43 | 44 | Debug(format string) 45 | 46 | Infof(format string, args ...interface{}) 47 | 48 | Info(format string) 49 | 50 | Warnf(format string, args ...interface{}) 51 | 52 | Warn(format string) 53 | 54 | Errorf(format string, args ...interface{}) 55 | 56 | Error(format string) 57 | 58 | Fatalf(format string, args ...interface{}) 59 | 60 | Panicf(format string, args ...interface{}) 61 | 62 | WithFields(keyValues Fields) Logger 63 | } 64 | 65 | // Configuration stores the config for the logger 66 | type Configuration struct { 67 | LogLevel string 68 | LogLocation string 69 | } 70 | 71 | // LoadLogConfig returns the log configuration 72 | func LoadLogConfig() *Configuration { 73 | return &Configuration{ 74 | LogLevel: GetLogLevel(), 75 | LogLocation: GetLogLocation(), 76 | } 77 | } 78 | 79 | // GetLogLocation returns the log file path 80 | func GetLogLocation() string { 81 | logFilePath := os.Getenv(envLogFilePath) 82 | if logFilePath == "" { 83 | logFilePath = defaultLogFilePath 84 | } 85 | return logFilePath 86 | } 87 | 88 | // GetLogLevel returns the log level 89 | func GetLogLevel() string { 90 | logLevel := os.Getenv(envLogLevel) 91 | switch logLevel { 92 | case "": 93 | logLevel = defaultLogLevel 94 | return logLevel 95 | default: 96 | return logLevel 97 | } 98 | } 99 | 100 | type structuredLogger struct { 101 | zapLogger *zap.SugaredLogger 102 | } 103 | 104 | // getZapLevel converts log level string to zapcore.Level 105 | func getZapLevel(inputLogLevel string) zapcore.Level { 106 | lvl := strings.ToLower(inputLogLevel) 107 | 108 | switch lvl { 109 | case "debug": 110 | return zapcore.DebugLevel 111 | case "info": 112 | return zapcore.InfoLevel 113 | case "warn": 114 | return zapcore.WarnLevel 115 | case "error": 116 | return zapcore.ErrorLevel 117 | case "fatal": 118 | return zapcore.FatalLevel 119 | default: 120 | return zapcore.DebugLevel 121 | } 122 | } 123 | 124 | func (logf *structuredLogger) Debugf(format string, args ...interface{}) { 125 | logf.zapLogger.Debugf(format, args...) 126 | } 127 | 128 | func (logf *structuredLogger) Debug(format string) { 129 | logf.zapLogger.Desugar().Debug(format) 130 | } 131 | 132 | func (logf *structuredLogger) Infof(format string, args ...interface{}) { 133 | logf.zapLogger.Infof(format, args...) 134 | } 135 | 136 | func (logf *structuredLogger) Info(format string) { 137 | logf.zapLogger.Desugar().Info(format) 138 | } 139 | 140 | func (logf *structuredLogger) Warnf(format string, args ...interface{}) { 141 | logf.zapLogger.Warnf(format, args...) 142 | } 143 | 144 | func (logf *structuredLogger) Warn(format string) { 145 | logf.zapLogger.Desugar().Warn(format) 146 | } 147 | 148 | func (logf *structuredLogger) Error(format string) { 149 | logf.zapLogger.Desugar().Error(format) 150 | } 151 | 152 | func (logf *structuredLogger) Errorf(format string, args ...interface{}) { 153 | logf.zapLogger.Errorf(format, args...) 154 | } 155 | 156 | func (logf *structuredLogger) Fatalf(format string, args ...interface{}) { 157 | logf.zapLogger.Fatalf(format, args...) 158 | } 159 | 160 | func (logf *structuredLogger) Panicf(format string, args ...interface{}) { 161 | logf.zapLogger.Fatalf(format, args...) 162 | } 163 | 164 | func (logf *structuredLogger) WithFields(fields Fields) Logger { 165 | var f = make([]interface{}, 0) 166 | for k, v := range fields { 167 | f = append(f, k) 168 | f = append(f, v) 169 | } 170 | newLogger := logf.zapLogger.With(f...) 171 | return &structuredLogger{newLogger} 172 | } 173 | 174 | func getEncoder() zapcore.Encoder { 175 | encoderConfig := zap.NewProductionEncoderConfig() 176 | encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 177 | return zapcore.NewJSONEncoder(encoderConfig) 178 | } 179 | 180 | func (logConfig *Configuration) newZapLogger() *structuredLogger { 181 | var cores []zapcore.Core 182 | 183 | logLevel := getZapLevel(logConfig.LogLevel) 184 | 185 | writer := getLogFilePath(logConfig.LogLocation) 186 | 187 | cores = append(cores, zapcore.NewCore(getEncoder(), writer, logLevel)) 188 | 189 | combinedCore := zapcore.NewTee(cores...) 190 | 191 | logger := zap.New(combinedCore, 192 | zap.AddCaller(), 193 | zap.AddCallerSkip(2), 194 | ) 195 | defer logger.Sync() 196 | sugar := logger.Sugar() 197 | 198 | return &structuredLogger{ 199 | zapLogger: sugar, 200 | } 201 | } 202 | 203 | // getLogFilePath returns the writer 204 | func getLogFilePath(logFilePath string) zapcore.WriteSyncer { 205 | var writer zapcore.WriteSyncer 206 | 207 | if logFilePath == "" { 208 | writer = zapcore.Lock(os.Stderr) 209 | } else if strings.ToLower(logFilePath) != "stdout" { 210 | writer = getLogWriter(logFilePath) 211 | } else { 212 | writer = zapcore.Lock(os.Stdout) 213 | } 214 | 215 | return writer 216 | } 217 | 218 | // getLogWriter is for lumberjack 219 | func getLogWriter(logFilePath string) zapcore.WriteSyncer { 220 | lumberJackLogger := &lumberjack.Logger{ 221 | Filename: logFilePath, 222 | MaxSize: 100, 223 | MaxBackups: 5, 224 | MaxAge: 30, 225 | Compress: true, 226 | } 227 | return zapcore.AddSync(lumberJackLogger) 228 | } 229 | 230 | // DefaultLogger creates and returns a new default logger. 231 | func DefaultLogger() Logger { 232 | productionConfig := zap.NewProductionConfig() 233 | productionConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 234 | productionConfig.EncoderConfig.EncodeCaller = func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) { 235 | _, caller.File, caller.Line, _ = runtime.Caller(8) 236 | enc.AppendString(caller.FullPath()) 237 | } 238 | logger, _ := productionConfig.Build() 239 | defer logger.Sync() 240 | sugar := logger.Sugar() 241 | return &structuredLogger{ 242 | zapLogger: sugar, 243 | } 244 | } 245 | 246 | // Get returns an default instance of the zap logger 247 | func Get() Logger { 248 | if log == nil { 249 | logConfig := LoadLogConfig() 250 | log = New(logConfig) 251 | log.Info("Initialized new logger as an existing instance was not found") 252 | } 253 | return log 254 | } 255 | 256 | // New logger initializes logger 257 | func New(inputLogConfig *Configuration) Logger { 258 | log = inputLogConfig.newZapLogger() 259 | log.Info("Constructed new logger instance") 260 | return log 261 | } 262 | -------------------------------------------------------------------------------- /pkg/logger/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package logger 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "go.uber.org/zap/zapcore" 23 | "gopkg.in/natefinch/lumberjack.v2" 24 | ) 25 | 26 | func TestEnvLogFilePath(t *testing.T) { 27 | path := "/var/log/test.log" 28 | _ = os.Setenv(envLogFilePath, path) 29 | defer os.Unsetenv(envLogFilePath) 30 | 31 | assert.Equal(t, path, GetLogLocation()) 32 | } 33 | 34 | func TestLoggerGetSameInstance(t *testing.T) { 35 | log1 := Get() 36 | log2 := Get() 37 | 38 | assert.True(t, log1 == log2) 39 | } 40 | 41 | func TestLoggerNewAndGetSameInstance(t *testing.T) { 42 | logConfig := LoadLogConfig() 43 | log1 := New(logConfig) 44 | log2 := Get() 45 | 46 | assert.True(t, log1 == log2) 47 | } 48 | 49 | func TestGetLogFileLocationReturnsDefaultPath(t *testing.T) { 50 | defaultPath := "/var/log/aws-routed-eni/ebpf-sdk.log" 51 | assert.Equal(t, defaultPath, GetLogLocation()) 52 | } 53 | 54 | func TestLogLevelReturnsOverriddenLevel(t *testing.T) { 55 | _ = os.Setenv(envLogLevel, "INFO") 56 | defer os.Unsetenv(envLogLevel) 57 | 58 | expectedLogLevel := zapcore.InfoLevel 59 | inputLogLevel := GetLogLevel() 60 | assert.Equal(t, expectedLogLevel, getZapLevel(inputLogLevel)) 61 | } 62 | 63 | func TestLogLevelReturnsDefaultLevelWhenEnvNotSet(t *testing.T) { 64 | expectedLogLevel := zapcore.DebugLevel 65 | inputLogLevel := GetLogLevel() 66 | assert.Equal(t, expectedLogLevel, getZapLevel(inputLogLevel)) 67 | } 68 | 69 | func TestLogLevelReturnsDefaultLevelWhenEnvSetToInvalidValue(t *testing.T) { 70 | _ = os.Setenv(envLogLevel, "EVERYTHING") 71 | defer os.Unsetenv(envLogLevel) 72 | 73 | var expectedLogLevel zapcore.Level 74 | inputLogLevel := GetLogLevel() 75 | expectedLogLevel = zapcore.DebugLevel 76 | assert.Equal(t, expectedLogLevel, getZapLevel(inputLogLevel)) 77 | } 78 | 79 | func TestGetLogFilePathEmpty(t *testing.T) { 80 | expectedWriter := zapcore.Lock(os.Stderr) 81 | inputSDKLogFile := "" 82 | assert.Equal(t, expectedWriter, getLogFilePath(inputSDKLogFile)) 83 | } 84 | 85 | func TestGetLogFilePathStdout(t *testing.T) { 86 | expectedWriter := zapcore.Lock(os.Stdout) 87 | inputSDKLogFile := "stdout" 88 | assert.Equal(t, expectedWriter, getLogFilePath(inputSDKLogFile)) 89 | } 90 | 91 | func TestGetLogFilePath(t *testing.T) { 92 | inputSDKLogFile := "/var/log/aws-routed-eni/ebpf-sdk.log" 93 | expectedLumberJackLogger := &lumberjack.Logger{ 94 | Filename: "/var/log/aws-routed-eni/ebpf-sdk.log", 95 | MaxSize: 100, 96 | MaxBackups: 5, 97 | MaxAge: 30, 98 | Compress: true, 99 | } 100 | assert.Equal(t, zapcore.AddSync(expectedLumberJackLogger), getLogFilePath(inputSDKLogFile)) 101 | } 102 | -------------------------------------------------------------------------------- /pkg/maps/generate_mocks.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | package maps 14 | 15 | //go:generate go run github.com/golang/mock/mockgen -destination mocks/ebpf_mocks.go . BpfMapAPIs 16 | -------------------------------------------------------------------------------- /pkg/maps/loader.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package maps 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "os" 21 | "path/filepath" 22 | "runtime" 23 | "unsafe" 24 | 25 | constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" 26 | "github.com/aws/aws-ebpf-sdk-go/pkg/logger" 27 | "github.com/aws/aws-ebpf-sdk-go/pkg/utils" 28 | "golang.org/x/sys/unix" 29 | ) 30 | 31 | var log = logger.Get() 32 | 33 | type BpfMap struct { 34 | MapFD uint32 35 | MapID uint32 36 | MapMetaData CreateEBPFMapInput 37 | } 38 | 39 | type BpfMapPinOptions struct { 40 | Type uint32 41 | PinPath string 42 | } 43 | 44 | type CreateEBPFMapInput struct { 45 | Type uint32 46 | KeySize uint32 47 | ValueSize uint32 48 | MaxEntries uint32 49 | Flags uint32 50 | InnerMapFd uint32 51 | PinOptions *BpfMapPinOptions 52 | NumaNode uint32 53 | Name string 54 | } 55 | 56 | /* 57 | * BpfMapDef is filled based on map parsing from ELF 58 | * Type : Map Type such as Trie, Array.. 59 | * Key size and Value size : Size of map key and value 60 | * MaxEntries : size of map 61 | * Flags : for instance if the map has to be preallocated 62 | * InnerMapFd: Map in map functionality 63 | * Pinning: if pinning is needed or not 64 | */ 65 | 66 | type BpfMapDef struct { 67 | Type uint32 68 | KeySize uint32 69 | ValueSize uint32 70 | MaxEntries uint32 71 | Flags uint32 72 | InnerMapFd uint32 73 | Pinning uint32 74 | } 75 | 76 | type bpfAttrBPFMapCreate struct { 77 | Def BpfMapDef 78 | NumaNode uint32 79 | Name string 80 | } 81 | 82 | type BpfMapInfo struct { 83 | Type uint32 84 | Id uint32 85 | KeySize uint32 86 | ValueSize uint32 87 | MaxEntries uint32 88 | MapFlags uint32 89 | Name [constdef.BPFObjNameLen]byte 90 | IfIndex uint32 91 | BtfVmLinuxValueTypeId uint32 92 | NetnsDev uint64 93 | NetnsIno uint64 94 | BTFID uint32 95 | BTFKeyTypeID uint32 96 | BTFValueTypeId uint32 97 | Pad uint32 98 | MapExtra uint64 99 | } 100 | 101 | /* 102 | * 103 | * struct { anonymous struct used by BPF_*_GET_*_ID 104 | * union { 105 | * __u32 start_id; 106 | * __u32 prog_id; 107 | * __u32 map_id; 108 | * __u32 btf_id; 109 | * __u32 link_id; 110 | * }; 111 | * __u32 next_id; 112 | * __u32 open_flags; 113 | * }; 114 | */ 115 | 116 | type BpfMapShowAttr struct { 117 | MapID uint32 118 | NextID uint32 119 | OpenFlags uint32 120 | } 121 | 122 | /* 123 | * struct { anonymous struct used by BPF_OBJ_GET_INFO_BY_FD 124 | * __u32 bpf_fd; 125 | * __u32 info_len; 126 | * __aligned_u64 info; 127 | * } info; 128 | * 129 | */ 130 | type BpfObjGetInfo struct { 131 | bpf_fd uint32 132 | info_len uint32 133 | info uintptr 134 | } 135 | 136 | /* 137 | * struct { anonymous struct used by BPF_OBJ_* commands 138 | * __aligned_u64 pathname; 139 | * __u32 bpf_fd; 140 | * __u32 file_flags; 141 | * }; 142 | */ 143 | type BpfObjGet struct { 144 | pathname uintptr 145 | bpf_fd uint32 146 | file_flags uint32 147 | } 148 | 149 | type BpfMapAPIs interface { 150 | // Wrapper for BPF_MAP_CREATE API 151 | CreateBPFMap(mapMetaData CreateEBPFMapInput) (BpfMap, error) 152 | // Pin map to the passed pinPath 153 | PinMap(pinPath string, pinType uint32) error 154 | // Delete pinPath 155 | UnPinMap(pinPath string) error 156 | // Add an entry to map, if the entry exists, we error out 157 | CreateMapEntry(key, value uintptr) error 158 | // Update an entry in map, if the entry exists, we update 159 | UpdateMapEntry(key, value uintptr) error 160 | // Wrapper to call create or update 161 | CreateUpdateMapEntry(key, value uintptr, updateFlags uint64) error 162 | // Delete a map entry 163 | DeleteMapEntry(key uintptr) error 164 | // Map iterator helper - Get the first map entry 165 | GetFirstMapEntry(nextKey uintptr) error 166 | // Map iterator helper - Get next map entry 167 | GetNextMapEntry(key, nextKey uintptr) error 168 | // Get map value 169 | GetMapEntry(key, value uintptr) error 170 | // Update multiple map entries 171 | BulkUpdateMapEntry(keyvalue map[string][]byte) error 172 | // Delete multiple map entries 173 | BulkDeleteMapEntry(keyvalue map[uintptr]uintptr) error 174 | // Wrapper for delete and update map entries 175 | BulkRefreshMapEntries(newMapContents map[string][]byte) error 176 | // Retrieve map info from pin path 177 | GetMapFromPinPath(pinPath string) (BpfMapInfo, error) 178 | // Retrieve map info without pin path 179 | GetBPFmapInfo(mapFD uint32) (BpfMapInfo, error) 180 | } 181 | 182 | func (m *BpfMap) CreateBPFMap(MapMetaData CreateEBPFMapInput) (BpfMap, error) { 183 | 184 | // Copying all contents, innerMapFD is 0 since we don't support map-in-map 185 | mapContents := bpfAttrBPFMapCreate{ 186 | Def: BpfMapDef{ 187 | Type: uint32(MapMetaData.Type), 188 | KeySize: MapMetaData.KeySize, 189 | ValueSize: MapMetaData.ValueSize, 190 | MaxEntries: MapMetaData.MaxEntries, 191 | Flags: MapMetaData.Flags, 192 | InnerMapFd: 0, 193 | }, 194 | Name: MapMetaData.Name, 195 | } 196 | mapData := unsafe.Pointer(&mapContents) 197 | mapDataSize := unsafe.Sizeof(mapContents) 198 | 199 | log.Infof("Calling BPFsys for name %s mapType %d keysize %d valuesize %d max entries %d and flags %d", string(MapMetaData.Name[:]), MapMetaData.Type, MapMetaData.KeySize, MapMetaData.ValueSize, MapMetaData.MaxEntries, MapMetaData.Flags) 200 | 201 | ret, _, errno := unix.Syscall( 202 | unix.SYS_BPF, 203 | uintptr(constdef.BPF_MAP_CREATE), 204 | uintptr(mapData), 205 | mapDataSize, 206 | ) 207 | 208 | if (errno < 0) || (int(ret) == -1) { 209 | log.Errorf("Unable to create map and ret %d and err %s", int(ret), errno) 210 | return BpfMap{}, fmt.Errorf("unable to create map: %s", errno) 211 | } 212 | 213 | log.Infof("Create map done with fd : %d", int(ret)) 214 | 215 | bpfMap := BpfMap{ 216 | MapFD: uint32(ret), 217 | MapMetaData: MapMetaData, 218 | } 219 | 220 | if MapMetaData.PinOptions != nil { 221 | bpfMap.PinMap(MapMetaData.PinOptions.PinPath, MapMetaData.PinOptions.Type) 222 | } 223 | return bpfMap, nil 224 | } 225 | 226 | func (m *BpfMap) PinMap(pinPath string, pinType uint32) error { 227 | if pinType == constdef.PIN_NONE.Index() { 228 | return nil 229 | } 230 | 231 | if pinType == constdef.PIN_GLOBAL_NS.Index() { 232 | 233 | //If pinPath is already present lets delete and create a new one 234 | found, err := utils.IsfileExists(pinPath) 235 | if err != nil { 236 | return fmt.Errorf("unable to check file: %w", err) 237 | } 238 | if found { 239 | log.Infof("Found file %s so deleting the path", pinPath) 240 | err := utils.UnPinObject(pinPath) 241 | if err != nil { 242 | log.Errorf("failed to UnPinObject %v", err) 243 | return err 244 | } 245 | } 246 | err = os.MkdirAll(filepath.Dir(pinPath), 0755) 247 | if err != nil { 248 | log.Errorf("error creating directory %s: %v", filepath.Dir(pinPath), err) 249 | return fmt.Errorf("error creating directory %s: %v", filepath.Dir(pinPath), err) 250 | } 251 | _, err = os.Stat(pinPath) 252 | if err == nil { 253 | log.Errorf("aborting, found file at %s", pinPath) 254 | return fmt.Errorf("aborting, found file at %s", pinPath) 255 | } 256 | if err != nil && !os.IsNotExist(err) { 257 | log.Errorf("failed to stat %s: %v", pinPath, err) 258 | return fmt.Errorf("failed to stat %s: %v", pinPath, err) 259 | } 260 | 261 | return utils.PinObject(m.MapFD, pinPath) 262 | 263 | } 264 | return nil 265 | 266 | } 267 | 268 | func (m *BpfMap) UnPinMap(pinPath string) error { 269 | err := utils.UnPinObject(pinPath) 270 | if err != nil { 271 | log.Errorf("failed to unpin map") 272 | return err 273 | } 274 | if m.MapFD <= 0 { 275 | log.Errorf("map FD is invalid or closed %d", m.MapFD) 276 | return nil 277 | } 278 | return unix.Close(int(m.MapFD)) 279 | } 280 | 281 | func (m *BpfMap) CreateMapEntry(key, value uintptr) error { 282 | return m.CreateUpdateMapEntry(key, value, uint64(constdef.BPF_NOEXIST)) 283 | } 284 | 285 | func (m *BpfMap) UpdateMapEntry(key, value uintptr) error { 286 | return m.CreateUpdateMapEntry(key, value, uint64(constdef.BPF_ANY)) 287 | } 288 | 289 | func (m *BpfMap) CreateUpdateMapEntry(key, value uintptr, updateFlags uint64) error { 290 | 291 | mapFD, err := utils.GetMapFDFromID(int(m.MapID)) 292 | if err != nil { 293 | log.Errorf("unable to GetMapFDfromID and ret %d and err %s", int(mapFD), err) 294 | return fmt.Errorf("unable to get FD: %s", err) 295 | } 296 | defer unix.Close(mapFD) 297 | 298 | attr := utils.BpfMapAttr{ 299 | MapFD: uint32(mapFD), 300 | Flags: updateFlags, 301 | Key: uint64(key), 302 | Value: uint64(value), 303 | } 304 | ret, _, errno := unix.Syscall( 305 | unix.SYS_BPF, 306 | uintptr(constdef.BPF_MAP_UPDATE_ELEM), 307 | uintptr(unsafe.Pointer(&attr)), 308 | unsafe.Sizeof(attr), 309 | ) 310 | runtime.KeepAlive(key) 311 | runtime.KeepAlive(value) 312 | 313 | if errno != 0 { 314 | log.Errorf("unable to create/update map entry and ret %d and err %s", int(ret), errno) 315 | return fmt.Errorf("unable to update map: %s", errno) 316 | } 317 | 318 | log.Infof("Create/Update map entry done with fd : %d and err %s", int(ret), errno) 319 | return nil 320 | } 321 | 322 | func (m *BpfMap) DeleteMapEntry(key uintptr) error { 323 | 324 | mapFD, err := utils.GetMapFDFromID(int(m.MapID)) 325 | if err != nil { 326 | log.Errorf("unable to GetMapFDfromID and ID %d and err %s", int(m.MapID), err) 327 | return fmt.Errorf("unable to get FD: %s", err) 328 | } 329 | defer unix.Close(mapFD) 330 | 331 | attr := utils.BpfMapAttr{ 332 | MapFD: uint32(mapFD), 333 | Key: uint64(key), 334 | } 335 | ret, _, errno := unix.Syscall( 336 | unix.SYS_BPF, 337 | uintptr(constdef.BPF_MAP_DELETE_ELEM), 338 | uintptr(unsafe.Pointer(&attr)), 339 | unsafe.Sizeof(attr), 340 | ) 341 | if errno != 0 { 342 | log.Errorf("unable to delete map entry and ret %d and err %s", int(ret), errno) 343 | return fmt.Errorf("unable to update map: %s", errno) 344 | } 345 | 346 | log.Infof("Delete map entry done with fd : %d and err %s", int(ret), errno) 347 | return nil 348 | } 349 | 350 | // To get the first entry pass key as `nil` 351 | func (m *BpfMap) GetFirstMapEntry(nextKey uintptr) error { 352 | return m.GetNextMapEntry(uintptr(unsafe.Pointer(nil)), nextKey) 353 | } 354 | 355 | func (m *BpfMap) GetNextMapEntry(key, nextKey uintptr) error { 356 | 357 | mapFD, err := utils.GetMapFDFromID(int(m.MapID)) 358 | if err != nil { 359 | log.Errorf("unable to GetMapFDfromID and ret %d and err %s", int(mapFD), err) 360 | return fmt.Errorf("unable to get FD: %s", err) 361 | } 362 | defer unix.Close(mapFD) 363 | 364 | attr := utils.BpfMapAttr{ 365 | MapFD: uint32(mapFD), 366 | Key: uint64(key), 367 | Value: uint64(nextKey), 368 | } 369 | ret, _, errno := unix.Syscall( 370 | unix.SYS_BPF, 371 | uintptr(constdef.BPF_MAP_GET_NEXT_KEY), 372 | uintptr(unsafe.Pointer(&attr)), 373 | unsafe.Sizeof(attr), 374 | ) 375 | if errors.Is(errno, unix.ENOENT) { 376 | log.Errorf("last entry read done") 377 | return errno 378 | } 379 | if errno != 0 { 380 | log.Errorf("unable to get next map entry and ret %d and err %s", int(ret), errno) 381 | return fmt.Errorf("unable to get next map entry: %s", errno) 382 | } 383 | 384 | log.Infof("Got next map entry with fd : %d and err %s", int(ret), errno) 385 | return nil 386 | } 387 | 388 | func (m *BpfMap) GetAllMapKeys() ([]string, error) { 389 | var keyList []string 390 | keySize := m.MapMetaData.KeySize 391 | 392 | curKey := make([]byte, keySize) 393 | nextKey := make([]byte, keySize) 394 | 395 | err := m.GetFirstMapEntry(uintptr(unsafe.Pointer(&curKey[0]))) 396 | if err != nil { 397 | log.Errorf("unable to get first key %s", err) 398 | return nil, fmt.Errorf("unable to get first key entry: %s", err) 399 | } else { 400 | for { 401 | err = m.GetNextMapEntry(uintptr(unsafe.Pointer(&curKey[0])), uintptr(unsafe.Pointer(&nextKey[0]))) 402 | log.Infof("Adding to key list %v", curKey) 403 | keyList = append(keyList, string(curKey)) 404 | if errors.Is(err, unix.ENOENT) { 405 | log.Infof("Done reading all entries") 406 | return keyList, nil 407 | } 408 | if err != nil { 409 | log.Infof("Unable to get next key %s", err) 410 | break 411 | } 412 | //curKey = nextKey 413 | copy(curKey, nextKey) 414 | } 415 | } 416 | log.Infof("Done get all keys") 417 | return keyList, err 418 | } 419 | 420 | func (m *BpfMap) GetMapEntry(key, value uintptr) error { 421 | 422 | mapFD, err := utils.GetMapFDFromID(int(m.MapID)) 423 | if err != nil { 424 | log.Errorf("unable to GetMapFDfromID and ret %d and err %s", int(mapFD), err) 425 | return fmt.Errorf("unable to get FD: %s", err) 426 | } 427 | defer unix.Close(mapFD) 428 | 429 | attr := utils.BpfMapAttr{ 430 | MapFD: uint32(mapFD), 431 | Key: uint64(key), 432 | Value: uint64(value), 433 | } 434 | ret, _, errno := unix.Syscall( 435 | unix.SYS_BPF, 436 | uintptr(constdef.BPF_MAP_LOOKUP_ELEM), 437 | uintptr(unsafe.Pointer(&attr)), 438 | unsafe.Sizeof(attr), 439 | ) 440 | if errno != 0 { 441 | log.Errorf("unable to get map entry and ret %d and err %s", int(ret), errno) 442 | return fmt.Errorf("unable to get next map entry: %s", errno) 443 | } 444 | 445 | log.Infof("Got map entry with fd : %d and err %s", int(ret), errno) 446 | return nil 447 | } 448 | 449 | func (m *BpfMap) BulkDeleteMapEntry(keyvalue map[uintptr]uintptr) error { 450 | for k, _ := range keyvalue { 451 | err := m.DeleteMapEntry(k) 452 | if err != nil { 453 | log.Infof("One of the element delete failed hence returning from bulk update") 454 | return err 455 | } 456 | } 457 | log.Infof("Bulk delete is successful for mapID: %d", int(m.MapID)) 458 | return nil 459 | } 460 | 461 | func (m *BpfMap) BulkUpdateMapEntry(keyvalue map[string][]byte) error { 462 | for k, v := range keyvalue { 463 | keyByte := []byte(k) 464 | keyPtr := uintptr(unsafe.Pointer(&keyByte[0])) 465 | valuePtr := uintptr(unsafe.Pointer(&v[0])) 466 | err := m.UpdateMapEntry(keyPtr, valuePtr) 467 | if err != nil { 468 | log.Infof("One of the element update failed hence returning from bulk update") 469 | return err 470 | } 471 | } 472 | log.Infof("Bulk update is successful for mapID: %d", int(m.MapID)) 473 | return nil 474 | } 475 | 476 | func (m *BpfMap) BulkRefreshMapEntries(newMapContents map[string][]byte) error { 477 | 478 | // 1. Update all map entries 479 | err := m.BulkUpdateMapEntry(newMapContents) 480 | if err != nil { 481 | log.Errorf("refresh map failed: during update %v", err) 482 | return err 483 | } 484 | 485 | // 2. Read all map entries 486 | retrievedMapKeyList, err := m.GetAllMapKeys() 487 | if err != nil { 488 | log.Errorf("get all map keys failed: during Refresh %v", err) 489 | return err 490 | } 491 | 492 | // 3. Delete stale Keys 493 | log.Infof("Check for stale entries and got %d entries from BPF map", len(retrievedMapKeyList)) 494 | for _, key := range retrievedMapKeyList { 495 | log.Infof("Checking if key %s is deletable", key) 496 | if _, ok := newMapContents[key]; !ok { 497 | log.Infof("This can be deleted, not needed anymore...") 498 | deletableKeyByte := []byte(key) 499 | deletableKeyBytePtr := uintptr(unsafe.Pointer(&deletableKeyByte[0])) 500 | err = m.DeleteMapEntry(deletableKeyBytePtr) 501 | if err != nil { 502 | log.Infof("Unable to delete entry %s but will continue and err %v", key, err) 503 | } 504 | } 505 | } 506 | return nil 507 | } 508 | 509 | func (attr *BpfMapShowAttr) isBpfMapGetNextID() bool { 510 | ret, _, errno := unix.Syscall( 511 | unix.SYS_BPF, 512 | uintptr(constdef.BPF_MAP_GET_NEXT_ID), 513 | uintptr(unsafe.Pointer(attr)), 514 | unsafe.Sizeof(*attr), 515 | ) 516 | if errno != 0 { 517 | log.Infof("Done get_next_id for Map - ret %d and err %s", int(ret), errno) 518 | return false 519 | } 520 | 521 | attr.MapID = attr.NextID 522 | return true 523 | } 524 | 525 | func (objattr *BpfObjGetInfo) BpfGetMapInfoForFD() error { 526 | ret, _, errno := unix.Syscall( 527 | unix.SYS_BPF, 528 | uintptr(constdef.BPF_OBJ_GET_INFO_BY_FD), 529 | uintptr(unsafe.Pointer(objattr)), 530 | unsafe.Sizeof(*objattr), 531 | ) 532 | if errno != 0 { 533 | log.Errorf("failed to get object info by FD - ret %d and err %s", int(ret), errno) 534 | return errno 535 | } 536 | return nil 537 | } 538 | 539 | func GetIDFromFD(mapFD int) (int, error) { 540 | mapInfo, err := GetBPFmapInfo(mapFD) 541 | if err != nil { 542 | return -1, err 543 | } 544 | return int(mapInfo.Id), nil 545 | } 546 | 547 | func (m *BpfMap) GetBPFmapInfo(mapFD uint32) (BpfMapInfo, error) { 548 | return GetBPFmapInfo(int(mapFD)) 549 | } 550 | 551 | func GetBPFmapInfo(mapFD int) (BpfMapInfo, error) { 552 | var bpfMapInfo BpfMapInfo 553 | objInfo := BpfObjGetInfo{ 554 | bpf_fd: uint32(mapFD), 555 | info_len: uint32(unsafe.Sizeof(bpfMapInfo)), 556 | info: uintptr(unsafe.Pointer(&bpfMapInfo)), 557 | } 558 | 559 | err := objInfo.BpfGetMapInfoForFD() 560 | if err != nil { 561 | log.Errorf("failed to get map Info for FD - ", mapFD) 562 | return BpfMapInfo{}, err 563 | } 564 | 565 | return bpfMapInfo, nil 566 | } 567 | 568 | func BpfGetAllMapInfo() ([]BpfMapInfo, error) { 569 | loadedMaps := []BpfMapInfo{} 570 | attr := BpfMapShowAttr{} 571 | log.Infof("In get all prog info") 572 | for attr.isBpfMapGetNextID() { 573 | log.Infof("Got ID - %d", attr.NextID) 574 | 575 | mapfd, err := utils.GetMapFDFromID(int(attr.NextID)) 576 | if err != nil { 577 | log.Errorf("failed to get map Info") 578 | return nil, err 579 | } 580 | log.Infof("Found map FD - %d", mapfd) 581 | bpfMapInfo, err := GetBPFmapInfo(mapfd) 582 | if err != nil { 583 | log.Errorf("failed to get map Info for FD", mapfd) 584 | unix.Close(mapfd) 585 | return nil, err 586 | } 587 | unix.Close(mapfd) 588 | 589 | loadedMaps = append(loadedMaps, bpfMapInfo) 590 | } 591 | log.Infof("Done all map info!!!") 592 | return loadedMaps, nil 593 | } 594 | 595 | func (attr *BpfObjGet) BpfGetObject() (int, error) { 596 | ret, _, errno := unix.Syscall( 597 | unix.SYS_BPF, 598 | uintptr(constdef.BPF_OBJ_GET), 599 | uintptr(unsafe.Pointer(attr)), 600 | unsafe.Sizeof(*attr), 601 | ) 602 | if errno != 0 { 603 | log.Errorf("failed to get Map FD - ret %d and err %s", int(ret), errno) 604 | return 0, errno 605 | } 606 | return int(ret), nil 607 | } 608 | 609 | func (m *BpfMap) GetMapFromPinPath(pinPath string) (BpfMapInfo, error) { 610 | if len(pinPath) == 0 { 611 | return BpfMapInfo{}, fmt.Errorf("invalid pinPath") 612 | } 613 | 614 | cPath := []byte(pinPath + "\x00") 615 | objInfo := BpfObjGet{ 616 | pathname: uintptr(unsafe.Pointer(&cPath[0])), 617 | } 618 | 619 | mapFD, err := objInfo.BpfGetObject() 620 | if err != nil { 621 | log.Errorf("failed to get object") 622 | return BpfMapInfo{}, err 623 | 624 | } 625 | 626 | bpfMapInfo, err := GetBPFmapInfo(mapFD) 627 | if err != nil { 628 | log.Errorf("failed to get map Info for FD - %d", mapFD) 629 | return bpfMapInfo, err 630 | } 631 | err = unix.Close(int(mapFD)) 632 | if err != nil { 633 | log.Infof("Failed to close but return the mapinfo") 634 | } 635 | 636 | return bpfMapInfo, nil 637 | } 638 | 639 | func GetFirstMapEntryByID(nextKey uintptr, mapID int) error { 640 | return GetNextMapEntryByID(uintptr(unsafe.Pointer(nil)), nextKey, mapID) 641 | } 642 | 643 | func GetNextMapEntryByID(key, nextKey uintptr, mapID int) error { 644 | 645 | mapFD, err := utils.GetMapFDFromID(mapID) 646 | if err != nil { 647 | log.Errorf("unable to GetMapFDfromID and ret %d and err %s", int(mapFD), err) 648 | return fmt.Errorf("unable to get FD: %s", err) 649 | } 650 | 651 | defer unix.Close(mapFD) 652 | attr := utils.BpfMapAttr{ 653 | MapFD: uint32(mapFD), 654 | Key: uint64(key), 655 | Value: uint64(nextKey), 656 | } 657 | ret, _, errno := unix.Syscall( 658 | unix.SYS_BPF, 659 | uintptr(constdef.BPF_MAP_GET_NEXT_KEY), 660 | uintptr(unsafe.Pointer(&attr)), 661 | unsafe.Sizeof(attr), 662 | ) 663 | if errors.Is(errno, unix.ENOENT) { 664 | return errno 665 | } 666 | if errno != 0 { 667 | log.Errorf("unable to get next map entry and ret %d and err %s", int(ret), errno) 668 | return fmt.Errorf("unable to get next map entry: %s", errno) 669 | } 670 | 671 | log.Infof("Got next map entry with fd : %d and err %s", int(ret), errno) 672 | return nil 673 | } 674 | 675 | func GetMapEntryByID(key, value uintptr, mapID int) error { 676 | 677 | mapFD, err := utils.GetMapFDFromID(mapID) 678 | if err != nil { 679 | log.Errorf("unable to GetMapFDfromID and ret %d and err %s", int(mapFD), err) 680 | return fmt.Errorf("unable to get FD: %s", err) 681 | } 682 | defer unix.Close(mapFD) 683 | 684 | attr := utils.BpfMapAttr{ 685 | MapFD: uint32(mapFD), 686 | Key: uint64(key), 687 | Value: uint64(value), 688 | } 689 | ret, _, errno := unix.Syscall( 690 | unix.SYS_BPF, 691 | uintptr(constdef.BPF_MAP_LOOKUP_ELEM), 692 | uintptr(unsafe.Pointer(&attr)), 693 | unsafe.Sizeof(attr), 694 | ) 695 | 696 | if errno != 0 { 697 | log.Errorf("unable to get map entry and ret %d and err %s", int(ret), errno) 698 | return fmt.Errorf("unable to get next map entry: %s", errno) 699 | } 700 | 701 | log.Infof("Got map entry with ret : %d and err %s", int(ret), errno) 702 | return nil 703 | } 704 | -------------------------------------------------------------------------------- /pkg/maps/mocks/ebpf_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/aws/aws-ebpf-sdk-go/pkg/maps (interfaces: BpfMapAPIs) 3 | 4 | // Package mock_maps is a generated GoMock package. 5 | package mock_maps 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | maps "github.com/aws/aws-ebpf-sdk-go/pkg/maps" 11 | gomock "github.com/golang/mock/gomock" 12 | ) 13 | 14 | // MockBpfMapAPIs is a mock of BpfMapAPIs interface. 15 | type MockBpfMapAPIs struct { 16 | ctrl *gomock.Controller 17 | recorder *MockBpfMapAPIsMockRecorder 18 | } 19 | 20 | // MockBpfMapAPIsMockRecorder is the mock recorder for MockBpfMapAPIs. 21 | type MockBpfMapAPIsMockRecorder struct { 22 | mock *MockBpfMapAPIs 23 | } 24 | 25 | // NewMockBpfMapAPIs creates a new mock instance. 26 | func NewMockBpfMapAPIs(ctrl *gomock.Controller) *MockBpfMapAPIs { 27 | mock := &MockBpfMapAPIs{ctrl: ctrl} 28 | mock.recorder = &MockBpfMapAPIsMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockBpfMapAPIs) EXPECT() *MockBpfMapAPIsMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // BulkDeleteMapEntry mocks base method. 38 | func (m *MockBpfMapAPIs) BulkDeleteMapEntry(arg0 map[uintptr]uintptr) error { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "BulkDeleteMapEntry", arg0) 41 | ret0, _ := ret[0].(error) 42 | return ret0 43 | } 44 | 45 | // BulkDeleteMapEntry indicates an expected call of BulkDeleteMapEntry. 46 | func (mr *MockBpfMapAPIsMockRecorder) BulkDeleteMapEntry(arg0 interface{}) *gomock.Call { 47 | mr.mock.ctrl.T.Helper() 48 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BulkDeleteMapEntry", reflect.TypeOf((*MockBpfMapAPIs)(nil).BulkDeleteMapEntry), arg0) 49 | } 50 | 51 | // BulkRefreshMapEntries mocks base method. 52 | func (m *MockBpfMapAPIs) BulkRefreshMapEntries(arg0 map[string][]byte) error { 53 | m.ctrl.T.Helper() 54 | ret := m.ctrl.Call(m, "BulkRefreshMapEntries", arg0) 55 | ret0, _ := ret[0].(error) 56 | return ret0 57 | } 58 | 59 | // BulkRefreshMapEntries indicates an expected call of BulkRefreshMapEntries. 60 | func (mr *MockBpfMapAPIsMockRecorder) BulkRefreshMapEntries(arg0 interface{}) *gomock.Call { 61 | mr.mock.ctrl.T.Helper() 62 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BulkRefreshMapEntries", reflect.TypeOf((*MockBpfMapAPIs)(nil).BulkRefreshMapEntries), arg0) 63 | } 64 | 65 | // BulkUpdateMapEntry mocks base method. 66 | func (m *MockBpfMapAPIs) BulkUpdateMapEntry(arg0 map[string][]byte) error { 67 | m.ctrl.T.Helper() 68 | ret := m.ctrl.Call(m, "BulkUpdateMapEntry", arg0) 69 | ret0, _ := ret[0].(error) 70 | return ret0 71 | } 72 | 73 | // BulkUpdateMapEntry indicates an expected call of BulkUpdateMapEntry. 74 | func (mr *MockBpfMapAPIsMockRecorder) BulkUpdateMapEntry(arg0 interface{}) *gomock.Call { 75 | mr.mock.ctrl.T.Helper() 76 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BulkUpdateMapEntry", reflect.TypeOf((*MockBpfMapAPIs)(nil).BulkUpdateMapEntry), arg0) 77 | } 78 | 79 | // CreateBPFMap mocks base method. 80 | func (m *MockBpfMapAPIs) CreateBPFMap(arg0 maps.CreateEBPFMapInput) (maps.BpfMap, error) { 81 | m.ctrl.T.Helper() 82 | ret := m.ctrl.Call(m, "CreateBPFMap", arg0) 83 | ret0, _ := ret[0].(maps.BpfMap) 84 | ret1, _ := ret[1].(error) 85 | return ret0, ret1 86 | } 87 | 88 | // CreateBPFMap indicates an expected call of CreateBPFMap. 89 | func (mr *MockBpfMapAPIsMockRecorder) CreateBPFMap(arg0 interface{}) *gomock.Call { 90 | mr.mock.ctrl.T.Helper() 91 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBPFMap", reflect.TypeOf((*MockBpfMapAPIs)(nil).CreateBPFMap), arg0) 92 | } 93 | 94 | // CreateMapEntry mocks base method. 95 | func (m *MockBpfMapAPIs) CreateMapEntry(arg0, arg1 uintptr) error { 96 | m.ctrl.T.Helper() 97 | ret := m.ctrl.Call(m, "CreateMapEntry", arg0, arg1) 98 | ret0, _ := ret[0].(error) 99 | return ret0 100 | } 101 | 102 | // CreateMapEntry indicates an expected call of CreateMapEntry. 103 | func (mr *MockBpfMapAPIsMockRecorder) CreateMapEntry(arg0, arg1 interface{}) *gomock.Call { 104 | mr.mock.ctrl.T.Helper() 105 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMapEntry", reflect.TypeOf((*MockBpfMapAPIs)(nil).CreateMapEntry), arg0, arg1) 106 | } 107 | 108 | // CreateUpdateMapEntry mocks base method. 109 | func (m *MockBpfMapAPIs) CreateUpdateMapEntry(arg0, arg1 uintptr, arg2 uint64) error { 110 | m.ctrl.T.Helper() 111 | ret := m.ctrl.Call(m, "CreateUpdateMapEntry", arg0, arg1, arg2) 112 | ret0, _ := ret[0].(error) 113 | return ret0 114 | } 115 | 116 | // CreateUpdateMapEntry indicates an expected call of CreateUpdateMapEntry. 117 | func (mr *MockBpfMapAPIsMockRecorder) CreateUpdateMapEntry(arg0, arg1, arg2 interface{}) *gomock.Call { 118 | mr.mock.ctrl.T.Helper() 119 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateUpdateMapEntry", reflect.TypeOf((*MockBpfMapAPIs)(nil).CreateUpdateMapEntry), arg0, arg1, arg2) 120 | } 121 | 122 | // DeleteMapEntry mocks base method. 123 | func (m *MockBpfMapAPIs) DeleteMapEntry(arg0 uintptr) error { 124 | m.ctrl.T.Helper() 125 | ret := m.ctrl.Call(m, "DeleteMapEntry", arg0) 126 | ret0, _ := ret[0].(error) 127 | return ret0 128 | } 129 | 130 | // DeleteMapEntry indicates an expected call of DeleteMapEntry. 131 | func (mr *MockBpfMapAPIsMockRecorder) DeleteMapEntry(arg0 interface{}) *gomock.Call { 132 | mr.mock.ctrl.T.Helper() 133 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMapEntry", reflect.TypeOf((*MockBpfMapAPIs)(nil).DeleteMapEntry), arg0) 134 | } 135 | 136 | // GetBPFmapInfo mocks base method. 137 | func (m *MockBpfMapAPIs) GetBPFmapInfo(arg0 uint32) (maps.BpfMapInfo, error) { 138 | m.ctrl.T.Helper() 139 | ret := m.ctrl.Call(m, "GetBPFmapInfo", arg0) 140 | ret0, _ := ret[0].(maps.BpfMapInfo) 141 | ret1, _ := ret[1].(error) 142 | return ret0, ret1 143 | } 144 | 145 | // GetBPFmapInfo indicates an expected call of GetBPFmapInfo. 146 | func (mr *MockBpfMapAPIsMockRecorder) GetBPFmapInfo(arg0 interface{}) *gomock.Call { 147 | mr.mock.ctrl.T.Helper() 148 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBPFmapInfo", reflect.TypeOf((*MockBpfMapAPIs)(nil).GetBPFmapInfo), arg0) 149 | } 150 | 151 | // GetFirstMapEntry mocks base method. 152 | func (m *MockBpfMapAPIs) GetFirstMapEntry(arg0 uintptr) error { 153 | m.ctrl.T.Helper() 154 | ret := m.ctrl.Call(m, "GetFirstMapEntry", arg0) 155 | ret0, _ := ret[0].(error) 156 | return ret0 157 | } 158 | 159 | // GetFirstMapEntry indicates an expected call of GetFirstMapEntry. 160 | func (mr *MockBpfMapAPIsMockRecorder) GetFirstMapEntry(arg0 interface{}) *gomock.Call { 161 | mr.mock.ctrl.T.Helper() 162 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFirstMapEntry", reflect.TypeOf((*MockBpfMapAPIs)(nil).GetFirstMapEntry), arg0) 163 | } 164 | 165 | // GetMapEntry mocks base method. 166 | func (m *MockBpfMapAPIs) GetMapEntry(arg0, arg1 uintptr) error { 167 | m.ctrl.T.Helper() 168 | ret := m.ctrl.Call(m, "GetMapEntry", arg0, arg1) 169 | ret0, _ := ret[0].(error) 170 | return ret0 171 | } 172 | 173 | // GetMapEntry indicates an expected call of GetMapEntry. 174 | func (mr *MockBpfMapAPIsMockRecorder) GetMapEntry(arg0, arg1 interface{}) *gomock.Call { 175 | mr.mock.ctrl.T.Helper() 176 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMapEntry", reflect.TypeOf((*MockBpfMapAPIs)(nil).GetMapEntry), arg0, arg1) 177 | } 178 | 179 | // GetMapFromPinPath mocks base method. 180 | func (m *MockBpfMapAPIs) GetMapFromPinPath(arg0 string) (maps.BpfMapInfo, error) { 181 | m.ctrl.T.Helper() 182 | ret := m.ctrl.Call(m, "GetMapFromPinPath", arg0) 183 | ret0, _ := ret[0].(maps.BpfMapInfo) 184 | ret1, _ := ret[1].(error) 185 | return ret0, ret1 186 | } 187 | 188 | // GetMapFromPinPath indicates an expected call of GetMapFromPinPath. 189 | func (mr *MockBpfMapAPIsMockRecorder) GetMapFromPinPath(arg0 interface{}) *gomock.Call { 190 | mr.mock.ctrl.T.Helper() 191 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMapFromPinPath", reflect.TypeOf((*MockBpfMapAPIs)(nil).GetMapFromPinPath), arg0) 192 | } 193 | 194 | // GetNextMapEntry mocks base method. 195 | func (m *MockBpfMapAPIs) GetNextMapEntry(arg0, arg1 uintptr) error { 196 | m.ctrl.T.Helper() 197 | ret := m.ctrl.Call(m, "GetNextMapEntry", arg0, arg1) 198 | ret0, _ := ret[0].(error) 199 | return ret0 200 | } 201 | 202 | // GetNextMapEntry indicates an expected call of GetNextMapEntry. 203 | func (mr *MockBpfMapAPIsMockRecorder) GetNextMapEntry(arg0, arg1 interface{}) *gomock.Call { 204 | mr.mock.ctrl.T.Helper() 205 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNextMapEntry", reflect.TypeOf((*MockBpfMapAPIs)(nil).GetNextMapEntry), arg0, arg1) 206 | } 207 | 208 | // PinMap mocks base method. 209 | func (m *MockBpfMapAPIs) PinMap(arg0 string, arg1 uint32) error { 210 | m.ctrl.T.Helper() 211 | ret := m.ctrl.Call(m, "PinMap", arg0, arg1) 212 | ret0, _ := ret[0].(error) 213 | return ret0 214 | } 215 | 216 | // PinMap indicates an expected call of PinMap. 217 | func (mr *MockBpfMapAPIsMockRecorder) PinMap(arg0, arg1 interface{}) *gomock.Call { 218 | mr.mock.ctrl.T.Helper() 219 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PinMap", reflect.TypeOf((*MockBpfMapAPIs)(nil).PinMap), arg0, arg1) 220 | } 221 | 222 | // UnPinMap mocks base method. 223 | func (m *MockBpfMapAPIs) UnPinMap(arg0 string) error { 224 | m.ctrl.T.Helper() 225 | ret := m.ctrl.Call(m, "UnPinMap", arg0) 226 | ret0, _ := ret[0].(error) 227 | return ret0 228 | } 229 | 230 | // UnPinMap indicates an expected call of UnPinMap. 231 | func (mr *MockBpfMapAPIsMockRecorder) UnPinMap(arg0 interface{}) *gomock.Call { 232 | mr.mock.ctrl.T.Helper() 233 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnPinMap", reflect.TypeOf((*MockBpfMapAPIs)(nil).UnPinMap), arg0) 234 | } 235 | 236 | // UpdateMapEntry mocks base method. 237 | func (m *MockBpfMapAPIs) UpdateMapEntry(arg0, arg1 uintptr) error { 238 | m.ctrl.T.Helper() 239 | ret := m.ctrl.Call(m, "UpdateMapEntry", arg0, arg1) 240 | ret0, _ := ret[0].(error) 241 | return ret0 242 | } 243 | 244 | // UpdateMapEntry indicates an expected call of UpdateMapEntry. 245 | func (mr *MockBpfMapAPIsMockRecorder) UpdateMapEntry(arg0, arg1 interface{}) *gomock.Call { 246 | mr.mock.ctrl.T.Helper() 247 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMapEntry", reflect.TypeOf((*MockBpfMapAPIs)(nil).UpdateMapEntry), arg0, arg1) 248 | } 249 | -------------------------------------------------------------------------------- /pkg/progs/generate_mocks.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | package progs 14 | 15 | //go:generate go run github.com/golang/mock/mockgen -destination mocks/ebpf_mocks.go . BpfProgAPIs 16 | -------------------------------------------------------------------------------- /pkg/progs/loader.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package progs 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "path/filepath" 21 | "runtime" 22 | "strings" 23 | "syscall" 24 | "unsafe" 25 | 26 | constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" 27 | "github.com/aws/aws-ebpf-sdk-go/pkg/logger" 28 | ebpf_maps "github.com/aws/aws-ebpf-sdk-go/pkg/maps" 29 | "github.com/aws/aws-ebpf-sdk-go/pkg/utils" 30 | "github.com/vishvananda/netlink" 31 | "golang.org/x/sys/unix" 32 | ) 33 | 34 | type BpfProgAPIs interface { 35 | PinProg(progFD uint32, pinPath string) error 36 | UnPinProg(pinPath string) error 37 | LoadProg(progMetaData CreateEBPFProgInput) (int, error) 38 | GetProgFromPinPath(pinPath string) (BpfProgInfo, int, error) 39 | GetBPFProgAssociatedMapsIDs(progFD int) ([]uint32, error) 40 | } 41 | 42 | var log = logger.Get() 43 | 44 | type CreateEBPFProgInput struct { 45 | ProgType string 46 | SubSystem string 47 | SubProgType string 48 | ProgData []byte 49 | LicenseStr string 50 | PinPath string 51 | InsDefSize int 52 | AssociatedMaps map[int]string 53 | } 54 | 55 | type BpfProgram struct { 56 | // return program name, prog FD and pinPath 57 | ProgID int 58 | ProgFD int 59 | PinPath string 60 | ProgType string 61 | SubSystem string 62 | SubProgType string 63 | } 64 | 65 | type BpfProgInfo struct { 66 | Type uint32 67 | ID uint32 68 | Tag [constdef.BPFTagSize]byte 69 | JitedProgLen uint32 70 | XlatedProgLen uint32 71 | JitedProgInsns uint64 72 | XlatedProgInsns uint64 73 | LoadTime int64 74 | CreatedByUID uint32 75 | NrMapIDs uint32 76 | MapIDs uint64 77 | Name [constdef.BPFObjNameLen]byte 78 | IfIndex uint32 79 | GPLCompatible uint32 `strcut:"bitfield"` 80 | Pad uint32 `strcut:"pad"` 81 | NetnsDev uint64 82 | NetnsIno uint64 83 | NrJitedKsyms uint32 84 | NrJitedFuncLens uint32 85 | JitedKsyms uint64 86 | JitedFuncLens uint64 87 | BTFID uint32 88 | FuncInfoRecSize uint32 89 | FuncInfo uint64 90 | NrFuncInfo uint32 91 | NrLineInfo uint32 92 | LineInfo uint64 93 | JitedLineInfo uint64 94 | NrJitedLineInfo uint32 95 | LineInfoRecSize uint32 96 | JitedLineInfoRecSize uint32 97 | NrProgTags uint32 98 | ProgTags uint64 99 | RunTimeNS uint64 100 | RunCnt uint64 101 | } 102 | 103 | type BpfProgAttr struct { 104 | prog_id uint32 105 | next_id uint32 106 | open_flags uint32 107 | } 108 | 109 | /* 110 | * struct { anonymous struct used by BPF_OBJ_GET_INFO_BY_FD 111 | * __u32 bpf_fd; 112 | * __u32 info_len; 113 | * __aligned_u64 info; 114 | * } info; 115 | * 116 | */ 117 | type BpfObjGetInfo struct { 118 | bpf_fd uint32 119 | info_len uint32 120 | info uintptr 121 | } 122 | 123 | /* 124 | * struct { anonymous struct used by BPF_OBJ_* commands 125 | * __aligned_u64 pathname; 126 | * __u32 bpf_fd; 127 | * __u32 file_flags; 128 | * }; 129 | */ 130 | type BpfObjGet struct { 131 | pathname uintptr 132 | bpf_fd uint32 133 | file_flags uint32 134 | } 135 | 136 | func MountBpfFS() error { 137 | log.Infof("Let's mount BPF FS") 138 | err := syscall.Mount("bpf", constdef.BPF_DIR_MNT, "bpf", 0, "mode=0700") 139 | if err != nil { 140 | log.Errorf("error mounting bpffs: %v", err) 141 | } 142 | return err 143 | } 144 | 145 | func (m *BpfProgram) PinProg(progFD uint32, pinPath string) error { 146 | found, err := utils.IsfileExists(pinPath) 147 | if err != nil { 148 | return fmt.Errorf("unable to check file: %w", err) 149 | } 150 | if found { 151 | log.Infof("Found file %s so deleting the path", pinPath) 152 | err = utils.UnPinObject(pinPath) 153 | if err != nil { 154 | log.Errorf("failed to UnPinObject during pinning") 155 | return err 156 | } 157 | } 158 | 159 | err = os.MkdirAll(filepath.Dir(pinPath), 0755) 160 | if err != nil { 161 | log.Infof("error creating directory %q: %v", filepath.Dir(pinPath), err) 162 | return fmt.Errorf("error creating directory %q: %v", filepath.Dir(pinPath), err) 163 | } 164 | _, err = os.Stat(pinPath) 165 | if err == nil { 166 | log.Infof("aborting, found file at %q", pinPath) 167 | return fmt.Errorf("aborting, found file at %q", pinPath) 168 | } 169 | if err != nil && !os.IsNotExist(err) { 170 | log.Infof("failed to stat %q: %v", pinPath, err) 171 | return fmt.Errorf("failed to stat %q: %v", pinPath, err) 172 | } 173 | 174 | return utils.PinObject(progFD, pinPath) 175 | } 176 | 177 | func (m *BpfProgram) UnPinProg(pinPath string) error { 178 | err := utils.UnPinObject(pinPath) 179 | if err != nil { 180 | log.Errorf("failed to unpin prog") 181 | return err 182 | } 183 | if m.ProgFD <= 0 { 184 | log.Errorf("map FD is invalid or closed %d", m.ProgFD) 185 | return nil 186 | } 187 | return unix.Close(int(m.ProgFD)) 188 | } 189 | 190 | func parseLogs(log []byte) []string { 191 | logStr := string(log) 192 | logs := strings.Split(logStr, "\n") 193 | return logs 194 | } 195 | 196 | func (m *BpfProgram) LoadProg(progMetaData CreateEBPFProgInput) (int, error) { 197 | 198 | var prog_type uint32 199 | switch progMetaData.ProgType { 200 | case "xdp": 201 | prog_type = uint32(netlink.BPF_PROG_TYPE_XDP) 202 | case "tc_cls": 203 | prog_type = uint32(netlink.BPF_PROG_TYPE_SCHED_CLS) 204 | case "tc_act": 205 | prog_type = uint32(netlink.BPF_PROG_TYPE_SCHED_ACT) 206 | case "kprobe": 207 | prog_type = uint32(netlink.BPF_PROG_TYPE_KPROBE) 208 | case "kretprobe": 209 | prog_type = uint32(netlink.BPF_PROG_TYPE_KPROBE) 210 | case "tracepoint": 211 | prog_type = uint32(netlink.BPF_PROG_TYPE_TRACEPOINT) 212 | default: 213 | prog_type = uint32(netlink.BPF_PROG_TYPE_UNSPEC) 214 | } 215 | 216 | logBuf := make([]byte, utils.GetLogBufferSize()) 217 | program := netlink.BPFAttr{ 218 | ProgType: prog_type, 219 | LogBuf: uintptr(unsafe.Pointer(&logBuf[0])), 220 | LogSize: uint32(cap(logBuf) - 1), 221 | LogLevel: 1, 222 | } 223 | 224 | program.Insns = uintptr(unsafe.Pointer(&progMetaData.ProgData[0])) 225 | program.InsnCnt = uint32(len(progMetaData.ProgData) / progMetaData.InsDefSize) 226 | 227 | license := []byte(progMetaData.LicenseStr) 228 | program.License = uintptr(unsafe.Pointer(&license[0])) 229 | 230 | fd, _, errno := unix.Syscall(unix.SYS_BPF, 231 | uintptr(constdef.BPF_PROG_LOAD), 232 | uintptr(unsafe.Pointer(&program)), 233 | unsafe.Sizeof(program)) 234 | runtime.KeepAlive(progMetaData.ProgData) 235 | runtime.KeepAlive(license) 236 | 237 | log.Infof("Load prog done with fd : %d", int(fd)) 238 | if errno != 0 { 239 | logArray := parseLogs(logBuf) 240 | for _, str := range logArray { 241 | fmt.Println(str) 242 | } 243 | return -1, errno 244 | } 245 | 246 | //Pin the prog 247 | err := m.PinProg(uint32(fd), progMetaData.PinPath) 248 | if err != nil { 249 | log.Errorf("pin prog failed %v", err) 250 | return -1, err 251 | } 252 | return int(fd), nil 253 | } 254 | 255 | func (attr *BpfProgAttr) isBpfProgGetNextID() bool { 256 | ret, _, errno := unix.Syscall( 257 | unix.SYS_BPF, 258 | uintptr(constdef.BPF_PROG_GET_NEXT_ID), 259 | uintptr(unsafe.Pointer(attr)), 260 | unsafe.Sizeof(*attr), 261 | ) 262 | if errno != 0 { 263 | log.Errorf("done get_next_id for Prog - ret %d and err %s", int(ret), errno) 264 | return false 265 | } 266 | 267 | attr.prog_id = attr.next_id 268 | return true 269 | } 270 | 271 | func (attr *BpfProgAttr) BpfProgGetFDbyID() (int, error) { 272 | ret, _, errno := unix.Syscall( 273 | unix.SYS_BPF, 274 | uintptr(constdef.BPF_PROG_GET_FD_BY_ID), 275 | uintptr(unsafe.Pointer(attr)), 276 | unsafe.Sizeof(*attr), 277 | ) 278 | if errno != 0 { 279 | log.Errorf("failed to get Prog FD - ret %d and err %s", int(ret), errno) 280 | return 0, errno 281 | } 282 | return int(ret), nil 283 | } 284 | 285 | func (objattr *BpfObjGetInfo) BpfGetProgramInfoForFD() error { 286 | ret, _, errno := unix.Syscall( 287 | unix.SYS_BPF, 288 | uintptr(constdef.BPF_OBJ_GET_INFO_BY_FD), 289 | uintptr(unsafe.Pointer(objattr)), 290 | unsafe.Sizeof(*objattr), 291 | ) 292 | if errno != 0 { 293 | log.Errorf("failed to get object info by FD - ret %d and err %s", int(ret), errno) 294 | return errno 295 | } 296 | return nil 297 | } 298 | 299 | func GetBPFprogInfo(progFD int) (BpfProgInfo, error) { 300 | var bpfProgInfo BpfProgInfo 301 | objInfo := BpfObjGetInfo{ 302 | bpf_fd: uint32(progFD), 303 | info_len: uint32(unsafe.Sizeof(bpfProgInfo)), 304 | info: uintptr(unsafe.Pointer(&bpfProgInfo)), 305 | } 306 | 307 | err := objInfo.BpfGetProgramInfoForFD() 308 | if err != nil { 309 | log.Errorf("failed to get program Info for FD - ", progFD) 310 | return BpfProgInfo{}, err 311 | } 312 | 313 | log.Infof("TYPE - %d", bpfProgInfo.Type) 314 | log.Infof("Prog Name - %s", string(bpfProgInfo.Name[:])) 315 | log.Infof("Maps linked - %d", bpfProgInfo.NrMapIDs) 316 | 317 | return bpfProgInfo, nil 318 | } 319 | 320 | func (m *BpfProgram) GetBPFProgAssociatedMapsIDs(progFD int) ([]uint32, error) { 321 | bpfProgInfo, err := GetBPFprogInfo(progFD) 322 | 323 | if bpfProgInfo.NrMapIDs <= 0 { 324 | return nil, nil 325 | } 326 | numMaps := bpfProgInfo.NrMapIDs 327 | 328 | associatedMaps := make([]uint32, numMaps) 329 | newBpfProgInfo := BpfProgInfo{ 330 | NrMapIDs: numMaps, 331 | MapIDs: uint64(uintptr(unsafe.Pointer(&associatedMaps[0]))), 332 | } 333 | objInfo := BpfObjGetInfo{ 334 | bpf_fd: uint32(progFD), 335 | info_len: uint32(unsafe.Sizeof(newBpfProgInfo)), 336 | info: uintptr(unsafe.Pointer(&newBpfProgInfo)), 337 | } 338 | 339 | err = objInfo.BpfGetProgramInfoForFD() 340 | if err != nil { 341 | log.Errorf("failed to get program Info for FD - ", progFD) 342 | return nil, err 343 | } 344 | return associatedMaps, nil 345 | } 346 | 347 | func BpfGetMapInfoFromProgInfo(progFD int, numMaps uint32) ([]ebpf_maps.BpfMapInfo, []int, error) { 348 | associatedMaps := make([]uint32, numMaps) 349 | newBpfProgInfo := BpfProgInfo{ 350 | NrMapIDs: numMaps, 351 | MapIDs: uint64(uintptr(unsafe.Pointer(&associatedMaps[0]))), 352 | } 353 | 354 | objInfo := BpfObjGetInfo{ 355 | bpf_fd: uint32(progFD), 356 | info_len: uint32(unsafe.Sizeof(newBpfProgInfo)), 357 | info: uintptr(unsafe.Pointer(&newBpfProgInfo)), 358 | } 359 | 360 | err := objInfo.BpfGetProgramInfoForFD() 361 | if err != nil { 362 | log.Errorf("failed to get program Info for FD - ", progFD) 363 | return nil, nil, err 364 | } 365 | 366 | log.Infof("TYPE - %d", newBpfProgInfo.Type) 367 | log.Infof("Prog Name - %s", unix.ByteSliceToString(newBpfProgInfo.Name[:])) 368 | log.Infof("Maps linked - %d", newBpfProgInfo.NrMapIDs) 369 | //Printing associated maps 370 | loadedMaps := []ebpf_maps.BpfMapInfo{} 371 | loadedMapsIDs := make([]int, 0) 372 | for mapIdx := 0; mapIdx < len(associatedMaps); mapIdx++ { 373 | log.Infof("MAP ID - %d", associatedMaps[mapIdx]) 374 | 375 | mapfd, err := utils.GetMapFDFromID(int(associatedMaps[mapIdx])) 376 | if err != nil { 377 | log.Errorf("failed to get map Info") 378 | return nil, nil, err 379 | } 380 | log.Infof("Creating temporary map FD - %d", mapfd) 381 | 382 | bpfMapInfo, err := ebpf_maps.GetBPFmapInfo(mapfd) 383 | if err != nil { 384 | log.Errorf("failed to get map Info for FD", mapfd) 385 | return nil, nil, err 386 | } 387 | 388 | log.Infof("Closing map FD %d", mapfd) 389 | unix.Close(mapfd) 390 | 391 | loadedMaps = append(loadedMaps, bpfMapInfo) 392 | loadedMapsIDs = append(loadedMapsIDs, int(associatedMaps[mapIdx])) 393 | } 394 | return loadedMaps, loadedMapsIDs, nil 395 | } 396 | 397 | func BpfGetAllProgramInfo() ([]BpfProgInfo, error) { 398 | loadedPrograms := []BpfProgInfo{} 399 | attr := BpfProgAttr{} 400 | log.Infof("In get all prog info") 401 | for attr.isBpfProgGetNextID() { 402 | log.Infof("Got ID - %d", attr.next_id) 403 | 404 | progfd, err := utils.GetProgFDFromID(int(attr.next_id)) 405 | if err != nil { 406 | log.Errorf("failed to get program Info") 407 | return nil, err 408 | } 409 | log.Infof("Found prog FD - %d", progfd) 410 | bpfProgInfo, err := GetBPFprogInfo(progfd) 411 | if err != nil { 412 | log.Errorf("failed to get program Info for FD", progfd) 413 | return nil, err 414 | } 415 | unix.Close(progfd) 416 | 417 | loadedPrograms = append(loadedPrograms, bpfProgInfo) 418 | } 419 | log.Infof("Done all prog info!!!") 420 | return loadedPrograms, nil 421 | } 422 | 423 | func (attr *BpfObjGet) BpfGetObject() (int, error) { 424 | ret, _, errno := unix.Syscall( 425 | unix.SYS_BPF, 426 | uintptr(constdef.BPF_OBJ_GET), 427 | uintptr(unsafe.Pointer(attr)), 428 | unsafe.Sizeof(*attr), 429 | ) 430 | if errno != 0 { 431 | log.Errorf("failed to get Prog FD - ret %d and err %s", int(ret), errno) 432 | return 0, errno 433 | } 434 | return int(ret), nil 435 | } 436 | 437 | func (m *BpfProgram) GetProgFromPinPath(pinPath string) (BpfProgInfo, int, error) { 438 | log.Infof("Printing pinpath - %s ", pinPath) 439 | if len(pinPath) == 0 { 440 | return BpfProgInfo{}, -1, fmt.Errorf("invalid pinPath") 441 | } 442 | 443 | cPath := []byte(pinPath + "\x00") 444 | objInfo := BpfObjGet{ 445 | pathname: uintptr(unsafe.Pointer(&cPath[0])), 446 | } 447 | 448 | progFD, err := objInfo.BpfGetObject() 449 | if err != nil { 450 | log.Errorf("failed to get object") 451 | return BpfProgInfo{}, -1, err 452 | 453 | } 454 | 455 | log.Infof("Got progFD - %d", progFD) 456 | bpfProgInfo, err := GetBPFprogInfo(progFD) 457 | if err != nil { 458 | log.Errorf("failed to get program Info for FD - %d", progFD) 459 | return bpfProgInfo, -1, err 460 | } 461 | 462 | return bpfProgInfo, progFD, nil 463 | } 464 | -------------------------------------------------------------------------------- /pkg/progs/mocks/ebpf_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/aws/aws-ebpf-sdk-go/pkg/progs (interfaces: BpfProgAPIs) 3 | 4 | // Package mock_progs is a generated GoMock package. 5 | package mock_progs 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | progs "github.com/aws/aws-ebpf-sdk-go/pkg/progs" 11 | gomock "github.com/golang/mock/gomock" 12 | ) 13 | 14 | // MockBpfProgAPIs is a mock of BpfProgAPIs interface. 15 | type MockBpfProgAPIs struct { 16 | ctrl *gomock.Controller 17 | recorder *MockBpfProgAPIsMockRecorder 18 | } 19 | 20 | // MockBpfProgAPIsMockRecorder is the mock recorder for MockBpfProgAPIs. 21 | type MockBpfProgAPIsMockRecorder struct { 22 | mock *MockBpfProgAPIs 23 | } 24 | 25 | // NewMockBpfProgAPIs creates a new mock instance. 26 | func NewMockBpfProgAPIs(ctrl *gomock.Controller) *MockBpfProgAPIs { 27 | mock := &MockBpfProgAPIs{ctrl: ctrl} 28 | mock.recorder = &MockBpfProgAPIsMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockBpfProgAPIs) EXPECT() *MockBpfProgAPIsMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // GetBPFProgAssociatedMapsIDs mocks base method. 38 | func (m *MockBpfProgAPIs) GetBPFProgAssociatedMapsIDs(arg0 int) ([]uint32, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "GetBPFProgAssociatedMapsIDs", arg0) 41 | ret0, _ := ret[0].([]uint32) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // GetBPFProgAssociatedMapsIDs indicates an expected call of GetBPFProgAssociatedMapsIDs. 47 | func (mr *MockBpfProgAPIsMockRecorder) GetBPFProgAssociatedMapsIDs(arg0 interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBPFProgAssociatedMapsIDs", reflect.TypeOf((*MockBpfProgAPIs)(nil).GetBPFProgAssociatedMapsIDs), arg0) 50 | } 51 | 52 | // GetProgFromPinPath mocks base method. 53 | func (m *MockBpfProgAPIs) GetProgFromPinPath(arg0 string) (progs.BpfProgInfo, int, error) { 54 | m.ctrl.T.Helper() 55 | ret := m.ctrl.Call(m, "GetProgFromPinPath", arg0) 56 | ret0, _ := ret[0].(progs.BpfProgInfo) 57 | ret1, _ := ret[1].(int) 58 | ret2, _ := ret[2].(error) 59 | return ret0, ret1, ret2 60 | } 61 | 62 | // GetProgFromPinPath indicates an expected call of GetProgFromPinPath. 63 | func (mr *MockBpfProgAPIsMockRecorder) GetProgFromPinPath(arg0 interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProgFromPinPath", reflect.TypeOf((*MockBpfProgAPIs)(nil).GetProgFromPinPath), arg0) 66 | } 67 | 68 | // LoadProg mocks base method. 69 | func (m *MockBpfProgAPIs) LoadProg(arg0 progs.CreateEBPFProgInput) (int, error) { 70 | m.ctrl.T.Helper() 71 | ret := m.ctrl.Call(m, "LoadProg", arg0) 72 | ret0, _ := ret[0].(int) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // LoadProg indicates an expected call of LoadProg. 78 | func (mr *MockBpfProgAPIsMockRecorder) LoadProg(arg0 interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadProg", reflect.TypeOf((*MockBpfProgAPIs)(nil).LoadProg), arg0) 81 | } 82 | 83 | // PinProg mocks base method. 84 | func (m *MockBpfProgAPIs) PinProg(arg0 uint32, arg1 string) error { 85 | m.ctrl.T.Helper() 86 | ret := m.ctrl.Call(m, "PinProg", arg0, arg1) 87 | ret0, _ := ret[0].(error) 88 | return ret0 89 | } 90 | 91 | // PinProg indicates an expected call of PinProg. 92 | func (mr *MockBpfProgAPIsMockRecorder) PinProg(arg0, arg1 interface{}) *gomock.Call { 93 | mr.mock.ctrl.T.Helper() 94 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PinProg", reflect.TypeOf((*MockBpfProgAPIs)(nil).PinProg), arg0, arg1) 95 | } 96 | 97 | // UnPinProg mocks base method. 98 | func (m *MockBpfProgAPIs) UnPinProg(arg0 string) error { 99 | m.ctrl.T.Helper() 100 | ret := m.ctrl.Call(m, "UnPinProg", arg0) 101 | ret0, _ := ret[0].(error) 102 | return ret0 103 | } 104 | 105 | // UnPinProg indicates an expected call of UnPinProg. 106 | func (mr *MockBpfProgAPIsMockRecorder) UnPinProg(arg0 interface{}) *gomock.Call { 107 | mr.mock.ctrl.T.Helper() 108 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnPinProg", reflect.TypeOf((*MockBpfProgAPIs)(nil).UnPinProg), arg0) 109 | } 110 | -------------------------------------------------------------------------------- /pkg/tc/generate_mocks.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | package tc 14 | 15 | //go:generate go run github.com/golang/mock/mockgen -destination mocks/tc_mocks.go . BpfTc 16 | -------------------------------------------------------------------------------- /pkg/tc/mocks/tc_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: pkg/tc/tc.go 3 | 4 | // Package mock_tc is a generated GoMock package. 5 | package mock_tc 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | ) 12 | 13 | // MockBpfTc is a mock of BpfTc interface. 14 | type MockBpfTc struct { 15 | ctrl *gomock.Controller 16 | recorder *MockBpfTcMockRecorder 17 | } 18 | 19 | // MockBpfTcMockRecorder is the mock recorder for MockBpfTc. 20 | type MockBpfTcMockRecorder struct { 21 | mock *MockBpfTc 22 | } 23 | 24 | // NewMockBpfTc creates a new mock instance. 25 | func NewMockBpfTc(ctrl *gomock.Controller) *MockBpfTc { 26 | mock := &MockBpfTc{ctrl: ctrl} 27 | mock.recorder = &MockBpfTcMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockBpfTc) EXPECT() *MockBpfTcMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // CleanupQdiscs mocks base method. 37 | func (m *MockBpfTc) CleanupQdiscs(ingressCleanup, egressCleanup bool) error { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "CleanupQdiscs", ingressCleanup, egressCleanup) 40 | ret0, _ := ret[0].(error) 41 | return ret0 42 | } 43 | 44 | // CleanupQdiscs indicates an expected call of CleanupQdiscs. 45 | func (mr *MockBpfTcMockRecorder) CleanupQdiscs(ingressCleanup, egressCleanup interface{}) *gomock.Call { 46 | mr.mock.ctrl.T.Helper() 47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanupQdiscs", reflect.TypeOf((*MockBpfTc)(nil).CleanupQdiscs), ingressCleanup, egressCleanup) 48 | } 49 | 50 | // GetAllAttachedProgIds mocks base method. 51 | func (m *MockBpfTc) GetAllAttachedProgIds() (map[string]int, map[string]int, error) { 52 | m.ctrl.T.Helper() 53 | ret := m.ctrl.Call(m, "GetAllAttachedProgIds") 54 | ret0, _ := ret[0].(map[string]int) 55 | ret1, _ := ret[1].(map[string]int) 56 | ret2, _ := ret[2].(error) 57 | return ret0, ret1, ret2 58 | } 59 | 60 | // GetAllAttachedProgIds indicates an expected call of GetAllAttachedProgIds. 61 | func (mr *MockBpfTcMockRecorder) GetAllAttachedProgIds() *gomock.Call { 62 | mr.mock.ctrl.T.Helper() 63 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllAttachedProgIds", reflect.TypeOf((*MockBpfTc)(nil).GetAllAttachedProgIds)) 64 | } 65 | 66 | // TCEgressAttach mocks base method. 67 | func (m *MockBpfTc) TCEgressAttach(interfaceName string, progFD int, funcName string) error { 68 | m.ctrl.T.Helper() 69 | ret := m.ctrl.Call(m, "TCEgressAttach", interfaceName, progFD, funcName) 70 | ret0, _ := ret[0].(error) 71 | return ret0 72 | } 73 | 74 | // TCEgressAttach indicates an expected call of TCEgressAttach. 75 | func (mr *MockBpfTcMockRecorder) TCEgressAttach(interfaceName, progFD, funcName interface{}) *gomock.Call { 76 | mr.mock.ctrl.T.Helper() 77 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TCEgressAttach", reflect.TypeOf((*MockBpfTc)(nil).TCEgressAttach), interfaceName, progFD, funcName) 78 | } 79 | 80 | // TCEgressDetach mocks base method. 81 | func (m *MockBpfTc) TCEgressDetach(interfaceName string) error { 82 | m.ctrl.T.Helper() 83 | ret := m.ctrl.Call(m, "TCEgressDetach", interfaceName) 84 | ret0, _ := ret[0].(error) 85 | return ret0 86 | } 87 | 88 | // TCEgressDetach indicates an expected call of TCEgressDetach. 89 | func (mr *MockBpfTcMockRecorder) TCEgressDetach(interfaceName interface{}) *gomock.Call { 90 | mr.mock.ctrl.T.Helper() 91 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TCEgressDetach", reflect.TypeOf((*MockBpfTc)(nil).TCEgressDetach), interfaceName) 92 | } 93 | 94 | // TCIngressAttach mocks base method. 95 | func (m *MockBpfTc) TCIngressAttach(interfaceName string, progFD int, funcName string) error { 96 | m.ctrl.T.Helper() 97 | ret := m.ctrl.Call(m, "TCIngressAttach", interfaceName, progFD, funcName) 98 | ret0, _ := ret[0].(error) 99 | return ret0 100 | } 101 | 102 | // TCIngressAttach indicates an expected call of TCIngressAttach. 103 | func (mr *MockBpfTcMockRecorder) TCIngressAttach(interfaceName, progFD, funcName interface{}) *gomock.Call { 104 | mr.mock.ctrl.T.Helper() 105 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TCIngressAttach", reflect.TypeOf((*MockBpfTc)(nil).TCIngressAttach), interfaceName, progFD, funcName) 106 | } 107 | 108 | // TCIngressDetach mocks base method. 109 | func (m *MockBpfTc) TCIngressDetach(interfaceName string) error { 110 | m.ctrl.T.Helper() 111 | ret := m.ctrl.Call(m, "TCIngressDetach", interfaceName) 112 | ret0, _ := ret[0].(error) 113 | return ret0 114 | } 115 | 116 | // TCIngressDetach indicates an expected call of TCIngressDetach. 117 | func (mr *MockBpfTcMockRecorder) TCIngressDetach(interfaceName interface{}) *gomock.Call { 118 | mr.mock.ctrl.T.Helper() 119 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TCIngressDetach", reflect.TypeOf((*MockBpfTc)(nil).TCIngressDetach), interfaceName) 120 | } 121 | -------------------------------------------------------------------------------- /pkg/tc/tc.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package tc 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "strings" 21 | 22 | constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" 23 | "github.com/aws/aws-ebpf-sdk-go/pkg/logger" 24 | "github.com/vishvananda/netlink" 25 | "golang.org/x/sys/unix" 26 | ) 27 | 28 | const ( 29 | FILTER_CLEANUP_FAILED = "filter cleanup failed" 30 | ) 31 | 32 | var log = logger.Get() 33 | 34 | type BpfTc interface { 35 | TCIngressAttach(interfaceName string, progFD int, funcName string) error 36 | TCIngressDetach(interfaceName string) error 37 | TCEgressAttach(interfaceName string, progFD int, funcName string) error 38 | TCEgressDetach(interfaceName string) error 39 | CleanupQdiscs(ingressCleanup bool, egressCleanup bool) error 40 | GetAllAttachedProgIds() (map[string]int, map[string]int, error) 41 | } 42 | 43 | var _ BpfTc = &bpfTc{} 44 | 45 | type bpfTc struct { 46 | InterfacePrefix []string 47 | } 48 | 49 | func New(interfacePrefix []string) BpfTc { 50 | return &bpfTc{ 51 | InterfacePrefix: interfacePrefix, 52 | } 53 | 54 | } 55 | 56 | func enableQdisc(link netlink.Link) bool { 57 | qdiscs, err := netlink.QdiscList(link) 58 | if err != nil { 59 | log.Infof("Unable to check qdisc hence try installing") 60 | return true 61 | } 62 | 63 | qdiscHandle := netlink.MakeHandle(constdef.QDISC_HANDLE, 0) 64 | for _, qdisc := range qdiscs { 65 | attrs := qdisc.Attrs() 66 | if attrs.LinkIndex != link.Attrs().Index { 67 | continue 68 | } 69 | if (attrs.Handle&qdiscHandle) == qdiscHandle && attrs.Parent == netlink.HANDLE_CLSACT { 70 | log.Infof("Found qdisc hence don't install again") 71 | return false 72 | } 73 | } 74 | log.Infof("Qdisc is not enabled hence install") 75 | return true 76 | 77 | } 78 | 79 | func mismatchedInterfacePrefix(interfaceName string, interfacePrefix []string) error { 80 | 81 | for _, prefix := range interfacePrefix { 82 | if strings.HasPrefix(interfaceName, prefix) { 83 | return nil 84 | } 85 | } 86 | 87 | log.Errorf("expected prefix - %s but got %s", interfacePrefix, interfaceName) 88 | return errors.New("Mismatched initialized prefix name and passed interface name") 89 | } 90 | 91 | func (m *bpfTc) TCIngressAttach(interfaceName string, progFD int, funcName string) error { 92 | 93 | if err := mismatchedInterfacePrefix(interfaceName, m.InterfacePrefix); err != nil { 94 | return err 95 | } 96 | 97 | intf, err := netlink.LinkByName(interfaceName) 98 | if err != nil { 99 | log.Errorf("failed to find device by name %s: %v", interfaceName, err) 100 | return err 101 | } 102 | 103 | attrs := netlink.QdiscAttrs{ 104 | LinkIndex: intf.Attrs().Index, 105 | Handle: netlink.MakeHandle(constdef.QDISC_HANDLE, 0), 106 | Parent: netlink.HANDLE_CLSACT, 107 | } 108 | 109 | if enableQdisc(intf) { 110 | qdisc := &netlink.GenericQdisc{ 111 | QdiscAttrs: attrs, 112 | QdiscType: "clsact", 113 | } 114 | 115 | if err := netlink.QdiscAdd(qdisc); err != nil { 116 | log.Errorf("cannot add clsact qdisc: %v", err) 117 | return err 118 | } 119 | } 120 | 121 | // construct the filter 122 | filter := &netlink.BpfFilter{ 123 | FilterAttrs: netlink.FilterAttrs{ 124 | LinkIndex: attrs.LinkIndex, 125 | Parent: uint32(netlink.HANDLE_MIN_INGRESS), 126 | Handle: constdef.DEFAULT_BPF_FILTER_HANDLE, 127 | Protocol: unix.ETH_P_ALL, 128 | Priority: 1, 129 | }, 130 | Fd: progFD, 131 | Name: funcName, 132 | DirectAction: true, 133 | } 134 | 135 | if err = netlink.FilterAdd(filter); err != nil { 136 | log.Errorf("while loading ingress program %q on fd %d: %v", "handle ingress", progFD, err) 137 | return err 138 | } 139 | log.Infof("TC ingress filter add done %s", interfaceName) 140 | return nil 141 | } 142 | 143 | func (m *bpfTc) TCIngressDetach(interfaceName string) error { 144 | 145 | if err := mismatchedInterfacePrefix(interfaceName, m.InterfacePrefix); err != nil { 146 | return err 147 | } 148 | 149 | intf, err := netlink.LinkByName(interfaceName) 150 | if err != nil { 151 | log.Errorf("failed to find device by name %s: %v", interfaceName, err) 152 | return err 153 | } 154 | 155 | //Currently supports only one handle, in future we might need to cache the handle 156 | filterHandle := uint32(constdef.DEFAULT_BPF_FILTER_HANDLE) 157 | filterParent := uint32(netlink.HANDLE_MIN_INGRESS) 158 | 159 | filters, err := netlink.FilterList(intf, filterParent) 160 | if err != nil { 161 | log.Errorf("failed to get filter list: %v", err) 162 | return err 163 | } 164 | 165 | for _, filter := range filters { 166 | if filter.Attrs().Handle == filterHandle { 167 | err = netlink.FilterDel(filter) 168 | if err != nil { 169 | log.Errorf("delete filter failed on intf %s : %v", interfaceName, err) 170 | return errors.New(FILTER_CLEANUP_FAILED) 171 | } 172 | log.Infof("TC ingress filter detach done") 173 | return nil 174 | } 175 | } 176 | return fmt.Errorf("no active filter to detach-%s", interfaceName) 177 | } 178 | 179 | func (m *bpfTc) TCEgressAttach(interfaceName string, progFD int, funcName string) error { 180 | 181 | if err := mismatchedInterfacePrefix(interfaceName, m.InterfacePrefix); err != nil { 182 | return err 183 | } 184 | 185 | intf, err := netlink.LinkByName(interfaceName) 186 | if err != nil { 187 | log.Errorf("failed to find device by name %s: %w", interfaceName, err) 188 | return err 189 | } 190 | 191 | attrs := netlink.QdiscAttrs{ 192 | LinkIndex: intf.Attrs().Index, 193 | Handle: netlink.MakeHandle(constdef.QDISC_HANDLE, 0), 194 | Parent: netlink.HANDLE_CLSACT, 195 | } 196 | 197 | if enableQdisc(intf) { 198 | qdisc := &netlink.GenericQdisc{ 199 | QdiscAttrs: attrs, 200 | QdiscType: "clsact", 201 | } 202 | 203 | if err := netlink.QdiscAdd(qdisc); err != nil { 204 | log.Errorf("cannot add clsact qdisc: %v", err) 205 | return err 206 | } 207 | } 208 | 209 | // construct the filter 210 | filter := &netlink.BpfFilter{ 211 | FilterAttrs: netlink.FilterAttrs{ 212 | LinkIndex: attrs.LinkIndex, 213 | Parent: uint32(netlink.HANDLE_MIN_EGRESS), 214 | Handle: constdef.DEFAULT_BPF_FILTER_HANDLE, 215 | Protocol: unix.ETH_P_ALL, 216 | Priority: 1, 217 | }, 218 | Fd: progFD, 219 | Name: funcName, 220 | DirectAction: true, 221 | } 222 | 223 | if err = netlink.FilterAdd(filter); err != nil { 224 | log.Errorf("while loading egress program %q on fd %d: %v", "handle egress", progFD, err) 225 | return err 226 | } 227 | log.Infof("TC filter egress add done %s", interfaceName) 228 | return nil 229 | } 230 | 231 | func (m *bpfTc) TCEgressDetach(interfaceName string) error { 232 | 233 | if err := mismatchedInterfacePrefix(interfaceName, m.InterfacePrefix); err != nil { 234 | return err 235 | } 236 | 237 | intf, err := netlink.LinkByName(interfaceName) 238 | if err != nil { 239 | log.Errorf("failed to find device by name %s: %w", interfaceName, err) 240 | return err 241 | } 242 | 243 | //Currently supports only one handle, in future we might need to cache the handle 244 | filterHandle := uint32(0x1) 245 | filterParent := uint32(netlink.HANDLE_MIN_EGRESS) 246 | 247 | filters, err := netlink.FilterList(intf, filterParent) 248 | if err != nil { 249 | log.Errorf("failed to get filter list: %v", err) 250 | return err 251 | } 252 | 253 | for _, filter := range filters { 254 | if filter.Attrs().Handle == filterHandle { 255 | err = netlink.FilterDel(filter) 256 | if err != nil { 257 | log.Errorf("delete filter failed on intf %s : %v", interfaceName, err) 258 | return errors.New(FILTER_CLEANUP_FAILED) 259 | } 260 | log.Infof("TC egress filter detach done") 261 | return nil 262 | } 263 | } 264 | return fmt.Errorf("no active filter to detach-%s", interfaceName) 265 | } 266 | 267 | func (m *bpfTc) CleanupQdiscs(ingressCleanup bool, egressCleanup bool) error { 268 | 269 | if len(m.InterfacePrefix) == 0 { 270 | log.Errorf("invalid empty prefix") 271 | return nil 272 | } 273 | 274 | linkList, err := netlink.LinkList() 275 | if err != nil { 276 | log.Errorf("unable to get link list") 277 | return err 278 | } 279 | 280 | for _, link := range linkList { 281 | linkName := link.Attrs().Name 282 | if err := mismatchedInterfacePrefix(linkName, m.InterfacePrefix); err == nil { 283 | if ingressCleanup { 284 | log.Infof("Trying to cleanup ingress on %s", linkName) 285 | err = m.TCIngressDetach(linkName) 286 | if err != nil { 287 | if err.Error() == FILTER_CLEANUP_FAILED { 288 | log.Errorf("failed to detach ingress, might not be present so moving on") 289 | } 290 | } 291 | } 292 | 293 | if egressCleanup { 294 | log.Infof("Trying to cleanup egress on %s", linkName) 295 | err = m.TCEgressDetach(linkName) 296 | if err != nil { 297 | if err.Error() == FILTER_CLEANUP_FAILED { 298 | log.Errorf("failed to detach egress, might not be present so moving on") 299 | } 300 | } 301 | } 302 | } 303 | } 304 | return nil 305 | } 306 | 307 | func (m *bpfTc) getAttachedProgId(link netlink.Link, filterParent uint32) int { 308 | linkName := link.Attrs().Name 309 | filters, err := netlink.FilterList(link, filterParent) 310 | if err != nil { 311 | log.Errorf("failed to list filters for: %s ", linkName, err) 312 | } 313 | progId := 0 314 | filterHandle := uint32(constdef.DEFAULT_BPF_FILTER_HANDLE) 315 | // You will only have one filter for a handle 316 | for _, filter := range filters { 317 | if filter.Attrs().Handle == filterHandle { 318 | bpf, ok := filter.(*netlink.BpfFilter) 319 | if !ok { 320 | continue 321 | } 322 | progId = int(bpf.Id) 323 | } 324 | } 325 | return progId 326 | } 327 | 328 | func (m *bpfTc) GetAllAttachedProgIds() (map[string]int, map[string]int, error) { 329 | 330 | if len(m.InterfacePrefix) == 0 { 331 | log.Errorf("invalid empty prefix") 332 | return nil, nil, fmt.Errorf("Invalid empty prefix") 333 | } 334 | 335 | linkList, err := netlink.LinkList() 336 | if err != nil { 337 | log.Errorf("unable to get link list") 338 | return nil, nil, err 339 | } 340 | 341 | interfaceToIngressProgId := make(map[string]int) 342 | interfaceToEgressProgId := make(map[string]int) 343 | for _, link := range linkList { 344 | linkName := link.Attrs().Name 345 | log.Infof("link name %s", linkName) 346 | ingressProgId := 0 347 | egressProgId := 0 348 | if err := mismatchedInterfacePrefix(linkName, m.InterfacePrefix); err == nil { 349 | // Get ingress ID attached 350 | filterParent := uint32(netlink.HANDLE_MIN_INGRESS) 351 | ingressProgId = m.getAttachedProgId(link, filterParent) 352 | log.Infof("Got ingress progId %d", ingressProgId) 353 | if ingressProgId > 0 { 354 | interfaceToIngressProgId[linkName] = ingressProgId 355 | } 356 | 357 | // Get egress ID attached 358 | filterParent = uint32(netlink.HANDLE_MIN_EGRESS) 359 | egressProgId = m.getAttachedProgId(link, filterParent) 360 | log.Infof("Got egress progId %d", egressProgId) 361 | if egressProgId > 0 { 362 | interfaceToEgressProgId[linkName] = egressProgId 363 | } 364 | } 365 | } 366 | return interfaceToIngressProgId, interfaceToEgressProgId, nil 367 | } 368 | -------------------------------------------------------------------------------- /pkg/tc/tc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package tc 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "os" 21 | "syscall" 22 | "testing" 23 | 24 | constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" 25 | "github.com/aws/aws-ebpf-sdk-go/pkg/elfparser" 26 | mock_ebpf_maps "github.com/aws/aws-ebpf-sdk-go/pkg/maps/mocks" 27 | mock_ebpf_progs "github.com/aws/aws-ebpf-sdk-go/pkg/progs/mocks" 28 | "github.com/golang/mock/gomock" 29 | "github.com/stretchr/testify/assert" 30 | "github.com/vishvananda/netlink" 31 | ) 32 | 33 | const ( 34 | DUMMY_PROG_NAME = "test" 35 | ) 36 | 37 | type testMocks struct { 38 | path string 39 | ctrl *gomock.Controller 40 | ebpf_progs *mock_ebpf_progs.MockBpfProgAPIs 41 | ebpf_maps *mock_ebpf_maps.MockBpfMapAPIs 42 | tcClient BpfTc 43 | } 44 | 45 | func setup(t *testing.T, testPath string, interfacePrefix []string) *testMocks { 46 | ctrl := gomock.NewController(t) 47 | return &testMocks{ 48 | path: testPath, 49 | ctrl: ctrl, 50 | ebpf_progs: mock_ebpf_progs.NewMockBpfProgAPIs(ctrl), 51 | ebpf_maps: mock_ebpf_maps.NewMockBpfMapAPIs(ctrl), 52 | tcClient: New(interfacePrefix), 53 | } 54 | } 55 | 56 | func mount_bpf_fs() error { 57 | fmt.Println("Let's mount BPF FS") 58 | err := syscall.Mount("bpf", "/sys/fs/bpf", "bpf", 0, "mode=0700") 59 | if err != nil { 60 | fmt.Println("error mounting bpffs") 61 | } 62 | return err 63 | } 64 | 65 | func unmount_bpf_fs() error { 66 | fmt.Println("Let's unmount BPF FS") 67 | err := syscall.Unmount("/sys/fs/bpf", 0) 68 | if err != nil { 69 | fmt.Println("error unmounting bpffs") 70 | } 71 | return err 72 | } 73 | 74 | func setupTest(interfaceNames []string, t *testing.T) { 75 | mount_bpf_fs() 76 | for _, interfaceName := range interfaceNames { 77 | linkAttr := netlink.LinkAttrs{Name: interfaceName} 78 | linkIFB := netlink.Ifb{} 79 | linkIFB.LinkAttrs = linkAttr 80 | if err := netlink.LinkAdd(&linkIFB); err != nil { 81 | assert.NoError(t, err) 82 | } 83 | } 84 | } 85 | 86 | func teardownTest(interfaceNames []string, t *testing.T) { 87 | unmount_bpf_fs() 88 | //Cleanup link 89 | for _, interfaceName := range interfaceNames { 90 | linkAttr := netlink.LinkAttrs{Name: interfaceName} 91 | linkIFB := netlink.Ifb{} 92 | linkIFB.LinkAttrs = linkAttr 93 | if err := netlink.LinkDel(&linkIFB); err != nil { 94 | assert.NoError(t, err) 95 | } 96 | } 97 | } 98 | 99 | func TestMismatchedPrefixName(t *testing.T) { 100 | m := setup(t, "../../test-data/tc.bpf.elf", []string{"eni", "vlan"}) 101 | defer m.ctrl.Finish() 102 | 103 | tests := []struct { 104 | name string 105 | interfaceName string 106 | wantErr error 107 | }{ 108 | { 109 | name: "Test Matched Prefix", 110 | interfaceName: "eni1", 111 | wantErr: nil, 112 | }, 113 | { 114 | name: "Test Mismatched Prefix", 115 | interfaceName: "fni1", 116 | wantErr: errors.New("Mismatched initialized prefix name and passed interface name"), 117 | }, 118 | { 119 | name: "Test Mismatched Prefix", 120 | interfaceName: "vlan1", 121 | wantErr: nil, 122 | }, 123 | } 124 | 125 | for _, tt := range tests { 126 | t.Run(tt.name, func(t *testing.T) { 127 | 128 | err := mismatchedInterfacePrefix(tt.interfaceName, []string{"eni", "vlan"}) 129 | if tt.wantErr != nil { 130 | assert.EqualError(t, err, tt.wantErr.Error()) 131 | } else { 132 | assert.NoError(t, err) 133 | } 134 | }) 135 | } 136 | } 137 | 138 | func TestTCIngressAttachDetach(t *testing.T) { 139 | if os.Getuid() != 0 { 140 | t.Skip("Test requires root privileges.") 141 | } 142 | 143 | m := setup(t, "../../test-data/tc.bpf.elf", []string{"f"}) 144 | defer m.ctrl.Finish() 145 | 146 | interfaceName := "foo" 147 | 148 | var interfaceNames []string 149 | interfaceNames = append(interfaceNames, interfaceName) 150 | setupTest(interfaceNames, t) 151 | defer teardownTest(interfaceNames, t) 152 | 153 | m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() 154 | m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() 155 | m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() 156 | m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() 157 | m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() 158 | m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() 159 | 160 | bpfSDKclient := elfparser.New() 161 | progInfo, _, err := bpfSDKclient.LoadBpfFile(m.path, DUMMY_PROG_NAME) 162 | if err != nil { 163 | assert.NoError(t, err) 164 | } 165 | pinPath := constdef.PROG_BPF_FS + DUMMY_PROG_NAME + "_handle_ingress" 166 | 167 | progFD := progInfo[pinPath].Program.ProgFD 168 | if err := m.tcClient.TCIngressAttach(interfaceName, progFD, DUMMY_PROG_NAME); err != nil { 169 | assert.NoError(t, err) 170 | } 171 | 172 | if err := m.tcClient.TCIngressDetach(interfaceName); err != nil { 173 | assert.NoError(t, err) 174 | } 175 | } 176 | 177 | func TestTCEgressAttachDetach(t *testing.T) { 178 | if os.Getuid() != 0 { 179 | t.Skip("Test requires root privileges.") 180 | } 181 | 182 | m := setup(t, "../../test-data/tc.bpf.elf", []string{"f"}) 183 | defer m.ctrl.Finish() 184 | 185 | interfaceName := "foo" 186 | 187 | var interfaceNames []string 188 | interfaceNames = append(interfaceNames, interfaceName) 189 | 190 | setupTest(interfaceNames, t) 191 | defer teardownTest(interfaceNames, t) 192 | 193 | m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() 194 | m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() 195 | m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() 196 | m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() 197 | m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() 198 | m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() 199 | 200 | bpfSDKclient := elfparser.New() 201 | progInfo, _, err := bpfSDKclient.LoadBpfFile(m.path, DUMMY_PROG_NAME) 202 | if err != nil { 203 | assert.NoError(t, err) 204 | } 205 | pinPath := constdef.PROG_BPF_FS + DUMMY_PROG_NAME + "_handle_ingress" 206 | 207 | progFD := progInfo[pinPath].Program.ProgFD 208 | if err := m.tcClient.TCEgressAttach(interfaceName, progFD, DUMMY_PROG_NAME); err != nil { 209 | assert.NoError(t, err) 210 | } 211 | 212 | if err := m.tcClient.TCEgressDetach(interfaceName); err != nil { 213 | assert.NoError(t, err) 214 | } 215 | } 216 | 217 | func TestQdiscCleanup(t *testing.T) { 218 | 219 | if os.Getuid() != 0 { 220 | t.Skip("Test requires root privileges.") 221 | } 222 | 223 | m := setup(t, "../../test-data/tc.bpf.elf", []string{"eni", "vlan"}) 224 | defer m.ctrl.Finish() 225 | 226 | interfaceName1 := "eni1" 227 | interfaceName2 := "eni2" 228 | interfaceName3 := "vlan1" 229 | 230 | var interfaceNames []string 231 | interfaceNames = append(interfaceNames, interfaceName1, interfaceName2, interfaceName3) 232 | 233 | setupTest(interfaceNames, t) 234 | defer teardownTest(interfaceNames, t) 235 | 236 | m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() 237 | m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() 238 | m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() 239 | m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() 240 | m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() 241 | m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() 242 | 243 | bpfSDKclient := elfparser.New() 244 | progInfo, _, err := bpfSDKclient.LoadBpfFile(m.path, DUMMY_PROG_NAME) 245 | if err != nil { 246 | assert.NoError(t, err) 247 | } 248 | pinPath := constdef.PROG_BPF_FS + DUMMY_PROG_NAME + "_handle_ingress" 249 | 250 | progFD := progInfo[pinPath].Program.ProgFD 251 | if err := m.tcClient.TCEgressAttach(interfaceName1, progFD, DUMMY_PROG_NAME); err != nil { 252 | assert.NoError(t, err) 253 | } 254 | 255 | if err := m.tcClient.TCIngressAttach(interfaceName2, progFD, DUMMY_PROG_NAME); err != nil { 256 | assert.NoError(t, err) 257 | } 258 | 259 | if err := m.tcClient.CleanupQdiscs(true, true); err != nil { 260 | assert.NoError(t, err) 261 | } 262 | } 263 | 264 | func TestNetLinkAPIs(t *testing.T) { 265 | 266 | netLinktests := []struct { 267 | name string 268 | interfaceName string 269 | overrideName bool 270 | want []int 271 | wantErr error 272 | }{ 273 | { 274 | name: "Failed Link By Name", 275 | interfaceName: "eni1", 276 | want: nil, 277 | overrideName: true, 278 | wantErr: errors.New("Link not found"), 279 | }, 280 | { 281 | name: "Failed to add filter", 282 | interfaceName: "eni1", 283 | overrideName: false, 284 | want: nil, 285 | wantErr: errors.New("invalid argument"), 286 | }, 287 | } 288 | 289 | for _, tt := range netLinktests { 290 | t.Run(tt.name, func(t *testing.T) { 291 | m := setup(t, "../../test-data/tc.bpf.elf", []string{"eni", "vlan"}) 292 | defer m.ctrl.Finish() 293 | 294 | var interfaceNames []string 295 | interfaceNames = append(interfaceNames, tt.interfaceName) 296 | 297 | setupTest(interfaceNames, t) 298 | defer teardownTest(interfaceNames, t) 299 | 300 | m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() 301 | m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() 302 | m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() 303 | m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() 304 | m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() 305 | m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() 306 | 307 | bpfSDKclient := elfparser.New() 308 | _, _, err := bpfSDKclient.LoadBpfFile(m.path, DUMMY_PROG_NAME) 309 | if err != nil { 310 | assert.NoError(t, err) 311 | } 312 | 313 | intfName := tt.interfaceName 314 | if tt.overrideName { 315 | intfName = intfName + "10" 316 | } 317 | err = m.tcClient.TCEgressAttach(intfName, -1, "test") 318 | if tt.wantErr != nil { 319 | assert.EqualError(t, err, tt.wantErr.Error()) 320 | } else { 321 | assert.NoError(t, err) 322 | } 323 | err = m.tcClient.TCIngressAttach(intfName, -1, "test") 324 | if tt.wantErr != nil { 325 | assert.EqualError(t, err, tt.wantErr.Error()) 326 | } else { 327 | assert.NoError(t, err) 328 | } 329 | }) 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /pkg/tracepoint/tracepoint.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package tracepoint 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "strconv" 21 | "strings" 22 | 23 | "unsafe" 24 | 25 | constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" 26 | "github.com/aws/aws-ebpf-sdk-go/pkg/logger" 27 | "golang.org/x/sys/unix" 28 | ) 29 | 30 | var log = logger.Get() 31 | 32 | func TracepointAttach(progFD int, subSystem, eventName string) error { 33 | 34 | if progFD <= 0 { 35 | log.Infof("Invalid BPF prog FD %d", progFD) 36 | return fmt.Errorf("invalid BPF prog FD %d", progFD) 37 | 38 | } 39 | 40 | if len(subSystem) == 0 || len(eventName) == 0 { 41 | return fmt.Errorf("invalid Arguement") 42 | } 43 | 44 | //Get the TP ID 45 | tracepointIDpath := fmt.Sprintf("%s/%s/%s/id", constdef.TRACEPOINT_EVENTS, subSystem, eventName) 46 | data, err := os.ReadFile(tracepointIDpath) 47 | if err != nil { 48 | log.Errorf("unable to read the tracepointID: %v", err) 49 | return err 50 | } 51 | id := strings.TrimSpace(string(data)) 52 | eventID, err := strconv.Atoi(id) 53 | if err != nil { 54 | log.Errorf("invalid ID during parsing: %s - %w", id, err) 55 | return err 56 | } 57 | 58 | log.Infof("Got eventID %d", eventID) 59 | 60 | attr := unix.PerfEventAttr{ 61 | Type: unix.PERF_TYPE_TRACEPOINT, 62 | Sample: 1, 63 | Wakeup: 1, 64 | Config: uint64(eventID), 65 | } 66 | attr.Size = uint32(unsafe.Sizeof(attr)) 67 | 68 | /* 69 | * Ref : https://man7.org/linux/man-pages/man2/perf_event_open.2.html 70 | * pid = -1 and cpu = 0 [This measures all processes/threads on the specified CPU] 71 | * group_fd = -1 Creates event group with leader first. 72 | */ 73 | fd, err := unix.PerfEventOpen(&attr, -1, 0, -1, unix.PERF_FLAG_FD_CLOEXEC) 74 | if err != nil { 75 | log.Errorf("failed to open perf event %v", err) 76 | return err 77 | } 78 | 79 | log.Infof("Attach bpf program to perf event Prog FD %d Event FD %d", progFD, fd) 80 | 81 | if _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(int(fd)), uintptr(uint(unix.PERF_EVENT_IOC_SET_BPF)), uintptr(progFD)); err != 0 { 82 | log.Errorf("error attaching bpf program to perf event: %v", err) 83 | return err 84 | } 85 | 86 | if _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(int(fd)), uintptr(uint(unix.PERF_EVENT_IOC_ENABLE)), 0); err != 0 { 87 | log.Errorf("error enabling perf event: %v", err) 88 | return err 89 | } 90 | 91 | log.Infof("Tracepoint attach done!!! %d", fd) 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package utils 16 | 17 | import ( 18 | "encoding/binary" 19 | "errors" 20 | "fmt" 21 | "io/fs" 22 | "math" 23 | "os" 24 | "runtime" 25 | "syscall" 26 | "unsafe" 27 | 28 | constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" 29 | "github.com/aws/aws-ebpf-sdk-go/pkg/logger" 30 | "golang.org/x/sys/unix" 31 | ) 32 | 33 | type BPFInsn struct { 34 | Code uint8 // Opcode 35 | DstReg uint8 // 4 bits: destination register, r0-r10 36 | SrcReg uint8 // 4 bits: source register, r0-r10 37 | Off int16 // Signed offset 38 | Imm int32 // Immediate constant 39 | } 40 | 41 | type BpfPin struct { 42 | Pathname uintptr 43 | Fd uint32 44 | FileFlags uint32 45 | } 46 | 47 | type BpfMapAttr struct { 48 | MapFD uint32 49 | _ [4]byte 50 | Key uint64 51 | Value uint64 // union: value or next_key 52 | Flags uint64 53 | } 54 | 55 | func PinObject(objFD uint32, pinPath string) error { 56 | var log = logger.Get() 57 | 58 | if pinPath == "" { 59 | return nil 60 | } 61 | cPath := []byte(pinPath + "\x00") 62 | 63 | pinAttr := BpfPin{ 64 | Fd: uint32(objFD), 65 | Pathname: uintptr(unsafe.Pointer(&cPath[0])), 66 | } 67 | pinData := unsafe.Pointer(&pinAttr) 68 | pinDataSize := unsafe.Sizeof(pinAttr) 69 | 70 | log.Infof("Calling BPFsys for FD %d and Path %s", objFD, pinPath) 71 | 72 | ret, _, errno := unix.Syscall( 73 | unix.SYS_BPF, 74 | uintptr(constdef.BPF_OBJ_PIN), 75 | uintptr(pinData), 76 | uintptr(int(pinDataSize)), 77 | ) 78 | if errno < 0 { 79 | log.Infof("Unable to pin map and ret %d and err %s", int(ret), errno) 80 | return fmt.Errorf("Unable to pin map: %s", errno) 81 | } 82 | //TODO : might have to return FD for node agent 83 | log.Infof("Pin done with fd : %d and errno %d", ret, errno) 84 | return nil 85 | } 86 | 87 | func IsfileExists(fname string) (bool, error) { 88 | info, err := os.Stat(fname) 89 | switch { 90 | case errors.Is(err, fs.ErrNotExist): 91 | return false, nil 92 | case err != nil: 93 | return false, fmt.Errorf("unexpected error stat %q: %w", fname, err) 94 | default: 95 | return !info.IsDir(), nil 96 | } 97 | } 98 | 99 | func UnPinObject(pinPath string) error { 100 | var log = logger.Get() 101 | found, err := IsfileExists(pinPath) 102 | if err != nil { 103 | return fmt.Errorf("unable to check file: %w", err) 104 | } 105 | if pinPath == "" || !found { 106 | log.Infof("PinPath is empty or file doesn't exist") 107 | return nil 108 | } 109 | 110 | err = os.Remove(pinPath) 111 | if err != nil { 112 | log.Infof("File remove failed ", pinPath) 113 | return err 114 | } 115 | 116 | return err 117 | } 118 | 119 | /* 120 | * 121 | * struct { anonymous struct used by BPF_*_GET_*_ID 122 | * union { 123 | * __u32 start_id; 124 | * __u32 prog_id; 125 | * __u32 map_id; 126 | * __u32 btf_id; 127 | * __u32 link_id; 128 | * }; 129 | * __u32 next_id; 130 | * __u32 open_flags; 131 | * }; 132 | */ 133 | 134 | type BpfShowAttr struct { 135 | id uint32 136 | next_id uint32 137 | open_flags uint32 138 | } 139 | 140 | func GetMapFDFromID(mapID int) (int, error) { 141 | var log = logger.Get() 142 | attr := BpfShowAttr{ 143 | id: uint32(mapID), 144 | } 145 | ret, _, errno := unix.Syscall( 146 | unix.SYS_BPF, 147 | uintptr(constdef.BPF_MAP_GET_FD_BY_ID), 148 | uintptr(unsafe.Pointer(&attr)), 149 | unsafe.Sizeof(attr), 150 | ) 151 | if errno != 0 { 152 | log.Infof("Failed to get Map FD - ret %d and err %s", int(ret), errno) 153 | return 0, errno 154 | } 155 | fd := int(ret) 156 | runtime.KeepAlive(fd) 157 | return fd, nil 158 | } 159 | 160 | func GetProgFDFromID(mapID int) (int, error) { 161 | var log = logger.Get() 162 | attr := BpfShowAttr{ 163 | id: uint32(mapID), 164 | } 165 | ret, _, errno := unix.Syscall( 166 | unix.SYS_BPF, 167 | uintptr(constdef.BPF_PROG_GET_FD_BY_ID), 168 | uintptr(unsafe.Pointer(&attr)), 169 | unsafe.Sizeof(attr), 170 | ) 171 | if errno != 0 { 172 | log.Infof("Failed to get Map FD - ret %d and err %s", int(ret), errno) 173 | return 0, errno 174 | } 175 | fd := int(ret) 176 | runtime.KeepAlive(fd) 177 | return fd, nil 178 | } 179 | 180 | // Converts BPF instruction into bytes 181 | func (b *BPFInsn) ConvertBPFInstructionToByteStream() []byte { 182 | res := make([]byte, 8) 183 | res[0] = b.Code 184 | res[1] = (b.SrcReg << 4) | (b.DstReg & 0x0f) 185 | binary.LittleEndian.PutUint16(res[2:], uint16(b.Off)) 186 | binary.LittleEndian.PutUint32(res[4:], uint32(b.Imm)) 187 | 188 | return res 189 | } 190 | 191 | func Mount_bpf_fs() error { 192 | fmt.Println("Let's mount BPF FS") 193 | err := syscall.Mount("bpf", "/sys/fs/bpf", "bpf", 0, "mode=0700") 194 | if err != nil { 195 | fmt.Println("error mounting bpffs") 196 | } 197 | return err 198 | } 199 | 200 | func Unmount_bpf_fs() error { 201 | fmt.Println("Let's unmount BPF FS") 202 | err := syscall.Unmount("/sys/fs/bpf", 0) 203 | if err != nil { 204 | fmt.Println("error unmounting bpffs") 205 | } 206 | return err 207 | } 208 | 209 | func GetLogBufferSize() int { 210 | return math.MaxUint32 >> 8 211 | } 212 | -------------------------------------------------------------------------------- /pkg/xdp/generate_mocks.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may 4 | // not use this file except in compliance with the License. A copy of the 5 | // License is located at 6 | // 7 | // http://aws.amazon.com/apache2.0/ 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed 10 | // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | package xdp 14 | 15 | //go:generate go run github.com/golang/mock/mockgen -destination mocks/xdp_mocks.go . BpfXdp 16 | -------------------------------------------------------------------------------- /pkg/xdp/mocks/xdp_mocks.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/aws/aws-ebpf-sdk-go/pkg/xdp (interfaces: BpfXdp) 3 | 4 | // Package mock_xdp is a generated GoMock package. 5 | package mock_xdp 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | ) 12 | 13 | // MockBpfXdp is a mock of BpfXdp interface. 14 | type MockBpfXdp struct { 15 | ctrl *gomock.Controller 16 | recorder *MockBpfXdpMockRecorder 17 | } 18 | 19 | // MockBpfXdpMockRecorder is the mock recorder for MockBpfXdp. 20 | type MockBpfXdpMockRecorder struct { 21 | mock *MockBpfXdp 22 | } 23 | 24 | // NewMockBpfXdp creates a new mock instance. 25 | func NewMockBpfXdp(ctrl *gomock.Controller) *MockBpfXdp { 26 | mock := &MockBpfXdp{ctrl: ctrl} 27 | mock.recorder = &MockBpfXdpMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockBpfXdp) EXPECT() *MockBpfXdpMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // XDPAttach mocks base method. 37 | func (m *MockBpfXdp) XDPAttach(arg0 int) error { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "XDPAttach", arg0) 40 | ret0, _ := ret[0].(error) 41 | return ret0 42 | } 43 | 44 | // XDPAttach indicates an expected call of XDPAttach. 45 | func (mr *MockBpfXdpMockRecorder) XDPAttach(arg0 interface{}) *gomock.Call { 46 | mr.mock.ctrl.T.Helper() 47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "XDPAttach", reflect.TypeOf((*MockBpfXdp)(nil).XDPAttach), arg0) 48 | } 49 | 50 | // XDPDetach mocks base method. 51 | func (m *MockBpfXdp) XDPDetach() error { 52 | m.ctrl.T.Helper() 53 | ret := m.ctrl.Call(m, "XDPDetach") 54 | ret0, _ := ret[0].(error) 55 | return ret0 56 | } 57 | 58 | // XDPDetach indicates an expected call of XDPDetach. 59 | func (mr *MockBpfXdpMockRecorder) XDPDetach() *gomock.Call { 60 | mr.mock.ctrl.T.Helper() 61 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "XDPDetach", reflect.TypeOf((*MockBpfXdp)(nil).XDPDetach)) 62 | } 63 | -------------------------------------------------------------------------------- /pkg/xdp/xdp.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package xdp 16 | 17 | import ( 18 | constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" 19 | "github.com/aws/aws-ebpf-sdk-go/pkg/logger" 20 | "github.com/vishvananda/netlink" 21 | ) 22 | 23 | var log = logger.Get() 24 | 25 | type BpfXdp interface { 26 | XDPAttach(progFD int) error 27 | XDPDetach() error 28 | } 29 | 30 | var _ BpfXdp = &bpfXdp{} 31 | 32 | type bpfXdp struct { 33 | interfaceName string 34 | } 35 | 36 | func New(ifName string) BpfXdp { 37 | return &bpfXdp{ 38 | interfaceName: ifName, 39 | } 40 | 41 | } 42 | 43 | func (b *bpfXdp) XDPAttach(progFD int) error { 44 | 45 | link, err := netlink.LinkByName(b.interfaceName) 46 | if err != nil { 47 | log.Errorf("failed to obtain link info for %s : %v", b.interfaceName, err) 48 | return err 49 | } 50 | 51 | log.Infof("Attaching xdp prog %d to interface %s", progFD, b.interfaceName) 52 | 53 | if err := netlink.LinkSetXdpFdWithFlags(link, progFD, constdef.XDP_ATTACH_MODE_SKB); err != nil { 54 | log.Errorf("failed to setup xdp: %v", err) 55 | return err 56 | } 57 | log.Infof("Attached XDP to interface %s", b.interfaceName) 58 | 59 | return nil 60 | } 61 | 62 | func (b *bpfXdp) XDPDetach() error { 63 | 64 | link, err := netlink.LinkByName(b.interfaceName) 65 | if err != nil { 66 | log.Errorf("failed to obtain link info for %s : %v", b.interfaceName, err) 67 | return err 68 | } 69 | 70 | if err := netlink.LinkSetXdpFdWithFlags(link, -1, constdef.XDP_ATTACH_MODE_SKB); err != nil { 71 | log.Errorf("failed to setup xdp: %v", err) 72 | return err 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /pkg/xdp/xdp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | //limitations under the License. 14 | 15 | package xdp 16 | 17 | import ( 18 | "errors" 19 | "os" 20 | "testing" 21 | 22 | constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" 23 | "github.com/aws/aws-ebpf-sdk-go/pkg/elfparser" 24 | mock_ebpf_maps "github.com/aws/aws-ebpf-sdk-go/pkg/maps/mocks" 25 | mock_ebpf_progs "github.com/aws/aws-ebpf-sdk-go/pkg/progs/mocks" 26 | "github.com/aws/aws-ebpf-sdk-go/pkg/utils" 27 | "github.com/golang/mock/gomock" 28 | "github.com/stretchr/testify/assert" 29 | "github.com/vishvananda/netlink" 30 | ) 31 | 32 | const ( 33 | DUMMY_PROG_PREFIX = "test" 34 | DUMMY_PROG_NAME = "xdp_test_prog" 35 | ) 36 | 37 | type testMocks struct { 38 | path string 39 | ctrl *gomock.Controller 40 | ebpf_progs *mock_ebpf_progs.MockBpfProgAPIs 41 | ebpf_maps *mock_ebpf_maps.MockBpfMapAPIs 42 | xdpClient BpfXdp 43 | } 44 | 45 | func setup(t *testing.T, testPath string, interfaceName string) *testMocks { 46 | ctrl := gomock.NewController(t) 47 | return &testMocks{ 48 | path: testPath, 49 | ctrl: ctrl, 50 | ebpf_progs: mock_ebpf_progs.NewMockBpfProgAPIs(ctrl), 51 | ebpf_maps: mock_ebpf_maps.NewMockBpfMapAPIs(ctrl), 52 | xdpClient: New(interfaceName), 53 | } 54 | } 55 | 56 | func setupTest(interfaceNames []string, t *testing.T) { 57 | utils.Mount_bpf_fs() 58 | for _, interfaceName := range interfaceNames { 59 | linkAttr := netlink.LinkAttrs{Name: interfaceName} 60 | linkIFB := netlink.Ifb{} 61 | linkIFB.LinkAttrs = linkAttr 62 | if err := netlink.LinkAdd(&linkIFB); err != nil { 63 | assert.NoError(t, err) 64 | } 65 | } 66 | } 67 | 68 | func teardownTest(interfaceNames []string, t *testing.T, ignoreDelErr bool) { 69 | utils.Unmount_bpf_fs() 70 | //Cleanup link 71 | for _, interfaceName := range interfaceNames { 72 | linkAttr := netlink.LinkAttrs{Name: interfaceName} 73 | linkIFB := netlink.Ifb{} 74 | linkIFB.LinkAttrs = linkAttr 75 | if err := netlink.LinkDel(&linkIFB); err != nil && !ignoreDelErr { 76 | assert.NoError(t, err) 77 | } 78 | } 79 | } 80 | 81 | func deleteLinks(interfaceNames []string, t *testing.T) { 82 | //Cleanup link 83 | for _, interfaceName := range interfaceNames { 84 | linkAttr := netlink.LinkAttrs{Name: interfaceName} 85 | linkIFB := netlink.Ifb{} 86 | linkIFB.LinkAttrs = linkAttr 87 | if err := netlink.LinkDel(&linkIFB); err != nil { 88 | assert.NoError(t, err) 89 | } 90 | } 91 | } 92 | 93 | func TestTCXdpAttachDetach(t *testing.T) { 94 | if os.Getuid() != 0 { 95 | t.Skip("Test requires root privileges.") 96 | } 97 | 98 | interfaceName := "foo1" 99 | 100 | m := setup(t, "../../test-data/xdp.bpf.elf", interfaceName) 101 | defer m.ctrl.Finish() 102 | 103 | var interfaceNames []string 104 | interfaceNames = append(interfaceNames, interfaceName) 105 | setupTest(interfaceNames, t) 106 | defer teardownTest(interfaceNames, t, false) 107 | 108 | m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() 109 | m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() 110 | m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() 111 | m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() 112 | m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() 113 | m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() 114 | 115 | bpfSDKclient := elfparser.New() 116 | progInfo, _, err := bpfSDKclient.LoadBpfFile(m.path, DUMMY_PROG_PREFIX) 117 | if err != nil { 118 | assert.NoError(t, err) 119 | } 120 | pinPath := constdef.PROG_BPF_FS + DUMMY_PROG_PREFIX + "_" + DUMMY_PROG_NAME 121 | 122 | progFD := progInfo[pinPath].Program.ProgFD 123 | if err := m.xdpClient.XDPAttach(progFD); err != nil { 124 | assert.NoError(t, err) 125 | } 126 | 127 | if err := m.xdpClient.XDPDetach(); err != nil { 128 | assert.NoError(t, err) 129 | } 130 | } 131 | 132 | func TestNetLinkAPIs(t *testing.T) { 133 | 134 | netLinktests := []struct { 135 | name string 136 | interfaceName string 137 | overrideName bool 138 | overrideProg bool 139 | skipAttach bool 140 | want []int 141 | wantErr error 142 | }{ 143 | { 144 | name: "Failed Link By Name", 145 | interfaceName: "foo2", 146 | want: nil, 147 | overrideName: true, 148 | wantErr: errors.New("Link not found"), 149 | }, 150 | { 151 | name: "Invalid Program", 152 | interfaceName: "foo3", 153 | want: nil, 154 | overrideProg: true, 155 | wantErr: errors.New("invalid argument"), 156 | }, 157 | { 158 | name: "Detach without attach Program", 159 | interfaceName: "foo4", 160 | want: nil, 161 | skipAttach: true, 162 | wantErr: nil, 163 | }, 164 | } 165 | 166 | for _, tt := range netLinktests { 167 | t.Run(tt.name, func(t *testing.T) { 168 | m := setup(t, "../../test-data/xdp.bpf.elf", tt.interfaceName) 169 | defer m.ctrl.Finish() 170 | 171 | var interfaceNames []string 172 | interfaceNames = append(interfaceNames, tt.interfaceName) 173 | 174 | setupTest(interfaceNames, t) 175 | defer teardownTest(interfaceNames, t, tt.overrideName) 176 | 177 | m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() 178 | m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() 179 | m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() 180 | m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() 181 | m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() 182 | m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() 183 | 184 | bpfSDKclient := elfparser.New() 185 | progInfo, _, err := bpfSDKclient.LoadBpfFile(m.path, DUMMY_PROG_PREFIX) 186 | if err != nil { 187 | assert.NoError(t, err) 188 | } 189 | 190 | if tt.overrideName { 191 | deleteLinks(interfaceNames, t) 192 | } 193 | 194 | pinPath := constdef.PROG_BPF_FS + DUMMY_PROG_PREFIX + "_" + DUMMY_PROG_NAME 195 | 196 | progFD := progInfo[pinPath].Program.ProgFD 197 | 198 | if tt.overrideProg { 199 | progFD = 0 200 | } 201 | if !tt.skipAttach { 202 | err = m.xdpClient.XDPAttach(progFD) 203 | if tt.wantErr != nil { 204 | assert.EqualError(t, err, tt.wantErr.Error()) 205 | } else { 206 | assert.NoError(t, err) 207 | } 208 | } 209 | 210 | if tt.skipAttach { 211 | err = m.xdpClient.XDPDetach() 212 | if tt.wantErr != nil { 213 | assert.EqualError(t, err, tt.wantErr.Error()) 214 | } else { 215 | assert.NoError(t, err) 216 | } 217 | } 218 | }) 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /test-data/invalid_map.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | struct bpf_map_def_pvt { 7 | __u32 type; 8 | __u32 key_size; 9 | __u32 value_size; 10 | __u32 max_entries; 11 | __u32 map_flags; 12 | __u32 pinning; 13 | __u32 inner_map_fd; 14 | }; 15 | 16 | struct bpf_map_def_pvt policy_events; 17 | 18 | char _license[] SEC("license") = "GPL"; -------------------------------------------------------------------------------- /test-data/recoverydata.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define BPF_F_NO_PREALLOC 1 7 | #define PIN_GLOBAL_NS 2 8 | 9 | struct bpf_map_def_pvt { 10 | __u32 type; 11 | __u32 key_size; 12 | __u32 value_size; 13 | __u32 max_entries; 14 | __u32 map_flags; 15 | __u32 pinning; 16 | __u32 inner_map_fd; 17 | }; 18 | 19 | struct lpm_trie_key { 20 | __u32 prefixlen; 21 | __u8 ip[4]; 22 | }; 23 | 24 | struct lpm_trie_val { 25 | __u32 protocol; 26 | __u32 start_port; 27 | __u32 end_port; 28 | }; 29 | 30 | struct conntrack_key { 31 | __u32 src_ip; 32 | __u16 src_port; 33 | __u32 dest_ip; 34 | __u16 dest_port; 35 | __u8 protocol; 36 | }; 37 | 38 | struct conntrack_value { 39 | __u8 val[4]; 40 | }; 41 | 42 | struct bpf_map_def_pvt SEC("maps") ingress_map = { 43 | .type = BPF_MAP_TYPE_LPM_TRIE, 44 | .key_size =sizeof(struct lpm_trie_key), 45 | .value_size = sizeof(struct lpm_trie_val[16]), 46 | .max_entries = 100, 47 | .map_flags = BPF_F_NO_PREALLOC, 48 | .pinning = PIN_GLOBAL_NS, 49 | }; 50 | 51 | struct bpf_map_def_pvt SEC("maps") aws_conntrack_map = { 52 | .type = BPF_MAP_TYPE_LRU_HASH, 53 | .key_size =sizeof(struct conntrack_key), 54 | .value_size = sizeof(struct conntrack_value), 55 | .max_entries = 65536, 56 | .pinning = PIN_GLOBAL_NS, 57 | }; 58 | 59 | 60 | SEC("tc_cls") 61 | int handle_ingress(struct __sk_buff *skb) 62 | { 63 | struct lpm_trie_key trie_key; 64 | trie_key.prefixlen = 32; 65 | trie_key.ip[0] = 10; 66 | trie_key.ip[1] = 1; 67 | trie_key.ip[2] = 1; 68 | trie_key.ip[3] = 100; 69 | 70 | struct lpm_trie_val *trie_val; 71 | trie_val = bpf_map_lookup_elem(&ingress_map, &trie_key); 72 | if (trie_val == NULL) { 73 | return BPF_DROP; 74 | } 75 | return BPF_OK; 76 | } 77 | 78 | SEC("kprobe/nf_ct_delete") 79 | int conn_del(struct pt_regs *ctx) { 80 | struct nf_conn *ct = (struct nf_conn *) PT_REGS_PARM1(ctx); 81 | struct nf_conn new_ct = {}; 82 | bpf_probe_read(&new_ct, sizeof(new_ct), ct); 83 | struct conntrack_key flow_key = {}; 84 | __builtin_memset(&flow_key, 0, sizeof(flow_key)); 85 | 86 | struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX]; 87 | bpf_probe_read(&tuplehash, sizeof(tuplehash), &new_ct.tuplehash); 88 | 89 | bpf_probe_read(&flow_key.src_ip, sizeof(flow_key.src_ip), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip); 90 | bpf_probe_read(&flow_key.src_port, sizeof(flow_key.src_port), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.all); 91 | bpf_probe_read(&flow_key.dest_ip, sizeof(flow_key.dest_ip), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.ip); 92 | bpf_probe_read(&flow_key.dest_port, sizeof(flow_key.dest_port), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.all); 93 | bpf_probe_read(&flow_key.protocol, sizeof(flow_key.protocol), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum); 94 | 95 | return 0; 96 | } 97 | 98 | SEC("tracepoint/sched/sched_process_fork") 99 | int sched_process_fork(void *ctx) { 100 | return 0; 101 | } 102 | 103 | char _license[] SEC("license") = "GPL"; 104 | -------------------------------------------------------------------------------- /test-data/ring_buffer.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define BPF_MAP_TYPE_RINGBUF 27 7 | #define PIN_GLOBAL_NS 2 8 | 9 | struct bpf_map_def_pvt { 10 | __u32 type; 11 | __u32 key_size; 12 | __u32 value_size; 13 | __u32 max_entries; 14 | __u32 map_flags; 15 | __u32 pinning; 16 | __u32 inner_map_fd; 17 | }; 18 | 19 | struct bpf_map_def_pvt SEC("maps") test_events = { 20 | .type = BPF_MAP_TYPE_RINGBUF, 21 | .max_entries = 256 * 1024, 22 | .pinning = PIN_GLOBAL_NS, 23 | }; 24 | 25 | SEC("kprobe/__nf_conntrack_hash_insert") 26 | int conn_insert(struct pt_regs *ctx) { 27 | __u32 evt_test = 20; 28 | bpf_ringbuf_output(&test_events, &evt_test, sizeof(evt_test), 2); 29 | return 0; 30 | } 31 | 32 | char _license[] SEC("license") = "GPL"; -------------------------------------------------------------------------------- /test-data/tc.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | SEC("tc_cls") 7 | int handle_ingress(struct __sk_buff *skb) 8 | { 9 | return BPF_OK; 10 | } 11 | 12 | SEC("test") 13 | int handle_test_ingress(struct __sk_buff *skb) 14 | { 15 | return BPF_OK; 16 | } 17 | 18 | char _license[] SEC("license") = "GPL"; -------------------------------------------------------------------------------- /test-data/tc.ingress.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define BPF_F_NO_PREALLOC 1 7 | #define PIN_GLOBAL_NS 2 8 | #define BPF_MAP_TYPE_RINGBUF 27 9 | 10 | struct bpf_map_def_pvt { 11 | __u32 type; 12 | __u32 key_size; 13 | __u32 value_size; 14 | __u32 max_entries; 15 | __u32 map_flags; 16 | __u32 pinning; 17 | __u32 inner_map_fd; 18 | }; 19 | 20 | struct lpm_trie_key { 21 | __u32 prefixlen; 22 | __u8 ip[4]; 23 | }; 24 | 25 | struct lpm_trie_val { 26 | __u32 protocol; 27 | __u32 start_port; 28 | __u32 end_port; 29 | }; 30 | 31 | struct conntrack_key { 32 | __u32 src_ip; 33 | __u16 src_port; 34 | __u32 dest_ip; 35 | __u16 dest_port; 36 | __u8 protocol; 37 | }; 38 | 39 | struct conntrack_value { 40 | __u8 val[4]; 41 | }; 42 | 43 | struct data_t { 44 | __u32 src_ip; 45 | __u32 src_port; 46 | __u32 dest_ip; 47 | __u32 dest_port; 48 | __u32 protocol; 49 | __u32 verdict; 50 | }; 51 | 52 | 53 | struct bpf_map_def_pvt SEC("maps") ingress_map = { 54 | .type = BPF_MAP_TYPE_LPM_TRIE, 55 | .key_size =sizeof(struct lpm_trie_key), 56 | .value_size = sizeof(struct lpm_trie_val[16]), 57 | .max_entries = 100, 58 | .map_flags = BPF_F_NO_PREALLOC, 59 | .pinning = PIN_GLOBAL_NS, 60 | }; 61 | 62 | struct bpf_map_def_pvt SEC("maps") aws_conntrack_map = { 63 | .type = BPF_MAP_TYPE_LRU_HASH, 64 | .key_size =sizeof(struct conntrack_key), 65 | .value_size = sizeof(struct conntrack_value), 66 | .max_entries = 65536, 67 | .pinning = PIN_GLOBAL_NS, 68 | }; 69 | 70 | struct bpf_map_def_pvt SEC("maps") policy_events = { 71 | .type = BPF_MAP_TYPE_RINGBUF, 72 | .max_entries = 256 * 1024, 73 | .pinning = PIN_GLOBAL_NS, 74 | }; 75 | 76 | SEC("tc_cls") 77 | int handle_ingress(struct __sk_buff *skb) 78 | { 79 | struct lpm_trie_key trie_key; 80 | trie_key.prefixlen = 32; 81 | trie_key.ip[0] = 10; 82 | trie_key.ip[1] = 1; 83 | trie_key.ip[2] = 1; 84 | trie_key.ip[3] = 100; 85 | 86 | struct lpm_trie_val *trie_val; 87 | trie_val = bpf_map_lookup_elem(&ingress_map, &trie_key); 88 | if (trie_val == NULL) { 89 | return BPF_DROP; 90 | } 91 | return BPF_OK; 92 | } 93 | 94 | SEC("kprobe/nf_ct_delete") 95 | int conn_del(struct pt_regs *ctx) { 96 | struct nf_conn *ct = (struct nf_conn *) PT_REGS_PARM1(ctx); 97 | struct nf_conn new_ct = {}; 98 | bpf_probe_read(&new_ct, sizeof(new_ct), ct); 99 | struct conntrack_key flow_key = {}; 100 | __builtin_memset(&flow_key, 0, sizeof(flow_key)); 101 | 102 | struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX]; 103 | bpf_probe_read(&tuplehash, sizeof(tuplehash), &new_ct.tuplehash); 104 | 105 | bpf_probe_read(&flow_key.src_ip, sizeof(flow_key.src_ip), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip); 106 | bpf_probe_read(&flow_key.src_port, sizeof(flow_key.src_port), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.all); 107 | bpf_probe_read(&flow_key.dest_ip, sizeof(flow_key.dest_ip), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.ip); 108 | bpf_probe_read(&flow_key.dest_port, sizeof(flow_key.dest_port), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.all); 109 | bpf_probe_read(&flow_key.protocol, sizeof(flow_key.protocol), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum); 110 | 111 | struct data_t evt = {}; 112 | evt.src_ip = flow_key.src_ip; 113 | evt.src_port = flow_key.src_port; 114 | evt.dest_ip = flow_key.dest_ip; 115 | evt.dest_port = flow_key.dest_port; 116 | evt.protocol = flow_key.protocol; 117 | bpf_ringbuf_output(&policy_events, &evt, sizeof(evt), 2); 118 | return 0; 119 | } 120 | 121 | SEC("tracepoint/sched/sched_process_fork") 122 | int sched_process_fork(void *ctx) { 123 | return 0; 124 | } 125 | 126 | char _license[] SEC("license") = "GPL"; 127 | -------------------------------------------------------------------------------- /test-data/test-kprobe.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | SEC("kprobe/oom_kill_process") 7 | int oom_kill(struct pt_regs *ctx) { 8 | return 0; 9 | } 10 | 11 | 12 | char _license[] SEC("license") = "GPL"; -------------------------------------------------------------------------------- /test-data/test.map.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define PIN_GLOBAL_NS 2 7 | 8 | struct conntrack_key { 9 | __u32 src_ip; 10 | __u16 src_port; 11 | __u32 dest_ip; 12 | __u16 dest_port; 13 | __u8 protocol; 14 | }; 15 | 16 | struct conntrack_value { 17 | __u8 val[4]; 18 | }; 19 | 20 | 21 | struct bpf_map_def_pvt { 22 | __u32 type; 23 | __u32 key_size; 24 | __u32 value_size; 25 | __u32 max_entries; 26 | __u32 map_flags; 27 | __u32 pinning; 28 | __u32 inner_map_fd; 29 | }; 30 | 31 | struct bpf_map_def_pvt SEC("maps") aws_conntrack_map = { 32 | .type = BPF_MAP_TYPE_LRU_HASH, 33 | .key_size =sizeof(struct conntrack_key), 34 | .value_size = sizeof(struct conntrack_value), 35 | .max_entries = 65536, 36 | .pinning = PIN_GLOBAL_NS, 37 | }; 38 | 39 | char _license[] SEC("license") = "GPL"; -------------------------------------------------------------------------------- /test-data/test_license.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define PIN_GLOBAL_NS 2 7 | #define BPF_MAP_TYPE_RINGBUF 27 8 | 9 | struct bpf_map_def_pvt { 10 | __u32 type; 11 | __u32 key_size; 12 | __u32 value_size; 13 | __u32 max_entries; 14 | __u32 map_flags; 15 | __u32 pinning; 16 | __u32 inner_map_fd; 17 | }; 18 | 19 | struct bpf_map_def_pvt SEC("maps") policy_events = { 20 | .type = BPF_MAP_TYPE_RINGBUF, 21 | .max_entries = 256 * 1024, 22 | .pinning = PIN_GLOBAL_NS, 23 | }; -------------------------------------------------------------------------------- /test-data/xdp.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | SEC("xdp") 7 | int xdp_test_prog(struct xdp_md *ctx) 8 | { 9 | return XDP_DROP; 10 | } 11 | char _license[] SEC("license") = "GPL"; -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build-test build-test-bpf 2 | 3 | export GOPROXY = direct 4 | 5 | UNAME_ARCH = $(shell uname -m) 6 | ARCH = $(lastword $(subst :, ,$(filter $(UNAME_ARCH):%,x86_64:x86 aarch64:arm64))) 7 | 8 | BUILD_MODE ?= -buildmode=pie 9 | build-test: BUILD_FLAGS = $(BUILD_MODE) -ldflags '-s -w' 10 | build-test: ## Build the VPC CNI plugin agent using the host's Go toolchain. 11 | go build $(BUILD_FLAGS) -o main main.go 12 | 13 | # Build BPF 14 | CLANG := clang 15 | CLANG_INCLUDE := -I../../.. 16 | CFLAGS := -g -O2 -Wall -fpie -target bpf -DCORE -D__BPF_TRACING__ -D__TARGET_ARCH_$(ARCH) 17 | SRCDIR := c 18 | TARGETS := $(patsubst %.c, %.elf, $(shell find $(SRCDIR) -type f -name "*.c")) 19 | 20 | build-test-bpf: ## Build BPF 21 | build-test-bpf: vmlinuxh 22 | build-test-bpf: $(TARGETS) 23 | 24 | %.elf: %.c 25 | $(CLANG) $(CLANG_INCLUDE) $(CFLAGS) -c $< -o $@ 26 | 27 | 28 | vmlinuxh: 29 | bpftool btf dump file /sys/kernel/btf/vmlinux format c > $(abspath c/vmlinux.h) 30 | 31 | 32 | ##@ Run Tests 33 | # Run tests 34 | run-test: vmlinuxh 35 | run-test: build-test-bpf 36 | run-test: build-test 37 | run-test: export AWS_EBPF_SDK_LOG_FILE=stdout 38 | run-test: ## Run unit tests 39 | ./main 40 | -------------------------------------------------------------------------------- /test/c/test-map.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define BPF_F_NO_PREALLOC 1 7 | #define PIN_GLOBAL_NS 2 8 | 9 | struct bpf_map_def_pvt { 10 | __u32 type; 11 | __u32 key_size; 12 | __u32 value_size; 13 | __u32 max_entries; 14 | __u32 map_flags; 15 | __u32 pinning; 16 | __u32 inner_map_fd; 17 | }; 18 | 19 | struct lpm_trie_key { 20 | __u32 prefixlen; 21 | __u32 ip; 22 | }; 23 | 24 | struct bpf_map_def_pvt SEC("maps") ingress_map = { 25 | .type = BPF_MAP_TYPE_LPM_TRIE, 26 | .key_size =sizeof(struct lpm_trie_key), 27 | .value_size = sizeof(int), 28 | .max_entries = 65536, 29 | .map_flags = BPF_F_NO_PREALLOC, 30 | .pinning = PIN_GLOBAL_NS, 31 | }; 32 | 33 | char _license[] SEC("license") = "GPL"; 34 | -------------------------------------------------------------------------------- /test/c/test-tracepoint.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | struct sched_process_fork_t { 7 | unsigned short common_type; 8 | unsigned char common_flags; 9 | unsigned char common_preempt_count; 10 | int common_pid; 11 | char parent_comm[16]; 12 | u32 parent_pid; 13 | char child_comm[16]; 14 | u32 child_pid; 15 | }; 16 | 17 | SEC("tracepoint/sched/sched_process_fork") 18 | int sched_process_fork(struct sched_process_fork_t *ctx) { 19 | return 0; 20 | } -------------------------------------------------------------------------------- /test/c/test-v6.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define BPF_F_NO_PREALLOC 1 7 | #define ETH_HLEN 14 8 | #define BPF_MAP_ID_INGRESS_MAP 2 9 | #define MAX_RULES 256 10 | #define MIN_RULES 128 11 | #define PIN_GLOBAL_NS 2 12 | #define RESERVED_IP_PROTOCOL 255 13 | #define ANY_IP_PROTOCOL 254 14 | #define ANY_PORT 0 15 | 16 | struct bpf_map_def_pvt { 17 | __u32 type; 18 | __u32 key_size; 19 | __u32 value_size; 20 | __u32 max_entries; 21 | __u32 map_flags; 22 | __u32 pinning; 23 | __u32 inner_map_fd; 24 | }; 25 | 26 | struct keystruct 27 | { 28 | __u32 prefix_len; 29 | __u8 ip[16]; 30 | }; 31 | 32 | struct lpm_trie_key { 33 | __u32 prefixlen; 34 | __u8 ip[16]; 35 | }; 36 | 37 | struct lpm_trie_val { 38 | __u32 protocol; 39 | __u32 start_port; 40 | __u32 end_port; 41 | }; 42 | 43 | struct bpf_map_def_pvt SEC("maps") ingress_map = { 44 | .type = BPF_MAP_TYPE_LPM_TRIE, 45 | .key_size =sizeof(struct lpm_trie_key), 46 | .value_size = sizeof(struct lpm_trie_val[30]), 47 | .max_entries = 65536, 48 | .map_flags = BPF_F_NO_PREALLOC, 49 | .pinning = PIN_GLOBAL_NS, 50 | }; 51 | 52 | 53 | SEC("tc_cls") 54 | int handle_ingress(struct __sk_buff *skb) 55 | { 56 | struct keystruct trie_key; 57 | struct lpm_trie_val *trie_val; 58 | __u16 l4_src_port = 0; 59 | __u16 l4_dst_port = 0; 60 | void *data_end = (void *)(long)skb->data_end; 61 | void *data = (void *)(long)skb->data; 62 | 63 | 64 | struct ethhdr *ether = data; 65 | if (data + sizeof(*ether) > data_end) { 66 | return BPF_OK; 67 | } 68 | 69 | if (ether->h_proto == 0xdd86) { // htons(ETH_P_IPV6) -> 0x086ddU 70 | data += sizeof(*ether); 71 | struct ipv6hdr *ip = data; 72 | struct tcphdr *l4_tcp_hdr = data + sizeof(struct ipv6hdr); 73 | struct udphdr *l4_udp_hdr = data + sizeof(struct ipv6hdr); 74 | struct sctphdr *l4_sctp_hdr = data + sizeof(struct ipv6hdr); 75 | 76 | if (data + sizeof(*ip) > data_end) { 77 | return BPF_OK; 78 | } 79 | 80 | if (ip->version != 6) { 81 | return BPF_OK; 82 | } 83 | 84 | //ICMPv6 - Neighbor Discovery Packets 85 | if (ip->nexthdr == 58) { 86 | return BPF_OK; 87 | } 88 | 89 | switch (ip->nexthdr) { 90 | case IPPROTO_TCP: 91 | if (data + sizeof(*ip) + sizeof(*l4_tcp_hdr) > data_end) { 92 | return BPF_OK; 93 | } 94 | l4_src_port = (((((unsigned short)(l4_tcp_hdr->source) & 0xFF)) << 8) | (((unsigned short)(l4_tcp_hdr->source) & 0xFF00) >> 8)); 95 | l4_dst_port = (((((unsigned short)(l4_tcp_hdr->dest) & 0xFF)) << 8) | (((unsigned short)(l4_tcp_hdr->dest) & 0xFF00) >> 8)); 96 | break; 97 | case IPPROTO_UDP: 98 | if (data + sizeof(*ip) + sizeof(*l4_udp_hdr) > data_end) { 99 | return BPF_OK; 100 | } 101 | l4_src_port = (((((unsigned short)(l4_udp_hdr->source) & 0xFF)) << 8) | (((unsigned short)(l4_udp_hdr->source) & 0xFF00) >> 8)); 102 | l4_dst_port = (((((unsigned short)(l4_udp_hdr->dest) & 0xFF)) << 8) | (((unsigned short)(l4_udp_hdr->dest) & 0xFF00) >> 8)); 103 | break; 104 | case IPPROTO_SCTP: 105 | if (data + sizeof(*ip) + sizeof(*l4_sctp_hdr) > data_end) { 106 | return BPF_OK; 107 | } 108 | l4_src_port = (((((unsigned short)(l4_sctp_hdr->source) & 0xFF)) << 8) | (((unsigned short)(l4_sctp_hdr->source) & 0xFF00) >> 8)); 109 | l4_dst_port = (((((unsigned short)(l4_sctp_hdr->dest) & 0xFF)) << 8) | (((unsigned short)(l4_sctp_hdr->dest) & 0xFF00) >> 8)); 110 | break; 111 | } 112 | 113 | trie_key.prefix_len = 128; 114 | //Fill the IP Key to be used for lookup 115 | for (int i=0; i<16; i++){ 116 | trie_key.ip[i] = ip->saddr.in6_u.u6_addr8[i]; 117 | } 118 | 119 | //Check if it's in the allowed list 120 | trie_val = bpf_map_lookup_elem(&ingress_map, &trie_key); 121 | if (trie_val == NULL) { 122 | return BPF_DROP; 123 | } 124 | 125 | for (int i=0; i<30; i++, trie_val++){ 126 | if (trie_val->protocol == RESERVED_IP_PROTOCOL) { 127 | return BPF_DROP; 128 | } 129 | 130 | if ((trie_val->protocol == ANY_IP_PROTOCOL) || (trie_val->protocol == ip->nexthdr && 131 | ((trie_val->start_port == ANY_PORT) || (l4_dst_port == trie_val->start_port) || 132 | (l4_dst_port > trie_val->start_port && l4_dst_port <= trie_val->end_port)))) { 133 | return BPF_OK; 134 | } 135 | } 136 | return BPF_DROP; 137 | } 138 | return BPF_OK; 139 | 140 | } 141 | 142 | char _license[] SEC("license") = "GPL"; 143 | -------------------------------------------------------------------------------- /test/c/test.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define BPF_F_NO_PREALLOC 1 7 | #define ETH_HLEN 14 8 | #define BPF_MAP_ID_INGRESS_MAP 2 9 | #define MAX_RULES 256 10 | #define MIN_RULES 128 11 | #define PIN_GLOBAL_NS 2 12 | #define RESERVED_IP_PROTOCOL 255 13 | #define ANY_IP_PROTOCOL 254 14 | #define ANY_PORT 0 15 | 16 | struct bpf_map_def_pvt { 17 | __u32 type; 18 | __u32 key_size; 19 | __u32 value_size; 20 | __u32 max_entries; 21 | __u32 map_flags; 22 | __u32 pinning; 23 | __u32 inner_map_fd; 24 | }; 25 | 26 | struct keystruct 27 | { 28 | __u32 prefix_len; 29 | __u8 ip[4]; 30 | }; 31 | 32 | struct lpm_trie_key { 33 | __u32 prefixlen; 34 | __u32 ip; 35 | }; 36 | 37 | struct lpm_trie_val { 38 | __u32 protocol; 39 | __u32 start_port; 40 | __u32 end_port; 41 | }; 42 | 43 | struct bpf_map_def_pvt SEC("maps") ingress_map = { 44 | .type = BPF_MAP_TYPE_LPM_TRIE, 45 | .key_size =sizeof(struct lpm_trie_key), 46 | .value_size = sizeof(struct lpm_trie_val[30]), 47 | .max_entries = 65536, 48 | .map_flags = BPF_F_NO_PREALLOC, 49 | .pinning = PIN_GLOBAL_NS, 50 | }; 51 | 52 | 53 | SEC("tc_cls") 54 | int handle_ingress(struct __sk_buff *skb) 55 | { 56 | struct keystruct trie_key; 57 | struct lpm_trie_val *trie_val; 58 | __u32 l4_src_port = 0; 59 | __u32 l4_dst_port = 0; 60 | void *data_end = (void *)(long)skb->data_end; 61 | void *data = (void *)(long)skb->data; 62 | 63 | struct ethhdr *ether = data; 64 | if (data + sizeof(*ether) > data_end) { 65 | return BPF_OK; 66 | } 67 | 68 | if (ether->h_proto == 0x08U) { // htons(ETH_P_IP) -> 0x08U 69 | data += sizeof(*ether); 70 | struct iphdr *ip = data; 71 | struct tcphdr *l4_tcp_hdr = data + sizeof(struct iphdr); 72 | struct udphdr *l4_udp_hdr = data + sizeof(struct iphdr); 73 | struct sctphdr *l4_sctp_hdr = data + sizeof(struct iphdr); 74 | 75 | if (data + sizeof(*ip) > data_end) { 76 | return BPF_OK; 77 | } 78 | if (ip->version != 4) { 79 | return BPF_OK; 80 | } 81 | 82 | switch (ip->protocol) { 83 | case IPPROTO_TCP: 84 | if (data + sizeof(*ip) + sizeof(*l4_tcp_hdr) > data_end) { 85 | return BPF_OK; 86 | } 87 | l4_src_port = (((((unsigned short)(l4_tcp_hdr->source) & 0xFF)) << 8) | (((unsigned short)(l4_tcp_hdr->source) & 0xFF00) >> 8)); 88 | l4_dst_port = (((((unsigned short)(l4_tcp_hdr->dest) & 0xFF)) << 8) | (((unsigned short)(l4_tcp_hdr->dest) & 0xFF00) >> 8)); 89 | break; 90 | case IPPROTO_UDP: 91 | if (data + sizeof(*ip) + sizeof(*l4_udp_hdr) > data_end) { 92 | return BPF_OK; 93 | } 94 | l4_src_port = (((((unsigned short)(l4_udp_hdr->source) & 0xFF)) << 8) | (((unsigned short)(l4_udp_hdr->source) & 0xFF00) >> 8)); 95 | l4_dst_port = (((((unsigned short)(l4_udp_hdr->dest) & 0xFF)) << 8) | (((unsigned short)(l4_udp_hdr->dest) & 0xFF00) >> 8)); 96 | break; 97 | case IPPROTO_SCTP: 98 | if (data + sizeof(*ip) + sizeof(*l4_sctp_hdr) > data_end) { 99 | return BPF_OK; 100 | } 101 | l4_src_port = (((((unsigned short)(l4_sctp_hdr->source) & 0xFF)) << 8) | (((unsigned short)(l4_sctp_hdr->source) & 0xFF00) >> 8)); 102 | l4_dst_port = (((((unsigned short)(l4_sctp_hdr->dest) & 0xFF)) << 8) | (((unsigned short)(l4_sctp_hdr->dest) & 0xFF00) >> 8)); 103 | break; 104 | } 105 | 106 | trie_key.prefix_len = 32; 107 | trie_key.ip[0] = ip->saddr & 0xff; 108 | trie_key.ip[1] = (ip->saddr >> 8) & 0xff; 109 | trie_key.ip[2] = (ip->saddr >> 16) & 0xff; 110 | trie_key.ip[3] = (ip->saddr >> 24) & 0xff; 111 | 112 | trie_val = bpf_map_lookup_elem(&ingress_map, &trie_key); 113 | if (trie_val == NULL) { 114 | return BPF_DROP; 115 | } 116 | 117 | for (int i=0; i<30; i++, trie_val++){ 118 | if (trie_val->protocol == RESERVED_IP_PROTOCOL) { 119 | return BPF_DROP; 120 | } 121 | 122 | if ((trie_val->protocol == ANY_IP_PROTOCOL) || (trie_val->protocol == ip->protocol && 123 | ((trie_val->start_port == ANY_PORT) || (l4_dst_port == trie_val->start_port) || 124 | (l4_dst_port > trie_val->start_port && l4_dst_port <= trie_val->end_port)))) { 125 | return BPF_OK; 126 | } 127 | } 128 | 129 | return BPF_OK; 130 | 131 | } 132 | return BPF_DROP; 133 | } 134 | 135 | char _license[] SEC("license") = "GPL"; 136 | -------------------------------------------------------------------------------- /test/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jayanthvn/pure-gobpf/test 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.7 6 | 7 | require ( 8 | github.com/aws/aws-ebpf-sdk-go v0.0.0-20230616053809-009e64b9692e 9 | github.com/fatih/color v1.15.0 10 | ) 11 | 12 | require ( 13 | github.com/mattn/go-colorable v0.1.13 // indirect 14 | github.com/mattn/go-isatty v0.0.17 // indirect 15 | github.com/vishvananda/netlink v1.3.0 // indirect 16 | github.com/vishvananda/netns v0.0.4 // indirect 17 | go.uber.org/multierr v1.10.0 // indirect 18 | go.uber.org/zap v1.27.0 // indirect 19 | golang.org/x/sys v0.30.0 // indirect 20 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 21 | ) 22 | 23 | replace github.com/aws/aws-ebpf-sdk-go => ../ 24 | -------------------------------------------------------------------------------- /test/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 4 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 5 | github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 6 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 7 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 8 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 9 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 10 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 11 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 15 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 16 | github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= 17 | github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= 18 | github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= 19 | github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 20 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 21 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 22 | go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= 23 | go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 24 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 25 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 26 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 27 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 28 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 29 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 30 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 31 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 32 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 33 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 34 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 35 | -------------------------------------------------------------------------------- /test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "net" 7 | "os" 8 | "strings" 9 | "syscall" 10 | "text/tabwriter" 11 | "unsafe" 12 | 13 | goelf "github.com/aws/aws-ebpf-sdk-go/pkg/elfparser" 14 | ebpf_tc "github.com/aws/aws-ebpf-sdk-go/pkg/tc" 15 | "github.com/fatih/color" 16 | ) 17 | 18 | type testFunc struct { 19 | Name string 20 | Func func() error 21 | } 22 | 23 | func mount_bpf_fs() error { 24 | fmt.Println("Let's mount BPF FS") 25 | err := syscall.Mount("bpf", "/sys/fs/bpf", "bpf", 0, "mode=0700") 26 | if err != nil { 27 | fmt.Println("error mounting bpffs: %v", err) 28 | } 29 | return err 30 | } 31 | 32 | func unmount_bpf_fs() error { 33 | fmt.Println("Let's unmount BPF FS") 34 | err := syscall.Unmount("/sys/fs/bpf", 0) 35 | if err != nil { 36 | fmt.Println("error unmounting bpffs: %v", err) 37 | } 38 | return err 39 | } 40 | 41 | func print_failure() { 42 | fmt.Println("\x1b[31mFAILED\x1b[0m") 43 | } 44 | 45 | func print_success() { 46 | fmt.Println("\x1b[32mSUCCESS!\x1b[0m") 47 | } 48 | 49 | func print_message(message string) { 50 | color := "\x1b[33m" 51 | formattedMessage := fmt.Sprintf("%s%s\x1b[0m", color, message) 52 | fmt.Println(formattedMessage) 53 | } 54 | 55 | func main() { 56 | fmt.Println("\x1b[34mStart testing SDK.........\x1b[0m") 57 | mount_bpf_fs() 58 | testFunctions := []testFunc{ 59 | {Name: "Test loading Program", Func: TestLoadProg}, 60 | {Name: "Test loading V6 Program", Func: TestLoadv6Prog}, 61 | {Name: "Test loading TC filter", Func: TestLoadTCfilter}, 62 | {Name: "Test loading Maps without Program", Func: TestLoadMapWithNoProg}, 63 | {Name: "Test loading Map operations", Func: TestMapOperations}, 64 | {Name: "Test updating Map size", Func: TestLoadMapWithCustomSize}, 65 | {Name: "Test bulk Map operations", Func: TestBulkMapOperations}, 66 | {Name: "Test bulk refresh Map operations", Func: TestBulkRefreshMapOperations}, 67 | } 68 | 69 | testSummary := make(map[string]string) 70 | 71 | for _, fn := range testFunctions { 72 | message := "Testing " + fn.Name 73 | print_message(message) 74 | err := fn.Func() 75 | if err != nil { 76 | print_failure() 77 | testSummary[fn.Name] = "FAILED" 78 | } else { 79 | print_success() 80 | testSummary[fn.Name] = "SUCCESS" 81 | } 82 | } 83 | unmount_bpf_fs() 84 | 85 | fmt.Println(color.MagentaString("===========================================================")) 86 | fmt.Println(color.MagentaString(" TESTING SUMMARY ")) 87 | fmt.Println(color.MagentaString("===========================================================")) 88 | summary := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', tabwriter.AlignRight|tabwriter.Debug) 89 | header := strings.Join([]string{color.YellowString("TestCase"), color.YellowString("Result")}, "\t") 90 | 91 | fmt.Fprintln(summary, header) 92 | 93 | for testName, testStatus := range testSummary { 94 | if testStatus == "FAILED" { 95 | fmt.Fprintf(summary, "%s\t%s\n", testName, color.RedString(testStatus)) 96 | } 97 | if testStatus == "SUCCESS" { 98 | fmt.Fprintf(summary, "%s\t%s\n", testName, color.GreenString(testStatus)) 99 | } 100 | } 101 | summary.Flush() 102 | fmt.Println(color.MagentaString("===========================================================")) 103 | } 104 | 105 | func TestLoadProg() error { 106 | gosdkClient := goelf.New() 107 | progInfo, _, err := gosdkClient.LoadBpfFile("c/test.bpf.elf", "test") 108 | if err != nil { 109 | fmt.Println("Load BPF failed", "err:", err) 110 | return err 111 | } 112 | 113 | for pinPath, _ := range progInfo { 114 | fmt.Println("Prog Info: ", "Pin Path: ", pinPath) 115 | } 116 | return nil 117 | } 118 | 119 | func TestLoadv6Prog() error { 120 | gosdkClient := goelf.New() 121 | progInfo, _, err := gosdkClient.LoadBpfFile("c/test-v6.bpf.elf", "test") 122 | if err != nil { 123 | fmt.Println("Load BPF failed", "err:", err) 124 | return err 125 | } 126 | 127 | for pinPath, _ := range progInfo { 128 | fmt.Println("Prog Info: ", "Pin Path: ", pinPath) 129 | } 130 | return nil 131 | } 132 | 133 | func TestLoadMapWithNoProg() error { 134 | gosdkClient := goelf.New() 135 | _, loadedMap, err := gosdkClient.LoadBpfFile("c/test-map.bpf.elf", "test") 136 | if err != nil { 137 | fmt.Println("Load BPF failed", "err:", err) 138 | return err 139 | } 140 | 141 | for mapName, _ := range loadedMap { 142 | fmt.Println("Map Info: ", "Name: ", mapName) 143 | } 144 | return nil 145 | 146 | } 147 | 148 | func TestMapOperations() error { 149 | gosdkClient := goelf.New() 150 | _, loadedMap, err := gosdkClient.LoadBpfFile("c/test-map.bpf.elf", "operations") 151 | if err != nil { 152 | fmt.Println("Load BPF failed", "err:", err) 153 | return err 154 | } 155 | 156 | for mapName, _ := range loadedMap { 157 | fmt.Println("Map Info: ", "Name: ", mapName) 158 | } 159 | 160 | type BPFInetTrieKey struct { 161 | Prefixlen uint32 162 | Addr [4]byte 163 | } 164 | dummykey := BPFInetTrieKey{ 165 | Prefixlen: 32, 166 | Addr: [4]byte{192, 168, 0, 0}, 167 | } 168 | dummyvalue := uint32(40) 169 | 170 | dummykey2 := BPFInetTrieKey{ 171 | Prefixlen: 32, 172 | Addr: [4]byte{192, 168, 0, 1}, 173 | } 174 | dummyvalue2 := uint32(30) 175 | 176 | if mapToUpdate, ok := loadedMap["ingress_map"]; ok { 177 | fmt.Println("Found map to Create entry") 178 | err = mapToUpdate.CreateMapEntry(uintptr(unsafe.Pointer((&dummykey))), uintptr(unsafe.Pointer((&dummyvalue)))) 179 | if err != nil { 180 | fmt.Println("Unable to Insert into eBPF map: ", err) 181 | return err 182 | } 183 | dummyvalue := uint32(20) 184 | 185 | fmt.Println("Found map to Update entry") 186 | err = mapToUpdate.UpdateMapEntry(uintptr(unsafe.Pointer((&dummykey))), uintptr(unsafe.Pointer((&dummyvalue)))) 187 | if err != nil { 188 | fmt.Println("Unable to Update into eBPF map: ", err) 189 | return err 190 | } 191 | 192 | var mapVal uint32 193 | fmt.Println("Get map entry") 194 | err := mapToUpdate.GetMapEntry(uintptr(unsafe.Pointer(&dummykey)), uintptr(unsafe.Pointer(&mapVal))) 195 | if err != nil { 196 | fmt.Println("Unable to get map entry: ", err) 197 | return err 198 | } else { 199 | fmt.Println("Found the map entry and value ", mapVal) 200 | } 201 | 202 | fmt.Println("Found map to Create dummy2 entry") 203 | err = mapToUpdate.CreateMapEntry(uintptr(unsafe.Pointer((&dummykey2))), uintptr(unsafe.Pointer((&dummyvalue2)))) 204 | if err != nil { 205 | fmt.Println("Unable to Insert into eBPF map: ", err) 206 | return err 207 | } 208 | 209 | fmt.Println("Try get first key") 210 | nextKey := BPFInetTrieKey{} 211 | err = mapToUpdate.GetNextMapEntry(uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(&nextKey))) 212 | if err != nil { 213 | fmt.Println("Unable to get next key: ", err) 214 | return err 215 | } else { 216 | fmt.Println("Get map entry of next key") 217 | var newMapVal uint32 218 | err := mapToUpdate.GetMapEntry(uintptr(unsafe.Pointer(&nextKey)), uintptr(unsafe.Pointer(&newMapVal))) 219 | if err != nil { 220 | fmt.Println("Unable to get next map entry: ", err) 221 | return err 222 | } else { 223 | fmt.Println("Found the next map entry and value ", newMapVal) 224 | } 225 | } 226 | 227 | fmt.Println("Try next key") 228 | nextKey = BPFInetTrieKey{} 229 | err = mapToUpdate.GetNextMapEntry(uintptr(unsafe.Pointer(&dummykey)), uintptr(unsafe.Pointer(&nextKey))) 230 | if err != nil { 231 | fmt.Println("Unable to get next key: ", err) 232 | return err 233 | } else { 234 | fmt.Println("Get map entry of next key") 235 | var newMapVal uint32 236 | err := mapToUpdate.GetMapEntry(uintptr(unsafe.Pointer(&nextKey)), uintptr(unsafe.Pointer(&newMapVal))) 237 | if err != nil { 238 | fmt.Println("Unable to get next map entry: ", err) 239 | return err 240 | } else { 241 | fmt.Println("Found the next map entry and value ", newMapVal) 242 | } 243 | } 244 | 245 | fmt.Println("Dump all entries in map") 246 | 247 | iterKey := BPFInetTrieKey{} 248 | iterNextKey := BPFInetTrieKey{} 249 | 250 | err = mapToUpdate.GetFirstMapEntry(uintptr(unsafe.Pointer(&iterKey))) 251 | if err != nil { 252 | fmt.Println("Unable to get First key: ", err) 253 | return err 254 | } else { 255 | for { 256 | var newMapVal uint32 257 | err = mapToUpdate.GetMapEntry(uintptr(unsafe.Pointer(&iterKey)), uintptr(unsafe.Pointer(&newMapVal))) 258 | if err != nil { 259 | fmt.Println("Unable to get map entry: ", err) 260 | return err 261 | } else { 262 | fmt.Println("Found the map entry and value ", newMapVal) 263 | } 264 | 265 | err = mapToUpdate.GetNextMapEntry(uintptr(unsafe.Pointer(&iterKey)), uintptr(unsafe.Pointer(&iterNextKey))) 266 | if err != nil { 267 | fmt.Println("Done searching") 268 | break 269 | } 270 | iterKey = iterNextKey 271 | } 272 | } 273 | 274 | fmt.Println("Found map to Delete entry") 275 | err = mapToUpdate.DeleteMapEntry(uintptr(unsafe.Pointer((&dummykey)))) 276 | if err != nil { 277 | fmt.Println("Unable to Delete in eBPF map: ", err) 278 | return err 279 | } 280 | } 281 | return nil 282 | 283 | } 284 | 285 | func TestLoadTCfilter() error { 286 | gosdkClient := goelf.New() 287 | progInfo, _, err := gosdkClient.LoadBpfFile("c/test.bpf.elf", "test") 288 | if err != nil { 289 | fmt.Println("Load BPF failed", "err:", err) 290 | return err 291 | } 292 | 293 | for pinPath, _ := range progInfo { 294 | fmt.Println("Prog Info: ", "Pin Path: ", pinPath) 295 | } 296 | 297 | tcProg := progInfo["/sys/fs/bpf/globals/aws/programs/test_handle_ingress"].Program 298 | progFD := tcProg.ProgFD 299 | 300 | gosdkTcClient := ebpf_tc.New([]string{"lo"}) 301 | 302 | fmt.Println("Try Attach ingress probe") 303 | err = gosdkTcClient.TCIngressAttach("lo", int(progFD), "ingress_test") 304 | if err != nil { 305 | fmt.Println("Failed attaching ingress probe") 306 | } 307 | fmt.Println("Try Attach egress probe") 308 | err = gosdkTcClient.TCEgressAttach("lo", int(progFD), "egress_test") 309 | if err != nil { 310 | fmt.Println("Failed attaching ingress probe") 311 | } 312 | fmt.Println("Try Detach ingress probe") 313 | err = gosdkTcClient.TCIngressDetach("lo") 314 | if err != nil { 315 | fmt.Println("Failed attaching ingress probe") 316 | } 317 | fmt.Println("Try Detach egress probe") 318 | err = gosdkTcClient.TCEgressDetach("lo") 319 | if err != nil { 320 | fmt.Println("Failed attaching ingress probe") 321 | } 322 | return nil 323 | } 324 | 325 | func TestLoadMapWithCustomSize() error { 326 | gosdkClient := goelf.New() 327 | 328 | var customData goelf.BpfCustomData 329 | customData.FilePath = "c/test-map.bpf.elf" 330 | customData.CustomPinPath = "test" 331 | customData.CustomMapSize = make(map[string]int) 332 | customData.CustomMapSize["ingress_map"] = 1024 333 | 334 | _, loadedMap, err := gosdkClient.LoadBpfFileWithCustomData(customData) 335 | if err != nil { 336 | fmt.Println("Load BPF failed", "err:", err) 337 | return err 338 | } 339 | 340 | for mapName, mapData := range loadedMap { 341 | fmt.Println("Map Info: ", "Name: ", mapName) 342 | fmt.Println("Map Info: ", "Size: ", mapData.MapMetaData.MaxEntries) 343 | } 344 | return nil 345 | 346 | } 347 | 348 | func TestBulkMapOperations() error { 349 | gosdkClient := goelf.New() 350 | _, loadedMap, err := gosdkClient.LoadBpfFile("c/test-map.bpf.elf", "operations") 351 | if err != nil { 352 | fmt.Println("Load BPF failed", "err:", err) 353 | return err 354 | } 355 | 356 | for mapName, _ := range loadedMap { 357 | fmt.Println("Map Info: ", "Name: ", mapName) 358 | } 359 | 360 | type BPFInetTrieKey struct { 361 | Prefixlen uint32 362 | Addr [4]byte 363 | } 364 | 365 | const numEntries = 32 * 1000 // 32K entries 366 | 367 | // Create 32K entries 368 | mapToUpdate, ok := loadedMap["ingress_map"] 369 | if !ok { 370 | return fmt.Errorf("map 'ingress_map' not found") 371 | } 372 | 373 | for i := 0; i < numEntries; i++ { 374 | dummykey := BPFInetTrieKey{ 375 | Prefixlen: 32, 376 | Addr: [4]byte{byte(192 + i/256), byte(168 + (i/256)%256), byte(i % 256), 0}, 377 | } 378 | dummyvalue := uint32(40) 379 | 380 | err = mapToUpdate.CreateMapEntry(uintptr(unsafe.Pointer(&dummykey)), uintptr(unsafe.Pointer(&dummyvalue))) 381 | if err != nil { 382 | fmt.Println("Unable to Insert into eBPF map: ", err) 383 | return err 384 | } 385 | } 386 | fmt.Println("Created 32K entries successfully") 387 | 388 | // Update 32K entries 389 | for i := 0; i < numEntries; i++ { 390 | dummykey := BPFInetTrieKey{ 391 | Prefixlen: 32, 392 | Addr: [4]byte{byte(192 + i/256), byte(168 + (i/256)%256), byte(i % 256), 0}, 393 | } 394 | dummyvalue := uint32(20) 395 | 396 | err = mapToUpdate.UpdateMapEntry(uintptr(unsafe.Pointer(&dummykey)), uintptr(unsafe.Pointer(&dummyvalue))) 397 | if err != nil { 398 | fmt.Println("Unable to Update into eBPF map: ", err) 399 | return err 400 | } 401 | } 402 | fmt.Println("Updated 32K entries successfully") 403 | 404 | return nil 405 | } 406 | 407 | func ComputeTrieKey(n net.IPNet) []byte { 408 | prefixLen, _ := n.Mask.Size() 409 | key := make([]byte, 8) 410 | 411 | // Set the prefix length 412 | key[0] = byte(prefixLen) 413 | 414 | // Set the IP address 415 | copy(key[4:], n.IP.To4()) 416 | 417 | fmt.Printf("Key: %v\n", key) 418 | return key 419 | } 420 | 421 | type BPFInetTrieKey struct { 422 | Prefixlen uint32 423 | Addr [4]byte 424 | } 425 | 426 | func bpfInetTrieKeyToIPNet(key BPFInetTrieKey) net.IPNet { 427 | ip := net.IPv4(key.Addr[0], key.Addr[1], key.Addr[2], key.Addr[3]) 428 | return net.IPNet{ 429 | IP: ip, 430 | Mask: net.CIDRMask(int(key.Prefixlen), 32), 431 | } 432 | } 433 | 434 | func TestBulkRefreshMapOperations() error { 435 | gosdkClient := goelf.New() 436 | _, loadedMap, err := gosdkClient.LoadBpfFile("c/test-map.bpf.elf", "operations") 437 | if err != nil { 438 | fmt.Println("Load BPF failed", "err:", err) 439 | return err 440 | } 441 | 442 | for mapName, _ := range loadedMap { 443 | fmt.Println("Map Info: ", "Name: ", mapName) 444 | } 445 | 446 | const numEntries = 32 * 1000 // 32K entries 447 | // Create 32K entries 448 | mapToUpdate, ok := loadedMap["ingress_map"] 449 | if !ok { 450 | return fmt.Errorf("map 'ingress_map' not found") 451 | } 452 | 453 | newMapContents := make(map[string][]byte, numEntries) 454 | for i := 0; i < numEntries; i++ { 455 | dummykey := BPFInetTrieKey{ 456 | Prefixlen: 32, 457 | Addr: [4]byte{byte(1 + i/65536), byte(0 + (i/256)%256), byte(i % 256), 0}, 458 | } 459 | dummyvalue := uint32(40) 460 | 461 | err = mapToUpdate.CreateMapEntry(uintptr(unsafe.Pointer(&dummykey)), uintptr(unsafe.Pointer(&dummyvalue))) 462 | if err != nil { 463 | fmt.Println("Unable to Insert into eBPF map: ", err) 464 | return err 465 | } 466 | dummyvalue = uint32(50) 467 | ipnet := bpfInetTrieKeyToIPNet(dummykey) 468 | fmt.Println(ipnet) 469 | keyByte := ComputeTrieKey(ipnet) 470 | dummyValueByteArray := make([]byte, 4) 471 | binary.LittleEndian.PutUint32(dummyValueByteArray, dummyvalue) 472 | newMapContents[string(keyByte)] = dummyValueByteArray 473 | 474 | } 475 | fmt.Println("Created 32K entries successfully") 476 | 477 | // Update 32K entries 478 | err = mapToUpdate.BulkRefreshMapEntries(newMapContents) 479 | if err != nil { 480 | fmt.Println("Unable to Bulk Refresh eBPF map: ", err) 481 | return err 482 | } 483 | fmt.Println("Updated 32K entries successfully") 484 | 485 | return nil 486 | } 487 | --------------------------------------------------------------------------------