From 48125179eb6fbccecbcbed017c8471c26e2738ca Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 26 Mar 2021 23:22:45 +0100 Subject: [PATCH] go.mod: github.com/cilium/ebpf v0.4.0 full diff: https://github.com/cilium/ebpf/compare/v0.2.0...v0.4.0 Signed-off-by: Sebastiaan van Stijn --- go.mod | 2 +- go.sum | 16 +- vendor/github.com/cilium/ebpf/ARCHITECTURE.md | 80 +++ vendor/github.com/cilium/ebpf/CONTRIBUTING.md | 23 + vendor/github.com/cilium/ebpf/Makefile | 67 ++ vendor/github.com/cilium/ebpf/README.md | 54 ++ vendor/github.com/cilium/ebpf/abi.go | 200 ------ .../github.com/cilium/ebpf/asm/instruction.go | 27 + vendor/github.com/cilium/ebpf/collection.go | 311 ++++++--- vendor/github.com/cilium/ebpf/elf_reader.go | 632 +++++++++++------- .../github.com/cilium/ebpf/elf_reader_fuzz.go | 21 + vendor/github.com/cilium/ebpf/go.mod | 7 +- vendor/github.com/cilium/ebpf/go.sum | 15 +- vendor/github.com/cilium/ebpf/info.go | 239 +++++++ .../cilium/ebpf/internal/btf/btf.go | 170 ++--- .../cilium/ebpf/internal/btf/core.go | 388 +++++++++++ .../cilium/ebpf/internal/btf/ext_info.go | 141 +++- .../cilium/ebpf/internal/btf/types.go | 279 ++++++-- vendor/github.com/cilium/ebpf/internal/elf.go | 52 ++ .../cilium/ebpf/internal/feature.go | 48 +- .../cilium/ebpf/internal/syscall.go | 13 + .../cilium/ebpf/internal/unix/types_linux.go | 26 +- .../cilium/ebpf/internal/unix/types_other.go | 24 +- vendor/github.com/cilium/ebpf/linker.go | 4 +- vendor/github.com/cilium/ebpf/map.go | 604 ++++++++++++----- vendor/github.com/cilium/ebpf/marshalers.go | 27 +- vendor/github.com/cilium/ebpf/pinning.go | 42 ++ vendor/github.com/cilium/ebpf/prog.go | 328 +++++---- vendor/github.com/cilium/ebpf/readme.md | 29 - vendor/github.com/cilium/ebpf/run-tests.sh | 20 +- vendor/github.com/cilium/ebpf/syscalls.go | 200 ++++-- vendor/github.com/cilium/ebpf/types.go | 29 +- vendor/modules.txt | 2 +- 33 files changed, 2965 insertions(+), 1155 deletions(-) create mode 100644 vendor/github.com/cilium/ebpf/ARCHITECTURE.md create mode 100644 vendor/github.com/cilium/ebpf/CONTRIBUTING.md create mode 100644 vendor/github.com/cilium/ebpf/Makefile create mode 100644 vendor/github.com/cilium/ebpf/README.md delete mode 100644 vendor/github.com/cilium/ebpf/abi.go create mode 100644 vendor/github.com/cilium/ebpf/elf_reader_fuzz.go create mode 100644 vendor/github.com/cilium/ebpf/info.go create mode 100644 vendor/github.com/cilium/ebpf/internal/btf/core.go create mode 100644 vendor/github.com/cilium/ebpf/internal/elf.go create mode 100644 vendor/github.com/cilium/ebpf/pinning.go delete mode 100644 vendor/github.com/cilium/ebpf/readme.md diff --git a/go.mod b/go.mod index 51814c237..ad86c08ae 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.14 require ( github.com/checkpoint-restore/go-criu/v5 v5.0.0 - github.com/cilium/ebpf v0.2.0 + github.com/cilium/ebpf v0.4.0 github.com/containerd/console v1.0.1 github.com/coreos/go-systemd/v22 v22.3.1 github.com/cyphar/filepath-securejoin v0.2.2 diff --git a/go.sum b/go.sum index 4e1cdbd42..b5c9723ed 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/checkpoint-restore/go-criu/v5 v5.0.0 h1:TW8f/UvntYoVDMN1K2HlT82qH1rb0sOjpGw3m6Ym+i4= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= -github.com/cilium/ebpf v0.2.0 h1:Fv93L3KKckEcEHR3oApXVzyBTDA8WAm6VXhPE00N3f8= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0 h1:QlHdikaxALkqWasW8hAC1mfR0jdmvbfaBdBPFmRSglA= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= @@ -20,6 +20,8 @@ github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -39,8 +41,13 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/moby/sys/mountinfo v0.4.0 h1:1KInV3Huv18akCu58V7lzNlt+jFmqlu1EaErnEHE/VM= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6Z4= @@ -94,7 +101,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/cilium/ebpf/ARCHITECTURE.md b/vendor/github.com/cilium/ebpf/ARCHITECTURE.md new file mode 100644 index 000000000..aee9c0a0d --- /dev/null +++ b/vendor/github.com/cilium/ebpf/ARCHITECTURE.md @@ -0,0 +1,80 @@ +Architecture of the library +=== + + ELF -> Specifications -> Objects -> Links + +ELF +--- + +BPF is usually produced by using Clang to compile a subset of C. Clang outputs +an ELF file which contains program byte code (aka BPF), but also metadata for +maps used by the program. The metadata follows the conventions set by libbpf +shipped with the kernel. Certain ELF sections have special meaning +and contain structures defined by libbpf. Newer versions of clang emit +additional metadata in BPF Type Format (aka BTF). + +The library aims to be compatible with libbpf so that moving from a C toolchain +to a Go one creates little friction. To that end, the [ELF reader](elf_reader.go) +is tested against the Linux selftests and avoids introducing custom behaviour +if possible. + +The output of the ELF reader is a `CollectionSpec` which encodes +all of the information contained in the ELF in a form that is easy to work with +in Go. + +### BTF + +The BPF Type Format describes more than just the types used by a BPF program. It +includes debug aids like which source line corresponds to which instructions and +what global variables are used. + +[BTF parsing](internal/btf/) lives in a separate internal package since exposing +it would mean an additional maintenance burden, and because the API still +has sharp corners. The most important concept is the `btf.Type` interface, which +also describes things that aren't really types like `.rodata` or `.bss` sections. +`btf.Type`s can form cyclical graphs, which can easily lead to infinite loops if +one is not careful. Hopefully a safe pattern to work with `btf.Type` emerges as +we write more code that deals with it. + +Specifications +--- + +`CollectionSpec`, `ProgramSpec` and `MapSpec` are blueprints for in-kernel +objects and contain everything necessary to execute the relevant `bpf(2)` +syscalls. Since the ELF reader outputs a `CollectionSpec` it's possible to +modify clang-compiled BPF code, for example to rewrite constants. At the same +time the [asm](asm/) package provides an assembler that can be used to generate +`ProgramSpec` on the fly. + +Creating a spec should never require any privileges or be restricted in any way, +for example by only allowing programs in native endianness. This ensures that +the library stays flexible. + +Objects +--- + +`Program` and `Map` are the result of loading specs into the kernel. Sometimes +loading a spec will fail because the kernel is too old, or a feature is not +enabled. There are multiple ways the library deals with that: + +* Fallback: older kernels don't allowing naming programs and maps. The library + automatically detects support for names, and omits them during load if + necessary. This works since name is primarily a debug aid. + +* Sentinel error: sometimes it's possible to detect that a feature isn't available. + In that case the library will return an error wrapping `ErrNotSupported`. + This is also useful to skip tests that can't run on the current kernel. + +Once program and map objects are loaded they expose the kernel's low-level API, +e.g. `NextKey`. Often this API is awkward to use in Go, so there are safer +wrappers on top of the low-level API, like `MapIterator`. The low-level API is +useful as an out when our higher-level API doesn't support a particular use case. + +Links +--- + +BPF can be attached to many different points in the kernel and newer BPF hooks +tend to use bpf_link to do so. Older hooks unfortunately use a combination of +syscalls, netlink messages, etc. Adding support for a new link type should not +pull in large dependencies like netlink, so XDP programs or tracepoints are +out of scope. diff --git a/vendor/github.com/cilium/ebpf/CONTRIBUTING.md b/vendor/github.com/cilium/ebpf/CONTRIBUTING.md new file mode 100644 index 000000000..97c794f3a --- /dev/null +++ b/vendor/github.com/cilium/ebpf/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# How to contribute + +Development is on [GitHub](https://github.com/cilium/ebpf) and contributions in +the form of pull requests and issues reporting bugs or suggesting new features +are welcome. Please take a look at [the architecture](ARCHITECTURE.md) to get +a better understanding for the high-level goals. + +New features must be accompanied by tests. Before starting work on any large +feature, please [join](https://cilium.herokuapp.com/) the +[#libbpf-go](https://cilium.slack.com/messages/libbpf-go) channel on Slack to +discuss the design first. + +When submitting pull requests, consider writing details about what problem you +are solving and why the proposed approach solves that problem in commit messages +and/or pull request description to help future library users and maintainers to +reason about the proposed changes. + +## Running the tests + +Many of the tests require privileges to set resource limits and load eBPF code. +The easiest way to obtain these is to run the tests with `sudo`: + + sudo go test ./... \ No newline at end of file diff --git a/vendor/github.com/cilium/ebpf/Makefile b/vendor/github.com/cilium/ebpf/Makefile new file mode 100644 index 000000000..5d4195833 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/Makefile @@ -0,0 +1,67 @@ +# The development version of clang is distributed as the 'clang' binary, +# while stable/released versions have a version number attached. +# Pin the default clang to a stable version. +CLANG ?= clang-11 +CFLAGS := -target bpf -O2 -g -Wall -Werror $(CFLAGS) + +# Obtain an absolute path to the directory of the Makefile. +# Assume the Makefile is in the root of the repository. +REPODIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +UIDGID := $(shell stat -c '%u:%g' ${REPODIR}) + +IMAGE := $(shell cat ${REPODIR}/testdata/docker/IMAGE) +VERSION := $(shell cat ${REPODIR}/testdata/docker/VERSION) + +# clang <8 doesn't tag relocs properly (STT_NOTYPE) +# clang 9 is the first version emitting BTF +TARGETS := \ + testdata/loader-clang-7 \ + testdata/loader-clang-9 \ + testdata/loader-clang-11 \ + testdata/invalid_map \ + testdata/raw_tracepoint \ + testdata/invalid_map_static \ + testdata/initialized_btf_map \ + testdata/strings \ + internal/btf/testdata/relocs + +.PHONY: all clean docker-all docker-shell + +.DEFAULT_TARGET = docker-all + +# Build all ELF binaries using a Dockerized LLVM toolchain. +docker-all: + docker run --rm --user "${UIDGID}" \ + -v "${REPODIR}":/ebpf -w /ebpf --env MAKEFLAGS \ + "${IMAGE}:${VERSION}" \ + make all + +# (debug) Drop the user into a shell inside the Docker container as root. +docker-shell: + docker run --rm -ti \ + -v "${REPODIR}":/ebpf -w /ebpf \ + "${IMAGE}:${VERSION}" + +clean: + -$(RM) testdata/*.elf + -$(RM) internal/btf/testdata/*.elf + +all: $(addsuffix -el.elf,$(TARGETS)) $(addsuffix -eb.elf,$(TARGETS)) + +testdata/loader-%-el.elf: testdata/loader.c + $* $(CFLAGS) -mlittle-endian -c $< -o $@ + +testdata/loader-%-eb.elf: testdata/loader.c + $* $(CFLAGS) -mbig-endian -c $< -o $@ + +%-el.elf: %.c + $(CLANG) $(CFLAGS) -mlittle-endian -c $< -o $@ + +%-eb.elf : %.c + $(CLANG) $(CFLAGS) -mbig-endian -c $< -o $@ + +# Usage: make VMLINUX=/path/to/vmlinux vmlinux-btf +.PHONY: vmlinux-btf +vmlinux-btf: internal/btf/testdata/vmlinux-btf.gz +internal/btf/testdata/vmlinux-btf.gz: $(VMLINUX) + objcopy --dump-section .BTF=/dev/stdout "$<" /dev/null | gzip > "$@" diff --git a/vendor/github.com/cilium/ebpf/README.md b/vendor/github.com/cilium/ebpf/README.md new file mode 100644 index 000000000..7f504d334 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/README.md @@ -0,0 +1,54 @@ +# eBPF + +[![PkgGoDev](https://pkg.go.dev/badge/github.com/cilium/ebpf)](https://pkg.go.dev/github.com/cilium/ebpf) + +eBPF is a pure Go library that provides utilities for loading, compiling, and +debugging eBPF programs. It has minimal external dependencies and is intended to +be used in long running processes. + +* [asm](https://pkg.go.dev/github.com/cilium/ebpf/asm) contains a basic + assembler +* [link](https://pkg.go.dev/github.com/cilium/ebpf/link) allows attaching eBPF + to various hooks +* [perf](https://pkg.go.dev/github.com/cilium/ebpf/perf) allows reading from a + `PERF_EVENT_ARRAY` +* [cmd/bpf2go](https://pkg.go.dev/github.com/cilium/ebpf/cmd/bpf2go) allows + embedding eBPF in Go + +The library is maintained by [Cloudflare](https://www.cloudflare.com) and +[Cilium](https://www.cilium.io). Feel free to +[join](https://cilium.herokuapp.com/) the +[#libbpf-go](https://cilium.slack.com/messages/libbpf-go) channel on Slack. + +## Current status + +The package is production ready, but **the API is explicitly unstable right +now**. Expect to update your code if you want to follow along. + +## Requirements + +* A version of Go that is [supported by + upstream](https://golang.org/doc/devel/release.html#policy) +* Linux 4.9, 4.19 or 5.4 (versions in-between should work, but are not tested) + +## Useful resources + +* [eBPF.io](https://ebpf.io) (recommended) +* [Cilium eBPF documentation](https://docs.cilium.io/en/latest/bpf/#bpf-guide) + (recommended) +* [Linux documentation on + BPF](https://www.kernel.org/doc/html/latest/networking/filter.html) +* [eBPF features by Linux + version](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md) + +## Regenerating Testdata + +Run `make` in the root of this repository to rebuild testdata in all +subpackages. This requires Docker, as it relies on a standardized build +environment to keep the build output stable. + +The toolchain image build files are kept in [testdata/docker/](testdata/docker/). + +## License + +MIT diff --git a/vendor/github.com/cilium/ebpf/abi.go b/vendor/github.com/cilium/ebpf/abi.go deleted file mode 100644 index f86a17ee7..000000000 --- a/vendor/github.com/cilium/ebpf/abi.go +++ /dev/null @@ -1,200 +0,0 @@ -package ebpf - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "os" - "syscall" - - "github.com/cilium/ebpf/internal" -) - -// MapABI are the attributes of a Map which are available across all supported kernels. -type MapABI struct { - Type MapType - KeySize uint32 - ValueSize uint32 - MaxEntries uint32 - Flags uint32 -} - -func newMapABIFromSpec(spec *MapSpec) *MapABI { - return &MapABI{ - spec.Type, - spec.KeySize, - spec.ValueSize, - spec.MaxEntries, - spec.Flags, - } -} - -func newMapABIFromFd(fd *internal.FD) (string, *MapABI, error) { - info, err := bpfGetMapInfoByFD(fd) - if err != nil { - if errors.Is(err, syscall.EINVAL) { - abi, err := newMapABIFromProc(fd) - return "", abi, err - } - return "", nil, err - } - - return "", &MapABI{ - MapType(info.mapType), - info.keySize, - info.valueSize, - info.maxEntries, - info.flags, - }, nil -} - -func newMapABIFromProc(fd *internal.FD) (*MapABI, error) { - var abi MapABI - err := scanFdInfo(fd, map[string]interface{}{ - "map_type": &abi.Type, - "key_size": &abi.KeySize, - "value_size": &abi.ValueSize, - "max_entries": &abi.MaxEntries, - "map_flags": &abi.Flags, - }) - if err != nil { - return nil, err - } - return &abi, nil -} - -// Equal returns true if two ABIs have the same values. -func (abi *MapABI) Equal(other *MapABI) bool { - switch { - case abi.Type != other.Type: - return false - case abi.KeySize != other.KeySize: - return false - case abi.ValueSize != other.ValueSize: - return false - case abi.MaxEntries != other.MaxEntries: - return false - case abi.Flags != other.Flags: - return false - default: - return true - } -} - -// ProgramABI are the attributes of a Program which are available across all supported kernels. -type ProgramABI struct { - Type ProgramType -} - -func newProgramABIFromFd(fd *internal.FD) (string, *ProgramABI, error) { - info, err := bpfGetProgInfoByFD(fd) - if err != nil { - if errors.Is(err, syscall.EINVAL) { - return newProgramABIFromProc(fd) - } - - return "", nil, err - } - - var name string - if bpfName := internal.CString(info.name[:]); bpfName != "" { - name = bpfName - } else { - name = internal.CString(info.tag[:]) - } - - return name, &ProgramABI{ - Type: ProgramType(info.progType), - }, nil -} - -func newProgramABIFromProc(fd *internal.FD) (string, *ProgramABI, error) { - var ( - abi ProgramABI - name string - ) - - err := scanFdInfo(fd, map[string]interface{}{ - "prog_type": &abi.Type, - "prog_tag": &name, - }) - if errors.Is(err, errMissingFields) { - return "", nil, &internal.UnsupportedFeatureError{ - Name: "reading ABI from /proc/self/fdinfo", - MinimumVersion: internal.Version{4, 11, 0}, - } - } - if err != nil { - return "", nil, err - } - - return name, &abi, nil -} - -func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error { - raw, err := fd.Value() - if err != nil { - return err - } - - fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", raw)) - if err != nil { - return err - } - defer fh.Close() - - if err := scanFdInfoReader(fh, fields); err != nil { - return fmt.Errorf("%s: %w", fh.Name(), err) - } - return nil -} - -var errMissingFields = errors.New("missing fields") - -func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error { - var ( - scanner = bufio.NewScanner(r) - scanned int - ) - - for scanner.Scan() { - parts := bytes.SplitN(scanner.Bytes(), []byte("\t"), 2) - if len(parts) != 2 { - continue - } - - name := bytes.TrimSuffix(parts[0], []byte(":")) - field, ok := fields[string(name)] - if !ok { - continue - } - - if n, err := fmt.Fscanln(bytes.NewReader(parts[1]), field); err != nil || n != 1 { - return fmt.Errorf("can't parse field %s: %v", name, err) - } - - scanned++ - } - - if err := scanner.Err(); err != nil { - return err - } - - if scanned != len(fields) { - return errMissingFields - } - - return nil -} - -// Equal returns true if two ABIs have the same values. -func (abi *ProgramABI) Equal(other *ProgramABI) bool { - switch { - case abi.Type != other.Type: - return false - default: - return true - } -} diff --git a/vendor/github.com/cilium/ebpf/asm/instruction.go b/vendor/github.com/cilium/ebpf/asm/instruction.go index 890dc008a..5d9d820e5 100644 --- a/vendor/github.com/cilium/ebpf/asm/instruction.go +++ b/vendor/github.com/cilium/ebpf/asm/instruction.go @@ -1,12 +1,16 @@ package asm import ( + "crypto/sha1" "encoding/binary" + "encoding/hex" "errors" "fmt" "io" "math" "strings" + + "github.com/cilium/ebpf/internal/unix" ) // InstructionSize is the size of a BPF instruction in bytes @@ -159,6 +163,9 @@ func (ins *Instruction) mapOffset() uint32 { return uint32(uint64(ins.Constant) >> 32) } +// isLoadFromMap returns true if the instruction loads from a map. +// +// This covers both loading the map pointer and direct map value loads. func (ins *Instruction) isLoadFromMap() bool { return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue) } @@ -390,6 +397,25 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error { return nil } +// Tag calculates the kernel tag for a series of instructions. +// +// It mirrors bpf_prog_calc_tag in the kernel and so can be compared +// to ProgramInfo.Tag to figure out whether a loaded program matches +// certain instructions. +func (insns Instructions) Tag(bo binary.ByteOrder) (string, error) { + h := sha1.New() + for i, ins := range insns { + if ins.isLoadFromMap() { + ins.Constant = 0 + } + _, err := ins.Marshal(h, bo) + if err != nil { + return "", fmt.Errorf("instruction %d: %w", i, err) + } + } + return hex.EncodeToString(h.Sum(nil)[:unix.BPF_TAG_SIZE]), nil +} + // Iterate allows iterating a BPF program while keeping track of // various offsets. // @@ -417,6 +443,7 @@ func (iter *InstructionIterator) Next() bool { } if iter.Ins != nil { + iter.Index++ iter.Offset += RawInstructionOffset(iter.Ins.OpCode.rawInstructions()) } iter.Ins = &iter.insns[0] diff --git a/vendor/github.com/cilium/ebpf/collection.go b/vendor/github.com/cilium/ebpf/collection.go index 81ee2bd1a..8e3629003 100644 --- a/vendor/github.com/cilium/ebpf/collection.go +++ b/vendor/github.com/cilium/ebpf/collection.go @@ -131,10 +131,11 @@ func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error return nil } -// Assign the contents of a collection spec to a struct. +// Assign the contents of a CollectionSpec to a struct. // // This function is a short-cut to manually checking the presence -// of maps and programs in a collection spec. +// of maps and programs in a collection spec. Consider using bpf2go if this +// sounds useful. // // The argument to must be a pointer to a struct. A field of the // struct is updated with values from Programs or Maps if it @@ -173,21 +174,61 @@ func (cs *CollectionSpec) Assign(to interface{}) error { return assignValues(to, valueOf) } -// LoadAndAssign creates a collection from a spec, and assigns it to a struct. +// LoadAndAssign maps and programs into the kernel and assign them to a struct. // -// See Collection.Assign for details. +// This function is a short-cut to manually checking the presence +// of maps and programs in a collection spec. Consider using bpf2go if this +// sounds useful. +// +// The argument to must be a pointer to a struct. A field of the +// struct is updated with values from Programs or Maps if it +// has an `ebpf` tag and its type is *Program or *Map. +// The tag gives the name of the program or map as found in +// the CollectionSpec. +// +// struct { +// Foo *ebpf.Program `ebpf:"xdp_foo"` +// Bar *ebpf.Map `ebpf:"bar_map"` +// Ignored int +// } +// +// opts may be nil. +// +// Returns an error if any of the fields can't be found, or +// if the same map or program is assigned multiple times. func (cs *CollectionSpec) LoadAndAssign(to interface{}, opts *CollectionOptions) error { if opts == nil { opts = &CollectionOptions{} } - coll, err := NewCollectionWithOptions(cs, *opts) - if err != nil { + loadMap, loadProgram, done, cleanup := lazyLoadCollection(cs, opts) + defer cleanup() + + valueOf := func(typ reflect.Type, name string) (reflect.Value, error) { + switch typ { + case reflect.TypeOf((*Program)(nil)): + p, err := loadProgram(name) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(p), nil + case reflect.TypeOf((*Map)(nil)): + m, err := loadMap(name) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(m), nil + default: + return reflect.Value{}, fmt.Errorf("unsupported type %s", typ) + } + } + + if err := assignValues(to, valueOf); err != nil { return err } - defer coll.Close() - return coll.Assign(to) + done() + return nil } // Collection is a collection of Programs and Maps associated @@ -198,28 +239,75 @@ type Collection struct { } // NewCollection creates a Collection from a specification. -// -// Only maps referenced by at least one of the programs are initialized. func NewCollection(spec *CollectionSpec) (*Collection, error) { return NewCollectionWithOptions(spec, CollectionOptions{}) } // NewCollectionWithOptions creates a Collection from a specification. -// -// Only maps referenced by at least one of the programs are initialized. -func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (coll *Collection, err error) { +func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) { + loadMap, loadProgram, done, cleanup := lazyLoadCollection(spec, &opts) + defer cleanup() + + for mapName := range spec.Maps { + _, err := loadMap(mapName) + if err != nil { + return nil, err + } + } + + for progName := range spec.Programs { + _, err := loadProgram(progName) + if err != nil { + return nil, err + } + } + + maps, progs := done() + return &Collection{ + progs, + maps, + }, nil +} + +type btfHandleCache map[*btf.Spec]*btf.Handle + +func (btfs btfHandleCache) load(spec *btf.Spec) (*btf.Handle, error) { + if btfs[spec] != nil { + return btfs[spec], nil + } + + handle, err := btf.NewHandle(spec) + if err != nil { + return nil, err + } + + btfs[spec] = handle + return handle, nil +} + +func (btfs btfHandleCache) close() { + for _, handle := range btfs { + handle.Close() + } +} + +func lazyLoadCollection(coll *CollectionSpec, opts *CollectionOptions) ( + loadMap func(string) (*Map, error), + loadProgram func(string) (*Program, error), + done func() (map[string]*Map, map[string]*Program), + cleanup func(), +) { var ( - maps = make(map[string]*Map) - progs = make(map[string]*Program) - btfs = make(map[*btf.Spec]*btf.Handle) + maps = make(map[string]*Map) + progs = make(map[string]*Program) + btfs = make(btfHandleCache) + skipMapsAndProgs = false ) - defer func() { - for _, btf := range btfs { - btf.Close() - } + cleanup = func() { + btfs.close() - if err == nil { + if skipMapsAndProgs { return } @@ -230,40 +318,43 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (col for _, p := range progs { p.Close() } - }() - - loadBTF := func(spec *btf.Spec) (*btf.Handle, error) { - if btfs[spec] != nil { - return btfs[spec], nil - } - - handle, err := btf.NewHandle(spec) - if err != nil { - return nil, err - } - - btfs[spec] = handle - return handle, nil } - for mapName, mapSpec := range spec.Maps { - var handle *btf.Handle - if mapSpec.BTF != nil { - handle, err = loadBTF(btf.MapSpec(mapSpec.BTF)) - if err != nil && !errors.Is(err, btf.ErrNotSupported) { - return nil, err - } + done = func() (map[string]*Map, map[string]*Program) { + skipMapsAndProgs = true + return maps, progs + } + + loadMap = func(mapName string) (*Map, error) { + if m := maps[mapName]; m != nil { + return m, nil } - m, err := newMapWithBTF(mapSpec, handle, opts.Maps) + mapSpec := coll.Maps[mapName] + if mapSpec == nil { + return nil, fmt.Errorf("missing map %s", mapName) + } + + m, err := newMapWithOptions(mapSpec, opts.Maps, btfs) if err != nil { return nil, fmt.Errorf("map %s: %w", mapName, err) } + maps[mapName] = m + return m, nil } - for progName, origProgSpec := range spec.Programs { - progSpec := origProgSpec.Copy() + loadProgram = func(progName string) (*Program, error) { + if prog := progs[progName]; prog != nil { + return prog, nil + } + + progSpec := coll.Programs[progName] + if progSpec == nil { + return nil, fmt.Errorf("unknown program %s", progName) + } + + progSpec = progSpec.Copy() // Rewrite any reference to a valid map. for i := range progSpec.Instructions { @@ -279,9 +370,9 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (col continue } - m := maps[ins.Reference] - if m == nil { - return nil, fmt.Errorf("program %s: missing map %s", progName, ins.Reference) + m, err := loadMap(ins.Reference) + if err != nil { + return nil, fmt.Errorf("program %s: %s", progName, err) } fd := m.FD() @@ -293,25 +384,16 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (col } } - var handle *btf.Handle - if progSpec.BTF != nil { - handle, err = loadBTF(btf.ProgramSpec(progSpec.BTF)) - if err != nil && !errors.Is(err, btf.ErrNotSupported) { - return nil, err - } - } - - prog, err := newProgramWithBTF(progSpec, handle, opts.Programs) + prog, err := newProgramWithOptions(progSpec, opts.Programs, btfs) if err != nil { return nil, fmt.Errorf("program %s: %w", progName, err) } + progs[progName] = prog + return prog, nil } - return &Collection{ - progs, - maps, - }, nil + return } // LoadCollection parses an object file and converts it to a collection. @@ -359,18 +441,8 @@ func (coll *Collection) DetachProgram(name string) *Program { // Assign the contents of a collection to a struct. // -// `to` must be a pointer to a struct like the following: -// -// struct { -// Foo *ebpf.Program `ebpf:"xdp_foo"` -// Bar *ebpf.Map `ebpf:"bar_map"` -// Ignored int -// } -// -// See CollectionSpec.Assign for the semantics of this function. -// -// DetachMap and DetachProgram is invoked for all assigned elements -// if the function is successful. +// Deprecated: use CollectionSpec.Assign instead. It provides the same +// functionality but creates only the maps and programs requested. func (coll *Collection) Assign(to interface{}) error { assignedMaps := make(map[string]struct{}) assignedPrograms := make(map[string]struct{}) @@ -411,28 +483,86 @@ func (coll *Collection) Assign(to interface{}) error { } func assignValues(to interface{}, valueOf func(reflect.Type, string) (reflect.Value, error)) error { - v := reflect.ValueOf(to) - if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { - return fmt.Errorf("%T is not a pointer to a struct", to) + type structField struct { + reflect.StructField + value reflect.Value + } + + var ( + fields []structField + visitedTypes = make(map[reflect.Type]bool) + flattenStruct func(reflect.Value) error + ) + + flattenStruct = func(structVal reflect.Value) error { + structType := structVal.Type() + if structType.Kind() != reflect.Struct { + return fmt.Errorf("%s is not a struct", structType) + } + + if visitedTypes[structType] { + return fmt.Errorf("recursion on type %s", structType) + } + + for i := 0; i < structType.NumField(); i++ { + field := structField{structType.Field(i), structVal.Field(i)} + + name := field.Tag.Get("ebpf") + if name != "" { + fields = append(fields, field) + continue + } + + var err error + switch field.Type.Kind() { + case reflect.Ptr: + if field.Type.Elem().Kind() != reflect.Struct { + continue + } + + if field.value.IsNil() { + return fmt.Errorf("nil pointer to %s", structType) + } + + err = flattenStruct(field.value.Elem()) + + case reflect.Struct: + err = flattenStruct(field.value) + + default: + continue + } + + if err != nil { + return fmt.Errorf("field %s: %s", field.Name, err) + } + } + + return nil + } + + toValue := reflect.ValueOf(to) + if toValue.Type().Kind() != reflect.Ptr { + return fmt.Errorf("%T is not a pointer to struct", to) + } + + if toValue.IsNil() { + return fmt.Errorf("nil pointer to %T", to) + } + + if err := flattenStruct(toValue.Elem()); err != nil { + return err } type elem struct { + // Either *Map or *Program typ reflect.Type name string } - var ( - s = v.Elem() - sT = s.Type() - assignedTo = make(map[elem]string) - ) - for i := 0; i < sT.NumField(); i++ { - field := sT.Field(i) - + assignedTo := make(map[elem]string) + for _, field := range fields { name := field.Tag.Get("ebpf") - if name == "" { - continue - } if strings.Contains(name, ",") { return fmt.Errorf("field %s: ebpf tag contains a comma", field.Name) } @@ -447,12 +577,11 @@ func assignValues(to interface{}, valueOf func(reflect.Type, string) (reflect.Va return fmt.Errorf("field %s: %w", field.Name, err) } - fieldValue := s.Field(i) - if !fieldValue.CanSet() { - return fmt.Errorf("can't set value of field %s", field.Name) + if !field.value.CanSet() { + return fmt.Errorf("field %s: can't set value", field.Name) } - fieldValue.Set(value) + field.value.Set(value) assignedTo[e] = field.Name } diff --git a/vendor/github.com/cilium/ebpf/elf_reader.go b/vendor/github.com/cilium/ebpf/elf_reader.go index 2b564dae0..943142c49 100644 --- a/vendor/github.com/cilium/ebpf/elf_reader.go +++ b/vendor/github.com/cilium/ebpf/elf_reader.go @@ -18,12 +18,14 @@ import ( "github.com/cilium/ebpf/internal/unix" ) +// elfCode is a convenience to reduce the amount of arguments that have to +// be passed around explicitly. You should treat it's contents as immutable. type elfCode struct { - *elf.File - symbols []elf.Symbol - symbolsPerSection map[elf.SectionIndex]map[uint64]elf.Symbol - license string - version uint32 + *internal.SafeELFFile + sections map[elf.SectionIndex]*elfSection + license string + version uint32 + btf *btf.Spec } // LoadCollectionSpec parses an ELF file into a CollectionSpec. @@ -43,63 +45,52 @@ func LoadCollectionSpec(file string) (*CollectionSpec, error) { // LoadCollectionSpecFromReader parses an ELF file into a CollectionSpec. func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { - f, err := elf.NewFile(rd) + f, err := internal.NewSafeELFFile(rd) if err != nil { return nil, err } defer f.Close() - symbols, err := f.Symbols() - if err != nil { - return nil, fmt.Errorf("load symbols: %v", err) - } - - ec := &elfCode{f, symbols, symbolsPerSection(symbols), "", 0} - var ( licenseSection *elf.Section versionSection *elf.Section - btfMaps = make(map[elf.SectionIndex]*elf.Section) - progSections = make(map[elf.SectionIndex]*elf.Section) + sections = make(map[elf.SectionIndex]*elfSection) relSections = make(map[elf.SectionIndex]*elf.Section) - mapSections = make(map[elf.SectionIndex]*elf.Section) - dataSections = make(map[elf.SectionIndex]*elf.Section) ) - for i, sec := range ec.Sections { + // This is the target of relocations generated by inline assembly. + sections[elf.SHN_UNDEF] = newElfSection(new(elf.Section), undefSection) + + // Collect all the sections we're interested in. This includes relocations + // which we parse later. + for i, sec := range f.Sections { + idx := elf.SectionIndex(i) + switch { case strings.HasPrefix(sec.Name, "license"): licenseSection = sec case strings.HasPrefix(sec.Name, "version"): versionSection = sec case strings.HasPrefix(sec.Name, "maps"): - mapSections[elf.SectionIndex(i)] = sec + sections[idx] = newElfSection(sec, mapSection) case sec.Name == ".maps": - btfMaps[elf.SectionIndex(i)] = sec - case sec.Name == ".bss" || sec.Name == ".rodata" || sec.Name == ".data": - dataSections[elf.SectionIndex(i)] = sec + sections[idx] = newElfSection(sec, btfMapSection) + case sec.Name == ".bss" || sec.Name == ".data" || strings.HasPrefix(sec.Name, ".rodata"): + sections[idx] = newElfSection(sec, dataSection) case sec.Type == elf.SHT_REL: - if int(sec.Info) >= len(ec.Sections) { - return nil, fmt.Errorf("found relocation section %v for missing section %v", i, sec.Info) - } - // Store relocations under the section index of the target - idx := elf.SectionIndex(sec.Info) - if relSections[idx] != nil { - return nil, fmt.Errorf("section %d has multiple relocation sections", sec.Info) - } - relSections[idx] = sec + relSections[elf.SectionIndex(sec.Info)] = sec case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0: - progSections[elf.SectionIndex(i)] = sec + sections[idx] = newElfSection(sec, programSection) } } - ec.license, err = loadLicense(licenseSection) + license, err := loadLicense(licenseSection) if err != nil { return nil, fmt.Errorf("load license: %w", err) } - ec.version, err = loadVersion(versionSection, ec.ByteOrder) + version, err := loadVersion(versionSection, f.ByteOrder) if err != nil { return nil, fmt.Errorf("load version: %w", err) } @@ -109,37 +100,90 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) { return nil, fmt.Errorf("load BTF: %w", err) } - relocations, referencedSections, err := ec.loadRelocations(relSections) + // Assign symbols to all the sections we're interested in. + symbols, err := f.Symbols() if err != nil { - return nil, fmt.Errorf("load relocations: %w", err) + return nil, fmt.Errorf("load symbols: %v", err) } + for _, symbol := range symbols { + idx := symbol.Section + symType := elf.ST_TYPE(symbol.Info) + + section := sections[idx] + if section == nil { + continue + } + + // Older versions of LLVM don't tag symbols correctly, so keep + // all NOTYPE ones. + keep := symType == elf.STT_NOTYPE + switch section.kind { + case mapSection, btfMapSection, dataSection: + keep = keep || symType == elf.STT_OBJECT + case programSection: + keep = keep || symType == elf.STT_FUNC + } + if !keep || symbol.Name == "" { + continue + } + + section.symbols[symbol.Value] = symbol + } + + ec := &elfCode{ + SafeELFFile: f, + sections: sections, + license: license, + version: version, + btf: btfSpec, + } + + // Go through relocation sections, and parse the ones for sections we're + // interested in. Make sure that relocations point at valid sections. + for idx, relSection := range relSections { + section := sections[idx] + if section == nil { + continue + } + + rels, err := ec.loadRelocations(relSection, symbols) + if err != nil { + return nil, fmt.Errorf("relocation for section %q: %w", section.Name, err) + } + + for _, rel := range rels { + target := sections[rel.Section] + if target == nil { + return nil, fmt.Errorf("section %q: reference to %q in section %s: %w", section.Name, rel.Name, rel.Section, ErrNotSupported) + } + + if target.Flags&elf.SHF_STRINGS > 0 { + return nil, fmt.Errorf("section %q: string %q is not stack allocated: %w", section.Name, rel.Name, ErrNotSupported) + } + + target.references++ + } + + section.relocations = rels + } + + // Collect all the various ways to define maps. maps := make(map[string]*MapSpec) - if err := ec.loadMaps(maps, mapSections); err != nil { + if err := ec.loadMaps(maps); err != nil { return nil, fmt.Errorf("load maps: %w", err) } - if len(btfMaps) > 0 { - if err := ec.loadBTFMaps(maps, btfMaps, btfSpec); err != nil { - return nil, fmt.Errorf("load BTF maps: %w", err) - } + if err := ec.loadBTFMaps(maps); err != nil { + return nil, fmt.Errorf("load BTF maps: %w", err) } - if len(dataSections) > 0 { - for idx := range dataSections { - if !referencedSections[idx] { - // Prune data sections which are not referenced by any - // instructions. - delete(dataSections, idx) - } - } - - if err := ec.loadDataSections(maps, dataSections, btfSpec); err != nil { - return nil, fmt.Errorf("load data sections: %w", err) - } + if err := ec.loadDataSections(maps); err != nil { + return nil, fmt.Errorf("load data sections: %w", err) } - progs, err := ec.loadPrograms(progSections, relocations, btfSpec) + // Finally, collect programs and link them. + progs, err := ec.loadPrograms() if err != nil { return nil, fmt.Errorf("load programs: %w", err) } @@ -171,26 +215,61 @@ func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) { return version, nil } -func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section, relocations map[elf.SectionIndex]map[uint64]elf.Symbol, btfSpec *btf.Spec) (map[string]*ProgramSpec, error) { +type elfSectionKind int + +const ( + undefSection elfSectionKind = iota + mapSection + btfMapSection + programSection + dataSection +) + +type elfSection struct { + *elf.Section + kind elfSectionKind + // Offset from the start of the section to a symbol + symbols map[uint64]elf.Symbol + // Offset from the start of the section to a relocation, which points at + // a symbol in another section. + relocations map[uint64]elf.Symbol + // The number of relocations pointing at this section. + references int +} + +func newElfSection(section *elf.Section, kind elfSectionKind) *elfSection { + return &elfSection{ + section, + kind, + make(map[uint64]elf.Symbol), + make(map[uint64]elf.Symbol), + 0, + } +} + +func (ec *elfCode) loadPrograms() (map[string]*ProgramSpec, error) { var ( progs []*ProgramSpec libs []*ProgramSpec ) - for idx, sec := range progSections { - syms := ec.symbolsPerSection[idx] - if len(syms) == 0 { + for _, sec := range ec.sections { + if sec.kind != programSection { + continue + } + + if len(sec.symbols) == 0 { return nil, fmt.Errorf("section %v: missing symbols", sec.Name) } - funcSym, ok := syms[0] + funcSym, ok := sec.symbols[0] if !ok { return nil, fmt.Errorf("section %v: no label at start", sec.Name) } - insns, length, err := ec.loadInstructions(sec, syms, relocations[idx]) + insns, length, err := ec.loadInstructions(sec) if err != nil { - return nil, fmt.Errorf("program %s: can't unmarshal instructions: %w", funcSym.Name, err) + return nil, fmt.Errorf("program %s: %w", funcSym.Name, err) } progType, attachType, attachTo := getProgType(sec.Name) @@ -206,8 +285,8 @@ func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section, ByteOrder: ec.ByteOrder, } - if btfSpec != nil { - spec.BTF, err = btfSpec.Program(sec.Name, length) + if ec.btf != nil { + spec.BTF, err = ec.btf.Program(sec.Name, length) if err != nil && !errors.Is(err, btf.ErrNoExtendedInfo) { return nil, fmt.Errorf("program %s: %w", funcSym.Name, err) } @@ -235,7 +314,7 @@ func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section, return res, nil } -func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations map[uint64]elf.Symbol) (asm.Instructions, uint64, error) { +func (ec *elfCode) loadInstructions(section *elfSection) (asm.Instructions, uint64, error) { var ( r = bufio.NewReader(section.Open()) insns asm.Instructions @@ -251,11 +330,11 @@ func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations m return nil, 0, fmt.Errorf("offset %d: %w", offset, err) } - ins.Symbol = symbols[offset].Name + ins.Symbol = section.symbols[offset].Name - if rel, ok := relocations[offset]; ok { + if rel, ok := section.relocations[offset]; ok { if err = ec.relocateInstruction(&ins, rel); err != nil { - return nil, 0, fmt.Errorf("offset %d: can't relocate instruction: %w", offset, err) + return nil, 0, fmt.Errorf("offset %d: relocate instruction: %w", offset, err) } } @@ -271,69 +350,66 @@ func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) err name = rel.Name ) - if typ == elf.STT_SECTION { - // Symbols with section type do not have a name set. Get it - // from the section itself. - idx := int(rel.Section) - if idx > len(ec.Sections) { - return errors.New("out-of-bounds section index") + target := ec.sections[rel.Section] + + switch target.kind { + case mapSection, btfMapSection: + if bind != elf.STB_GLOBAL { + return fmt.Errorf("possible erroneous static qualifier on map definition: found reference to %q", name) } - name = ec.Sections[idx].Name - } - -outer: - switch { - case ins.OpCode == asm.LoadImmOp(asm.DWord): - // There are two distinct types of a load from a map: - // a direct one, where the value is extracted without - // a call to map_lookup_elem in eBPF, and an indirect one - // that goes via the helper. They are distinguished by - // different relocations. - switch typ { - case elf.STT_SECTION: - // This is a direct load since the referenced symbol is a - // section. Weirdly, the offset of the real symbol in the - // section is encoded in the instruction stream. - if bind != elf.STB_LOCAL { - return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind) - } - - // For some reason, clang encodes the offset of the symbol its - // section in the first basic BPF instruction, while the kernel - // expects it in the second one. - ins.Constant <<= 32 - ins.Src = asm.PseudoMapValue - - case elf.STT_NOTYPE: - if bind == elf.STB_GLOBAL && rel.Section == elf.SHN_UNDEF { - // This is a relocation generated by inline assembly. - // We can't do more than assigning ins.Reference. - break outer - } - - // This is an ELF generated on clang < 8, which doesn't tag + if typ != elf.STT_OBJECT && typ != elf.STT_NOTYPE { + // STT_NOTYPE is generated on clang < 8 which doesn't tag // relocations appropriately. - fallthrough - - case elf.STT_OBJECT: - if bind != elf.STB_GLOBAL { - return fmt.Errorf("load: %s: unsupported binding: %s", name, bind) - } - - ins.Src = asm.PseudoMapFD - - default: - return fmt.Errorf("load: %s: unsupported relocation: %s", name, typ) + return fmt.Errorf("map load: incorrect relocation type %v", typ) } + ins.Src = asm.PseudoMapFD + // Mark the instruction as needing an update when creating the // collection. if err := ins.RewriteMapPtr(-1); err != nil { return err } - case ins.OpCode.JumpOp() == asm.Call: + case dataSection: + switch typ { + case elf.STT_SECTION: + if bind != elf.STB_LOCAL { + return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind) + } + + case elf.STT_OBJECT: + if bind != elf.STB_GLOBAL { + return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind) + } + + default: + return fmt.Errorf("incorrect relocation type %v for direct map load", typ) + } + + // We rely on using the name of the data section as the reference. It + // would be nicer to keep the real name in case of an STT_OBJECT, but + // it's not clear how to encode that into Instruction. + name = target.Name + + // For some reason, clang encodes the offset of the symbol its + // section in the first basic BPF instruction, while the kernel + // expects it in the second one. + ins.Constant <<= 32 + ins.Src = asm.PseudoMapValue + + // Mark the instruction as needing an update when creating the + // collection. + if err := ins.RewriteMapPtr(-1); err != nil { + return err + } + + case programSection: + if ins.OpCode.JumpOp() != asm.Call { + return fmt.Errorf("not a call instruction: %s", ins) + } + if ins.Src != asm.PseudoCall { return fmt.Errorf("call: %s: incorrect source register", name) } @@ -358,7 +434,7 @@ outer: return fmt.Errorf("call: %s: invalid offset %d", name, offset) } - sym, ok := ec.symbolsPerSection[rel.Section][uint64(offset)] + sym, ok := target.symbols[uint64(offset)] if !ok { return fmt.Errorf("call: %s: no symbol at offset %d", name, offset) } @@ -370,31 +446,46 @@ outer: return fmt.Errorf("call: %s: invalid symbol type %s", name, typ) } + case undefSection: + if bind != elf.STB_GLOBAL { + return fmt.Errorf("asm relocation: %s: unsupported binding: %s", name, bind) + } + + if typ != elf.STT_NOTYPE { + return fmt.Errorf("asm relocation: %s: unsupported type %s", name, typ) + } + + // There is nothing to do here but set ins.Reference. + default: - return fmt.Errorf("relocation for unsupported instruction: %s", ins.OpCode) + return fmt.Errorf("relocation to %q: %w", target.Name, ErrNotSupported) } ins.Reference = name return nil } -func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section) error { - for idx, sec := range mapSections { - syms := ec.symbolsPerSection[idx] - if len(syms) == 0 { +func (ec *elfCode) loadMaps(maps map[string]*MapSpec) error { + for _, sec := range ec.sections { + if sec.kind != mapSection { + continue + } + + nSym := len(sec.symbols) + if nSym == 0 { return fmt.Errorf("section %v: no symbols", sec.Name) } - if sec.Size%uint64(len(syms)) != 0 { + if sec.Size%uint64(nSym) != 0 { return fmt.Errorf("section %v: map descriptors are not of equal size", sec.Name) } var ( r = bufio.NewReader(sec.Open()) - size = sec.Size / uint64(len(syms)) + size = sec.Size / uint64(nSym) ) - for i, offset := 0, uint64(0); i < len(syms); i, offset = i+1, offset+size { - mapSym, ok := syms[offset] + for i, offset := 0, uint64(0); i < nSym; i, offset = i+1, offset+size { + mapSym, ok := sec.symbols[offset] if !ok { return fmt.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset) } @@ -432,24 +523,46 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.Sectio return nil } -func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error { - if spec == nil { - return fmt.Errorf("missing BTF") - } +func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec) error { + for _, sec := range ec.sections { + if sec.kind != btfMapSection { + continue + } - for idx, sec := range mapSections { - syms := ec.symbolsPerSection[idx] - if len(syms) == 0 { + if ec.btf == nil { + return fmt.Errorf("missing BTF") + } + + if len(sec.symbols) == 0 { return fmt.Errorf("section %v: no symbols", sec.Name) } - for _, sym := range syms { + _, err := io.Copy(internal.DiscardZeroes{}, bufio.NewReader(sec.Open())) + if err != nil { + return fmt.Errorf("section %v: initializing BTF map definitions: %w", sec.Name, internal.ErrNotSupported) + } + + for _, sym := range sec.symbols { name := sym.Name if maps[name] != nil { return fmt.Errorf("section %v: map %v already exists", sec.Name, sym) } - mapSpec, err := mapSpecFromBTF(spec, name) + // A global Var is created by declaring a struct with a 'structure variable', + // as is common in eBPF C to declare eBPF maps. For example, + // `struct { ... } map_name ...;` emits a global variable `map_name` + // with the type of said struct (which can be anonymous). + var v btf.Var + if err := ec.btf.FindType(name, &v); err != nil { + return fmt.Errorf("cannot find global variable '%s' in BTF: %w", name, err) + } + + mapStruct, ok := v.Type.(*btf.Struct) + if !ok { + return fmt.Errorf("expected struct, got %s", v.Type) + } + + mapSpec, err := mapSpecFromBTF(name, mapStruct, false, ec.btf) if err != nil { return fmt.Errorf("map %v: %w", name, err) } @@ -461,31 +574,21 @@ func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec, mapSections map[elf.Sec return nil } -func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) { - btfMap, btfMapMembers, err := spec.Map(name) - if err != nil { - return nil, fmt.Errorf("can't get BTF: %w", err) - } - - keyType := btf.MapKey(btfMap) - size, err := btf.Sizeof(keyType) - if err != nil { - return nil, fmt.Errorf("can't get size of BTF key: %w", err) - } - keySize := uint32(size) - - valueType := btf.MapValue(btfMap) - size, err = btf.Sizeof(valueType) - if err != nil { - return nil, fmt.Errorf("can't get size of BTF value: %w", err) - } - valueSize := uint32(size) +// mapSpecFromBTF produces a MapSpec based on a btf.Struct def representing +// a BTF map definition. The name and spec arguments will be copied to the +// resulting MapSpec, and inner must be true on any resursive invocations. +func mapSpecFromBTF(name string, def *btf.Struct, inner bool, spec *btf.Spec) (*MapSpec, error) { var ( + key, value btf.Type + keySize, valueSize uint32 mapType, flags, maxEntries uint32 pinType PinType + innerMapSpec *MapSpec + err error ) - for _, member := range btfMapMembers { + + for i, member := range def.Members { switch member.Name { case "type": mapType, err = uintFromBTF(member.Type) @@ -505,8 +608,48 @@ func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) { return nil, fmt.Errorf("can't get BTF map max entries: %w", err) } + case "key": + if keySize != 0 { + return nil, errors.New("both key and key_size given") + } + + pk, ok := member.Type.(*btf.Pointer) + if !ok { + return nil, fmt.Errorf("key type is not a pointer: %T", member.Type) + } + + key = pk.Target + + size, err := btf.Sizeof(pk.Target) + if err != nil { + return nil, fmt.Errorf("can't get size of BTF key: %w", err) + } + + keySize = uint32(size) + + case "value": + if valueSize != 0 { + return nil, errors.New("both value and value_size given") + } + + vk, ok := member.Type.(*btf.Pointer) + if !ok { + return nil, fmt.Errorf("value type is not a pointer: %T", member.Type) + } + + value = vk.Target + + size, err := btf.Sizeof(vk.Target) + if err != nil { + return nil, fmt.Errorf("can't get size of BTF value: %w", err) + } + + valueSize = uint32(size) + case "key_size": - if _, isVoid := keyType.(*btf.Void); !isVoid { + // Key needs to be nil and keySize needs to be 0 for key_size to be + // considered a valid member. + if key != nil || keySize != 0 { return nil, errors.New("both key and key_size given") } @@ -516,7 +659,9 @@ func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) { } case "value_size": - if _, isVoid := valueType.(*btf.Void); !isVoid { + // Value needs to be nil and valueSize needs to be 0 for value_size to be + // considered a valid member. + if value != nil || valueSize != 0 { return nil, errors.New("both value and value_size given") } @@ -526,6 +671,10 @@ func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) { } case "pinning": + if inner { + return nil, errors.New("inner maps can't be pinned") + } + pinning, err := uintFromBTF(member.Type) if err != nil { return nil, fmt.Errorf("can't get pinning: %w", err) @@ -533,12 +682,58 @@ func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) { pinType = PinType(pinning) - case "key", "value": + case "values": + // The 'values' field in BTF map definitions is used for declaring map + // value types that are references to other BPF objects, like other maps + // or programs. It is always expected to be an array of pointers. + if i != len(def.Members)-1 { + return nil, errors.New("'values' must be the last member in a BTF map definition") + } + + if valueSize != 0 && valueSize != 4 { + return nil, errors.New("value_size must be 0 or 4") + } + valueSize = 4 + + valueType, err := resolveBTFArrayMacro(member.Type) + if err != nil { + return nil, fmt.Errorf("can't resolve type of member 'values': %w", err) + } + + switch t := valueType.(type) { + case *btf.Struct: + // The values member pointing to an array of structs means we're expecting + // a map-in-map declaration. + if MapType(mapType) != ArrayOfMaps && MapType(mapType) != HashOfMaps { + return nil, errors.New("outer map needs to be an array or a hash of maps") + } + if inner { + return nil, fmt.Errorf("nested inner maps are not supported") + } + + // This inner map spec is used as a map template, but it needs to be + // created as a traditional map before it can be used to do so. + // libbpf names the inner map template '.inner', but we + // opted for _inner to simplify validation logic. (dots only supported + // on kernels 5.2 and up) + // Pass the BTF spec from the parent object, since both parent and + // child must be created from the same BTF blob (on kernels that support BTF). + innerMapSpec, err = mapSpecFromBTF(name+"_inner", t, true, spec) + if err != nil { + return nil, fmt.Errorf("can't parse BTF map definition of inner map: %w", err) + } + + default: + return nil, fmt.Errorf("unsupported value type %q in 'values' field", t) + } + default: return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name) } } + bm := btf.NewMap(spec, key, value) + return &MapSpec{ Name: SanitizeName(name, -1), Type: MapType(mapType), @@ -546,8 +741,9 @@ func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) { ValueSize: valueSize, MaxEntries: maxEntries, Flags: flags, - BTF: btfMap, + BTF: &bm, Pinning: pinType, + InnerMap: innerMapSpec, }, nil } @@ -567,13 +763,40 @@ func uintFromBTF(typ btf.Type) (uint32, error) { return arr.Nelems, nil } -func (ec *elfCode) loadDataSections(maps map[string]*MapSpec, dataSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error { - if spec == nil { - return errors.New("data sections require BTF, make sure all consts are marked as static") +// resolveBTFArrayMacro resolves the __array macro, which declares an array +// of pointers to a given type. This function returns the target Type of +// the pointers in the array. +func resolveBTFArrayMacro(typ btf.Type) (btf.Type, error) { + arr, ok := typ.(*btf.Array) + if !ok { + return nil, fmt.Errorf("not an array: %v", typ) } - for _, sec := range dataSections { - btfMap, err := spec.Datasec(sec.Name) + ptr, ok := arr.Type.(*btf.Pointer) + if !ok { + return nil, fmt.Errorf("not an array of pointers: %v", typ) + } + + return ptr.Target, nil +} + +func (ec *elfCode) loadDataSections(maps map[string]*MapSpec) error { + for _, sec := range ec.sections { + if sec.kind != dataSection { + continue + } + + if sec.references == 0 { + // Prune data sections which are not referenced by any + // instructions. + continue + } + + if ec.btf == nil { + return errors.New("data sections require BTF, make sure all consts are marked as static") + } + + btfMap, err := ec.btf.Datasec(sec.Name) if err != nil { return err } @@ -678,69 +901,30 @@ func getProgType(sectionName string) (ProgramType, AttachType, string) { return UnspecifiedProgram, AttachNone, "" } -func (ec *elfCode) loadRelocations(sections map[elf.SectionIndex]*elf.Section) (map[elf.SectionIndex]map[uint64]elf.Symbol, map[elf.SectionIndex]bool, error) { - result := make(map[elf.SectionIndex]map[uint64]elf.Symbol) - targets := make(map[elf.SectionIndex]bool) - for idx, sec := range sections { - rels := make(map[uint64]elf.Symbol) +func (ec *elfCode) loadRelocations(sec *elf.Section, symbols []elf.Symbol) (map[uint64]elf.Symbol, error) { + rels := make(map[uint64]elf.Symbol) - if sec.Entsize < 16 { - return nil, nil, fmt.Errorf("section %s: relocations are less than 16 bytes", sec.Name) - } - - r := bufio.NewReader(sec.Open()) - for off := uint64(0); off < sec.Size; off += sec.Entsize { - ent := io.LimitReader(r, int64(sec.Entsize)) - - var rel elf.Rel64 - if binary.Read(ent, ec.ByteOrder, &rel) != nil { - return nil, nil, fmt.Errorf("can't parse relocation at offset %v", off) - } - - symNo := int(elf.R_SYM64(rel.Info) - 1) - if symNo >= len(ec.symbols) { - return nil, nil, fmt.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo) - } - - symbol := ec.symbols[symNo] - targets[symbol.Section] = true - rels[rel.Off] = ec.symbols[symNo] - } - - result[idx] = rels + if sec.Entsize < 16 { + return nil, fmt.Errorf("section %s: relocations are less than 16 bytes", sec.Name) } - return result, targets, nil -} -func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]elf.Symbol { - result := make(map[elf.SectionIndex]map[uint64]elf.Symbol) - for _, sym := range symbols { - switch elf.ST_TYPE(sym.Info) { - case elf.STT_NOTYPE: - // Older versions of LLVM doesn't tag - // symbols correctly. - break - case elf.STT_OBJECT: - break - case elf.STT_FUNC: - break - default: - continue + r := bufio.NewReader(sec.Open()) + for off := uint64(0); off < sec.Size; off += sec.Entsize { + ent := io.LimitReader(r, int64(sec.Entsize)) + + var rel elf.Rel64 + if binary.Read(ent, ec.ByteOrder, &rel) != nil { + return nil, fmt.Errorf("can't parse relocation at offset %v", off) } - if sym.Section == elf.SHN_UNDEF || sym.Section >= elf.SHN_LORESERVE { - continue + symNo := int(elf.R_SYM64(rel.Info) - 1) + if symNo >= len(symbols) { + return nil, fmt.Errorf("offset %d: symbol %d doesn't exist", off, symNo) } - if sym.Name == "" { - continue - } - - idx := sym.Section - if _, ok := result[idx]; !ok { - result[idx] = make(map[uint64]elf.Symbol) - } - result[idx][sym.Value] = sym + symbol := symbols[symNo] + rels[rel.Off] = symbol } - return result + + return rels, nil } diff --git a/vendor/github.com/cilium/ebpf/elf_reader_fuzz.go b/vendor/github.com/cilium/ebpf/elf_reader_fuzz.go new file mode 100644 index 000000000..d46d135f2 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/elf_reader_fuzz.go @@ -0,0 +1,21 @@ +// +build gofuzz + +// Use with https://github.com/dvyukov/go-fuzz + +package ebpf + +import "bytes" + +func FuzzLoadCollectionSpec(data []byte) int { + spec, err := LoadCollectionSpecFromReader(bytes.NewReader(data)) + if err != nil { + if spec != nil { + panic("spec is not nil") + } + return 0 + } + if spec == nil { + panic("spec is nil") + } + return 1 +} diff --git a/vendor/github.com/cilium/ebpf/go.mod b/vendor/github.com/cilium/ebpf/go.mod index 2d1004425..df8139621 100644 --- a/vendor/github.com/cilium/ebpf/go.mod +++ b/vendor/github.com/cilium/ebpf/go.mod @@ -1,8 +1,9 @@ module github.com/cilium/ebpf -go 1.14 +go 1.15 require ( - github.com/google/go-cmp v0.5.2 - golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 + github.com/frankban/quicktest v1.11.3 + github.com/google/go-cmp v0.5.4 + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c ) diff --git a/vendor/github.com/cilium/ebpf/go.sum b/vendor/github.com/cilium/ebpf/go.sum index 47dd82f29..a5039262a 100644 --- a/vendor/github.com/cilium/ebpf/go.sum +++ b/vendor/github.com/cilium/ebpf/go.sum @@ -1,6 +1,13 @@ -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/vendor/github.com/cilium/ebpf/info.go b/vendor/github.com/cilium/ebpf/info.go new file mode 100644 index 000000000..b95131ef5 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/info.go @@ -0,0 +1,239 @@ +package ebpf + +import ( + "bufio" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "strings" + "syscall" + "time" + + "github.com/cilium/ebpf/internal" +) + +// MapInfo describes a map. +type MapInfo struct { + Type MapType + id MapID + KeySize uint32 + ValueSize uint32 + MaxEntries uint32 + Flags uint32 + // Name as supplied by user space at load time. + Name string +} + +func newMapInfoFromFd(fd *internal.FD) (*MapInfo, error) { + info, err := bpfGetMapInfoByFD(fd) + if errors.Is(err, syscall.EINVAL) { + return newMapInfoFromProc(fd) + } + if err != nil { + return nil, err + } + + return &MapInfo{ + MapType(info.map_type), + MapID(info.id), + info.key_size, + info.value_size, + info.max_entries, + info.map_flags, + // name is available from 4.15. + internal.CString(info.name[:]), + }, nil +} + +func newMapInfoFromProc(fd *internal.FD) (*MapInfo, error) { + var mi MapInfo + err := scanFdInfo(fd, map[string]interface{}{ + "map_type": &mi.Type, + "key_size": &mi.KeySize, + "value_size": &mi.ValueSize, + "max_entries": &mi.MaxEntries, + "map_flags": &mi.Flags, + }) + if err != nil { + return nil, err + } + return &mi, nil +} + +// ID returns the map ID. +// +// Available from 4.13. +// +// The bool return value indicates whether this optional field is available. +func (mi *MapInfo) ID() (MapID, bool) { + return mi.id, mi.id > 0 +} + +// programStats holds statistics of a program. +type programStats struct { + // Total accumulated runtime of the program ins ns. + runtime time.Duration + // Total number of times the program was called. + runCount uint64 +} + +// ProgramInfo describes a program. +type ProgramInfo struct { + Type ProgramType + id ProgramID + // Truncated hash of the BPF bytecode. + Tag string + // Name as supplied by user space at load time. + Name string + + stats *programStats +} + +func newProgramInfoFromFd(fd *internal.FD) (*ProgramInfo, error) { + info, err := bpfGetProgInfoByFD(fd) + if errors.Is(err, syscall.EINVAL) { + return newProgramInfoFromProc(fd) + } + if err != nil { + return nil, err + } + + return &ProgramInfo{ + Type: ProgramType(info.prog_type), + id: ProgramID(info.id), + // tag is available if the kernel supports BPF_PROG_GET_INFO_BY_FD. + Tag: hex.EncodeToString(info.tag[:]), + // name is available from 4.15. + Name: internal.CString(info.name[:]), + stats: &programStats{ + runtime: time.Duration(info.run_time_ns), + runCount: info.run_cnt, + }, + }, nil +} + +func newProgramInfoFromProc(fd *internal.FD) (*ProgramInfo, error) { + var info ProgramInfo + err := scanFdInfo(fd, map[string]interface{}{ + "prog_type": &info.Type, + "prog_tag": &info.Tag, + }) + if errors.Is(err, errMissingFields) { + return nil, &internal.UnsupportedFeatureError{ + Name: "reading program info from /proc/self/fdinfo", + MinimumVersion: internal.Version{4, 10, 0}, + } + } + if err != nil { + return nil, err + } + + return &info, nil +} + +// ID returns the program ID. +// +// Available from 4.13. +// +// The bool return value indicates whether this optional field is available. +func (pi *ProgramInfo) ID() (ProgramID, bool) { + return pi.id, pi.id > 0 +} + +// RunCount returns the total number of times the program was called. +// +// Can return 0 if the collection of statistics is not enabled. See EnableStats(). +// The bool return value indicates whether this optional field is available. +func (pi *ProgramInfo) RunCount() (uint64, bool) { + if pi.stats != nil { + return pi.stats.runCount, true + } + return 0, false +} + +// Runtime returns the total accumulated runtime of the program. +// +// Can return 0 if the collection of statistics is not enabled. See EnableStats(). +// The bool return value indicates whether this optional field is available. +func (pi *ProgramInfo) Runtime() (time.Duration, bool) { + if pi.stats != nil { + return pi.stats.runtime, true + } + return time.Duration(0), false +} + +func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error { + raw, err := fd.Value() + if err != nil { + return err + } + + fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", raw)) + if err != nil { + return err + } + defer fh.Close() + + if err := scanFdInfoReader(fh, fields); err != nil { + return fmt.Errorf("%s: %w", fh.Name(), err) + } + return nil +} + +var errMissingFields = errors.New("missing fields") + +func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error { + var ( + scanner = bufio.NewScanner(r) + scanned int + ) + + for scanner.Scan() { + parts := strings.SplitN(scanner.Text(), "\t", 2) + if len(parts) != 2 { + continue + } + + name := strings.TrimSuffix(parts[0], ":") + field, ok := fields[string(name)] + if !ok { + continue + } + + if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 { + return fmt.Errorf("can't parse field %s: %v", name, err) + } + + scanned++ + } + + if err := scanner.Err(); err != nil { + return err + } + + if scanned != len(fields) { + return errMissingFields + } + + return nil +} + +// EnableStats starts the measuring of the runtime +// and run counts of eBPF programs. +// +// Collecting statistics can have an impact on the performance. +// +// Requires at least 5.8. +func EnableStats(which uint32) (io.Closer, error) { + attr := internal.BPFEnableStatsAttr{ + StatsType: which, + } + + fd, err := internal.BPFEnableStats(&attr) + if err != nil { + return nil, err + } + return fd, nil +} diff --git a/vendor/github.com/cilium/ebpf/internal/btf/btf.go b/vendor/github.com/cilium/ebpf/internal/btf/btf.go index 7a904a02c..1e66d9476 100644 --- a/vendor/github.com/cilium/ebpf/internal/btf/btf.go +++ b/vendor/github.com/cilium/ebpf/internal/btf/btf.go @@ -29,12 +29,14 @@ var ( // Spec represents decoded BTF. type Spec struct { - rawTypes []rawType - strings stringTable - types map[string][]namedType - funcInfos map[string]extInfo - lineInfos map[string]extInfo - byteOrder binary.ByteOrder + rawTypes []rawType + strings stringTable + types []Type + namedTypes map[string][]namedType + funcInfos map[string]extInfo + lineInfos map[string]extInfo + coreRelos map[string]bpfCoreRelos + byteOrder binary.ByteOrder } type btfHeader struct { @@ -53,7 +55,7 @@ type btfHeader struct { // // Returns a nil Spec and no error if no BTF was present. func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) { - file, err := elf.NewFile(rd) + file, err := internal.NewSafeELFFile(rd) if err != nil { return nil, err } @@ -80,6 +82,10 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) { continue } + if int(symbol.Section) >= len(file.Sections) { + return nil, fmt.Errorf("symbol %s: invalid section %d", symbol.Name, symbol.Section) + } + secName := file.Sections[symbol.Section].Name if _, ok := sectionSizes[secName]; !ok { continue @@ -101,7 +107,7 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) { return spec, nil } - spec.funcInfos, spec.lineInfos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings) + spec.funcInfos, spec.lineInfos, spec.coreRelos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings) if err != nil { return nil, fmt.Errorf("can't read ext info: %w", err) } @@ -109,7 +115,7 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) { return spec, nil } -func findBtfSections(file *elf.File) (*elf.Section, *elf.Section, map[string]uint32, error) { +func findBtfSections(file *internal.SafeELFFile) (*elf.Section, *elf.Section, map[string]uint32, error) { var ( btfSection *elf.Section btfExtSection *elf.Section @@ -138,7 +144,7 @@ func findBtfSections(file *elf.File) (*elf.Section, *elf.Section, map[string]uin } func loadSpecFromVmlinux(rd io.ReaderAt) (*Spec, error) { - file, err := elf.NewFile(rd) + file, err := internal.NewSafeELFFile(rd) if err != nil { return nil, err } @@ -165,16 +171,17 @@ func loadNakedSpec(btf io.ReadSeeker, bo binary.ByteOrder, sectionSizes map[stri return nil, err } - types, err := inflateRawTypes(rawTypes, rawStrings) + types, typesByName, err := inflateRawTypes(rawTypes, rawStrings) if err != nil { return nil, err } return &Spec{ - rawTypes: rawTypes, - types: types, - strings: rawStrings, - byteOrder: bo, + rawTypes: rawTypes, + namedTypes: typesByName, + types: types, + strings: rawStrings, + byteOrder: bo, }, nil } @@ -311,10 +318,14 @@ func fixupDatasec(rawTypes []rawType, rawStrings stringTable, sectionSizes map[s return err } - if name == ".kconfig" || name == ".ksym" { + if name == ".kconfig" || name == ".ksyms" { return fmt.Errorf("reference to %s: %w", name, ErrNotSupported) } + if rawTypes[i].SizeType != 0 { + continue + } + size, ok := sectionSizes[name] if !ok { return fmt.Errorf("data section %s: missing size", name) @@ -421,64 +432,19 @@ func (s *Spec) Program(name string, length uint64) (*Program, error) { return nil, errors.New("length musn't be zero") } - if s.funcInfos == nil && s.lineInfos == nil { + if s.funcInfos == nil && s.lineInfos == nil && s.coreRelos == nil { return nil, fmt.Errorf("BTF for section %s: %w", name, ErrNoExtendedInfo) } funcInfos, funcOK := s.funcInfos[name] lineInfos, lineOK := s.lineInfos[name] + coreRelos, coreOK := s.coreRelos[name] - if !funcOK && !lineOK { + if !funcOK && !lineOK && !coreOK { return nil, fmt.Errorf("no extended BTF info for section %s", name) } - return &Program{s, length, funcInfos, lineInfos}, nil -} - -// Map finds the BTF for a map. -// -// Returns an error if there is no BTF for the given name. -func (s *Spec) Map(name string) (*Map, []Member, error) { - var mapVar Var - if err := s.FindType(name, &mapVar); err != nil { - return nil, nil, err - } - - mapStruct, ok := mapVar.Type.(*Struct) - if !ok { - return nil, nil, fmt.Errorf("expected struct, have %s", mapVar.Type) - } - - var key, value Type - for _, member := range mapStruct.Members { - switch member.Name { - case "key": - key = member.Type - if pk, isPtr := key.(*Pointer); !isPtr { - return nil, nil, fmt.Errorf("key type is not a pointer: %T", key) - } else { - key = pk.Target - } - - case "value": - value = member.Type - if vk, isPtr := value.(*Pointer); !isPtr { - return nil, nil, fmt.Errorf("value type is not a pointer: %T", value) - } else { - value = vk.Target - } - } - } - - if key == nil { - key = (*Void)(nil) - } - - if value == nil { - value = (*Void)(nil) - } - - return &Map{s, key, value}, mapStruct.Members, nil + return &Program{s, length, funcInfos, lineInfos, coreRelos}, nil } // Datasec returns the BTF required to create maps which represent data sections. @@ -488,7 +454,8 @@ func (s *Spec) Datasec(name string) (*Map, error) { return nil, fmt.Errorf("data section %s: can't get BTF: %w", name, err) } - return &Map{s, &Void{}, &datasec}, nil + m := NewMap(s, &Void{}, &datasec) + return &m, nil } // FindType searches for a type with a specific name. @@ -503,7 +470,7 @@ func (s *Spec) FindType(name string, typ Type) error { candidate Type ) - for _, typ := range s.types[essentialName(name)] { + for _, typ := range s.namedTypes[essentialName(name)] { if reflect.TypeOf(typ) != wanted { continue } @@ -599,6 +566,23 @@ type Map struct { key, value Type } +// NewMap returns a new Map containing the given values. +// The key and value arguments are initialized to Void if nil values are given. +func NewMap(spec *Spec, key Type, value Type) Map { + if key == nil { + key = &Void{} + } + if value == nil { + value = &Void{} + } + + return Map{ + spec: spec, + key: key, + value: value, + } +} + // MapSpec should be a method on Map, but is a free function // to hide it from users of the ebpf package. func MapSpec(m *Map) *Spec { @@ -622,6 +606,7 @@ type Program struct { spec *Spec length uint64 funcInfos, lineInfos extInfo + coreRelos bpfCoreRelos } // ProgramSpec returns the Spec needed for loading function and line infos into the kernel. @@ -647,9 +632,10 @@ func ProgramAppend(s, other *Program) error { return fmt.Errorf("line infos: %w", err) } - s.length += other.length s.funcInfos = funcInfos s.lineInfos = lineInfos + s.coreRelos = s.coreRelos.append(other.coreRelos, s.length) + s.length += other.length return nil } @@ -679,6 +665,19 @@ func ProgramLineInfos(s *Program) (recordSize uint32, bytes []byte, err error) { return s.lineInfos.recordSize, bytes, nil } +// ProgramRelocations returns the CO-RE relocations required to adjust the +// program to the target. +// +// This is a free function instead of a method to hide it from users +// of package ebpf. +func ProgramRelocations(s *Program, target *Spec) (map[uint64]Relocation, error) { + if len(s.coreRelos) == 0 { + return nil, nil + } + + return coreRelocate(s.spec, target, s.coreRelos) +} + type bpfLoadBTFAttr struct { btf internal.Pointer logBuf internal.Pointer @@ -718,7 +717,7 @@ func marshalBTF(types interface{}, strings []byte, bo binary.ByteOrder) []byte { return buf.Bytes() } -var haveBTF = internal.FeatureTest("BTF", "5.1", func() (bool, error) { +var haveBTF = internal.FeatureTest("BTF", "5.1", func() error { var ( types struct { Integer btfType @@ -742,15 +741,24 @@ var haveBTF = internal.FeatureTest("BTF", "5.1", func() (bool, error) { btf: internal.NewSlicePointer(btf), btfSize: uint32(len(btf)), }) - if err == nil { - fd.Close() + if errors.Is(err, unix.EINVAL) || errors.Is(err, unix.EPERM) { + // Treat both EINVAL and EPERM as not supported: loading the program + // might still succeed without BTF. + return internal.ErrNotSupported } - // Check for EINVAL specifically, rather than err != nil since we - // otherwise misdetect due to insufficient permissions. - return !errors.Is(err, unix.EINVAL), nil + if err != nil { + return err + } + + fd.Close() + return nil }) -var haveFuncLinkage = internal.FeatureTest("BTF func linkage", "5.6", func() (bool, error) { +var haveFuncLinkage = internal.FeatureTest("BTF func linkage", "5.6", func() error { + if err := haveBTF(); err != nil { + return err + } + var ( types struct { FuncProto btfType @@ -771,11 +779,13 @@ var haveFuncLinkage = internal.FeatureTest("BTF func linkage", "5.6", func() (bo btf: internal.NewSlicePointer(btf), btfSize: uint32(len(btf)), }) - if err == nil { - fd.Close() + if errors.Is(err, unix.EINVAL) { + return internal.ErrNotSupported + } + if err != nil { + return err } - // Check for EINVAL specifically, rather than err != nil since we - // otherwise misdetect due to insufficient permissions. - return !errors.Is(err, unix.EINVAL), nil + fd.Close() + return nil }) diff --git a/vendor/github.com/cilium/ebpf/internal/btf/core.go b/vendor/github.com/cilium/ebpf/internal/btf/core.go new file mode 100644 index 000000000..52b59ed18 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/btf/core.go @@ -0,0 +1,388 @@ +package btf + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +// Code in this file is derived from libbpf, which is available under a BSD +// 2-Clause license. + +// Relocation describes a CO-RE relocation. +type Relocation struct { + Current uint32 + New uint32 +} + +func (r Relocation) equal(other Relocation) bool { + return r.Current == other.Current && r.New == other.New +} + +// coreReloKind is the type of CO-RE relocation +type coreReloKind uint32 + +const ( + reloFieldByteOffset coreReloKind = iota /* field byte offset */ + reloFieldByteSize /* field size in bytes */ + reloFieldExists /* field existence in target kernel */ + reloFieldSigned /* field signedness (0 - unsigned, 1 - signed) */ + reloFieldLShiftU64 /* bitfield-specific left bitshift */ + reloFieldRShiftU64 /* bitfield-specific right bitshift */ + reloTypeIDLocal /* type ID in local BPF object */ + reloTypeIDTarget /* type ID in target kernel */ + reloTypeExists /* type existence in target kernel */ + reloTypeSize /* type size in bytes */ + reloEnumvalExists /* enum value existence in target kernel */ + reloEnumvalValue /* enum value integer value */ +) + +func (k coreReloKind) String() string { + switch k { + case reloFieldByteOffset: + return "byte_off" + case reloFieldByteSize: + return "byte_sz" + case reloFieldExists: + return "field_exists" + case reloFieldSigned: + return "signed" + case reloFieldLShiftU64: + return "lshift_u64" + case reloFieldRShiftU64: + return "rshift_u64" + case reloTypeIDLocal: + return "local_type_id" + case reloTypeIDTarget: + return "target_type_id" + case reloTypeExists: + return "type_exists" + case reloTypeSize: + return "type_size" + case reloEnumvalExists: + return "enumval_exists" + case reloEnumvalValue: + return "enumval_value" + default: + return "unknown" + } +} + +func coreRelocate(local, target *Spec, coreRelos bpfCoreRelos) (map[uint64]Relocation, error) { + if target == nil { + var err error + target, err = loadKernelSpec() + if err != nil { + return nil, err + } + } + + if local.byteOrder != target.byteOrder { + return nil, fmt.Errorf("can't relocate %s against %s", local.byteOrder, target.byteOrder) + } + + relocations := make(map[uint64]Relocation, len(coreRelos)) + for _, relo := range coreRelos { + accessorStr, err := local.strings.Lookup(relo.AccessStrOff) + if err != nil { + return nil, err + } + + accessor, err := parseCoreAccessor(accessorStr) + if err != nil { + return nil, fmt.Errorf("accessor %q: %s", accessorStr, err) + } + + if int(relo.TypeID) >= len(local.types) { + return nil, fmt.Errorf("invalid type id %d", relo.TypeID) + } + + typ := local.types[relo.TypeID] + + if relo.ReloKind == reloTypeIDLocal { + relocations[uint64(relo.InsnOff)] = Relocation{ + uint32(typ.ID()), + uint32(typ.ID()), + } + continue + } + + named, ok := typ.(namedType) + if !ok || named.name() == "" { + return nil, fmt.Errorf("relocate anonymous type %s: %w", typ.String(), ErrNotSupported) + } + + name := essentialName(named.name()) + res, err := coreCalculateRelocation(typ, target.namedTypes[name], relo.ReloKind, accessor) + if err != nil { + return nil, fmt.Errorf("relocate %s: %w", name, err) + } + + relocations[uint64(relo.InsnOff)] = res + } + + return relocations, nil +} + +var errAmbiguousRelocation = errors.New("ambiguous relocation") + +func coreCalculateRelocation(local Type, targets []namedType, kind coreReloKind, localAccessor coreAccessor) (Relocation, error) { + var relos []Relocation + var matches []Type + for _, target := range targets { + switch kind { + case reloTypeIDTarget: + if localAccessor[0] != 0 { + return Relocation{}, fmt.Errorf("%s: unexpected non-zero accessor", kind) + } + + if compat, err := coreAreTypesCompatible(local, target); err != nil { + return Relocation{}, fmt.Errorf("%s: %s", kind, err) + } else if !compat { + continue + } + + relos = append(relos, Relocation{uint32(target.ID()), uint32(target.ID())}) + + default: + return Relocation{}, fmt.Errorf("relocation %s: %w", kind, ErrNotSupported) + } + matches = append(matches, target) + } + + if len(relos) == 0 { + // TODO: Add switch for existence checks like reloEnumvalExists here. + + // TODO: This might have to be poisoned. + return Relocation{}, fmt.Errorf("no relocation found, tried %v", targets) + } + + relo := relos[0] + for _, altRelo := range relos[1:] { + if !altRelo.equal(relo) { + return Relocation{}, fmt.Errorf("multiple types %v match: %w", matches, errAmbiguousRelocation) + } + } + + return relo, nil +} + +/* coreAccessor contains a path through a struct. It contains at least one index. + * + * The interpretation depends on the kind of the relocation. The following is + * taken from struct bpf_core_relo in libbpf_internal.h: + * + * - for field-based relocations, string encodes an accessed field using + * a sequence of field and array indices, separated by colon (:). It's + * conceptually very close to LLVM's getelementptr ([0]) instruction's + * arguments for identifying offset to a field. + * - for type-based relocations, strings is expected to be just "0"; + * - for enum value-based relocations, string contains an index of enum + * value within its enum type; + * + * Example to provide a better feel. + * + * struct sample { + * int a; + * struct { + * int b[10]; + * }; + * }; + * + * struct sample s = ...; + * int x = &s->a; // encoded as "0:0" (a is field #0) + * int y = &s->b[5]; // encoded as "0:1:0:5" (anon struct is field #1, + * // b is field #0 inside anon struct, accessing elem #5) + * int z = &s[10]->b; // encoded as "10:1" (ptr is used as an array) + */ +type coreAccessor []int + +func parseCoreAccessor(accessor string) (coreAccessor, error) { + if accessor == "" { + return nil, fmt.Errorf("empty accessor") + } + + var result coreAccessor + parts := strings.Split(accessor, ":") + for _, part := range parts { + // 31 bits to avoid overflowing int on 32 bit platforms. + index, err := strconv.ParseUint(part, 10, 31) + if err != nil { + return nil, fmt.Errorf("accessor index %q: %s", part, err) + } + + result = append(result, int(index)) + } + + return result, nil +} + +/* The comment below is from bpf_core_types_are_compat in libbpf.c: + * + * Check local and target types for compatibility. This check is used for + * type-based CO-RE relocations and follow slightly different rules than + * field-based relocations. This function assumes that root types were already + * checked for name match. Beyond that initial root-level name check, names + * are completely ignored. Compatibility rules are as follows: + * - any two STRUCTs/UNIONs/FWDs/ENUMs/INTs are considered compatible, but + * kind should match for local and target types (i.e., STRUCT is not + * compatible with UNION); + * - for ENUMs, the size is ignored; + * - for INT, size and signedness are ignored; + * - for ARRAY, dimensionality is ignored, element types are checked for + * compatibility recursively; + * - CONST/VOLATILE/RESTRICT modifiers are ignored; + * - TYPEDEFs/PTRs are compatible if types they pointing to are compatible; + * - FUNC_PROTOs are compatible if they have compatible signature: same + * number of input args and compatible return and argument types. + * These rules are not set in stone and probably will be adjusted as we get + * more experience with using BPF CO-RE relocations. + */ +func coreAreTypesCompatible(localType Type, targetType Type) (bool, error) { + var ( + localTs, targetTs typeDeque + l, t = &localType, &targetType + depth = 0 + ) + + for ; l != nil && t != nil; l, t = localTs.shift(), targetTs.shift() { + if depth >= maxTypeDepth { + return false, errors.New("types are nested too deep") + } + + localType = skipQualifierAndTypedef(*l) + targetType = skipQualifierAndTypedef(*t) + + if reflect.TypeOf(localType) != reflect.TypeOf(targetType) { + return false, nil + } + + switch lv := (localType).(type) { + case *Void, *Struct, *Union, *Enum, *Fwd: + // Nothing to do here + + case *Int: + tv := targetType.(*Int) + if lv.isBitfield() || tv.isBitfield() { + return false, nil + } + + case *Pointer, *Array: + depth++ + localType.walk(&localTs) + targetType.walk(&targetTs) + + case *FuncProto: + tv := targetType.(*FuncProto) + if len(lv.Params) != len(tv.Params) { + return false, nil + } + + depth++ + localType.walk(&localTs) + targetType.walk(&targetTs) + + default: + return false, fmt.Errorf("unsupported type %T", localType) + } + } + + if l != nil { + return false, fmt.Errorf("dangling local type %T", *l) + } + + if t != nil { + return false, fmt.Errorf("dangling target type %T", *t) + } + + return true, nil +} + +/* The comment below is from bpf_core_fields_are_compat in libbpf.c: + * + * Check two types for compatibility for the purpose of field access + * relocation. const/volatile/restrict and typedefs are skipped to ensure we + * are relocating semantically compatible entities: + * - any two STRUCTs/UNIONs are compatible and can be mixed; + * - any two FWDs are compatible, if their names match (modulo flavor suffix); + * - any two PTRs are always compatible; + * - for ENUMs, names should be the same (ignoring flavor suffix) or at + * least one of enums should be anonymous; + * - for ENUMs, check sizes, names are ignored; + * - for INT, size and signedness are ignored; + * - for ARRAY, dimensionality is ignored, element types are checked for + * compatibility recursively; + * - everything else shouldn't be ever a target of relocation. + * These rules are not set in stone and probably will be adjusted as we get + * more experience with using BPF CO-RE relocations. + */ +func coreAreMembersCompatible(localType Type, targetType Type) (bool, error) { + doNamesMatch := func(a, b string) bool { + if a == "" || b == "" { + // allow anonymous and named type to match + return true + } + + return essentialName(a) == essentialName(b) + } + + for depth := 0; depth <= maxTypeDepth; depth++ { + localType = skipQualifierAndTypedef(localType) + targetType = skipQualifierAndTypedef(targetType) + + _, lok := localType.(composite) + _, tok := targetType.(composite) + if lok && tok { + return true, nil + } + + if reflect.TypeOf(localType) != reflect.TypeOf(targetType) { + return false, nil + } + + switch lv := localType.(type) { + case *Pointer: + return true, nil + + case *Enum: + tv := targetType.(*Enum) + return doNamesMatch(lv.name(), tv.name()), nil + + case *Fwd: + tv := targetType.(*Fwd) + return doNamesMatch(lv.name(), tv.name()), nil + + case *Int: + tv := targetType.(*Int) + return !lv.isBitfield() && !tv.isBitfield(), nil + + case *Array: + tv := targetType.(*Array) + + localType = lv.Type + targetType = tv.Type + + default: + return false, fmt.Errorf("unsupported type %T", localType) + } + } + + return false, errors.New("types are nested too deep") +} + +func skipQualifierAndTypedef(typ Type) Type { + result := typ + for depth := 0; depth <= maxTypeDepth; depth++ { + switch v := (result).(type) { + case qualifier: + result = v.qualify() + case *Typedef: + result = v.Type + default: + return result + } + } + return typ +} diff --git a/vendor/github.com/cilium/ebpf/internal/btf/ext_info.go b/vendor/github.com/cilium/ebpf/internal/btf/ext_info.go index c1c82ec7f..6a21b6bda 100644 --- a/vendor/github.com/cilium/ebpf/internal/btf/ext_info.go +++ b/vendor/github.com/cilium/ebpf/internal/btf/ext_info.go @@ -25,57 +25,82 @@ type btfExtHeader struct { LineInfoLen uint32 } -func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, err error) { +type btfExtCoreHeader struct { + CoreReloOff uint32 + CoreReloLen uint32 +} + +func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, coreRelos map[string]bpfCoreRelos, err error) { var header btfExtHeader + var coreHeader btfExtCoreHeader if err := binary.Read(r, bo, &header); err != nil { - return nil, nil, fmt.Errorf("can't read header: %v", err) + return nil, nil, nil, fmt.Errorf("can't read header: %v", err) } if header.Magic != btfMagic { - return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic) + return nil, nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic) } if header.Version != 1 { - return nil, nil, fmt.Errorf("unexpected version %v", header.Version) + return nil, nil, nil, fmt.Errorf("unexpected version %v", header.Version) } if header.Flags != 0 { - return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags) + return nil, nil, nil, fmt.Errorf("unsupported flags %v", header.Flags) } remainder := int64(header.HdrLen) - int64(binary.Size(&header)) if remainder < 0 { - return nil, nil, errors.New("header is too short") + return nil, nil, nil, errors.New("header is too short") + } + + coreHdrSize := int64(binary.Size(&coreHeader)) + if remainder >= coreHdrSize { + if err := binary.Read(r, bo, &coreHeader); err != nil { + return nil, nil, nil, fmt.Errorf("can't read CO-RE relocation header: %v", err) + } + remainder -= coreHdrSize } // Of course, the .BTF.ext header has different semantics than the // .BTF ext header. We need to ignore non-null values. _, err = io.CopyN(ioutil.Discard, r, remainder) if err != nil { - return nil, nil, fmt.Errorf("header padding: %v", err) + return nil, nil, nil, fmt.Errorf("header padding: %v", err) } if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil { - return nil, nil, fmt.Errorf("can't seek to function info section: %v", err) + return nil, nil, nil, fmt.Errorf("can't seek to function info section: %v", err) } buf := bufio.NewReader(io.LimitReader(r, int64(header.FuncInfoLen))) funcInfo, err = parseExtInfo(buf, bo, strings) if err != nil { - return nil, nil, fmt.Errorf("function info: %w", err) + return nil, nil, nil, fmt.Errorf("function info: %w", err) } if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil { - return nil, nil, fmt.Errorf("can't seek to line info section: %v", err) + return nil, nil, nil, fmt.Errorf("can't seek to line info section: %v", err) } buf = bufio.NewReader(io.LimitReader(r, int64(header.LineInfoLen))) lineInfo, err = parseExtInfo(buf, bo, strings) if err != nil { - return nil, nil, fmt.Errorf("line info: %w", err) + return nil, nil, nil, fmt.Errorf("line info: %w", err) } - return funcInfo, lineInfo, nil + if coreHeader.CoreReloOff > 0 && coreHeader.CoreReloLen > 0 { + if _, err := r.Seek(int64(header.HdrLen+coreHeader.CoreReloOff), io.SeekStart); err != nil { + return nil, nil, nil, fmt.Errorf("can't seek to CO-RE relocation section: %v", err) + } + + coreRelos, err = parseExtInfoRelos(io.LimitReader(r, int64(coreHeader.CoreReloLen)), bo, strings) + if err != nil { + return nil, nil, nil, fmt.Errorf("CO-RE relocation info: %w", err) + } + } + + return funcInfo, lineInfo, coreRelos, nil } type btfExtInfoSec struct { @@ -147,20 +172,9 @@ func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[st result := make(map[string]extInfo) for { - var infoHeader btfExtInfoSec - if err := binary.Read(r, bo, &infoHeader); err == io.EOF { + secName, infoHeader, err := parseExtInfoHeader(r, bo, strings) + if errors.Is(err, io.EOF) { return result, nil - } else if err != nil { - return nil, fmt.Errorf("can't read ext info header: %v", err) - } - - secName, err := strings.Lookup(infoHeader.SecNameOff) - if err != nil { - return nil, fmt.Errorf("can't get section name: %w", err) - } - - if infoHeader.NumInfo == 0 { - return nil, fmt.Errorf("section %s has invalid number of records", secName) } var records []extInfoRecord @@ -188,3 +202,80 @@ func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[st } } } + +// bpfCoreRelo matches `struct bpf_core_relo` from the kernel +type bpfCoreRelo struct { + InsnOff uint32 + TypeID TypeID + AccessStrOff uint32 + ReloKind coreReloKind +} + +type bpfCoreRelos []bpfCoreRelo + +// append two slices of extInfoRelo to each other. The InsnOff of b are adjusted +// by offset. +func (r bpfCoreRelos) append(other bpfCoreRelos, offset uint64) bpfCoreRelos { + result := make([]bpfCoreRelo, 0, len(r)+len(other)) + result = append(result, r...) + for _, relo := range other { + relo.InsnOff += uint32(offset) + result = append(result, relo) + } + return result +} + +var extInfoReloSize = binary.Size(bpfCoreRelo{}) + +func parseExtInfoRelos(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]bpfCoreRelos, error) { + var recordSize uint32 + if err := binary.Read(r, bo, &recordSize); err != nil { + return nil, fmt.Errorf("read record size: %v", err) + } + + if recordSize != uint32(extInfoReloSize) { + return nil, fmt.Errorf("expected record size %d, got %d", extInfoReloSize, recordSize) + } + + result := make(map[string]bpfCoreRelos) + for { + secName, infoHeader, err := parseExtInfoHeader(r, bo, strings) + if errors.Is(err, io.EOF) { + return result, nil + } + + var relos []bpfCoreRelo + for i := uint32(0); i < infoHeader.NumInfo; i++ { + var relo bpfCoreRelo + if err := binary.Read(r, bo, &relo); err != nil { + return nil, fmt.Errorf("section %v: read record: %v", secName, err) + } + + if relo.InsnOff%asm.InstructionSize != 0 { + return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, relo.InsnOff) + } + + relos = append(relos, relo) + } + + result[secName] = relos + } +} + +func parseExtInfoHeader(r io.Reader, bo binary.ByteOrder, strings stringTable) (string, *btfExtInfoSec, error) { + var infoHeader btfExtInfoSec + if err := binary.Read(r, bo, &infoHeader); err != nil { + return "", nil, fmt.Errorf("read ext info header: %w", err) + } + + secName, err := strings.Lookup(infoHeader.SecNameOff) + if err != nil { + return "", nil, fmt.Errorf("get section name: %w", err) + } + + if infoHeader.NumInfo == 0 { + return "", nil, fmt.Errorf("section %s has zero records", secName) + } + + return secName, &infoHeader, nil +} diff --git a/vendor/github.com/cilium/ebpf/internal/btf/types.go b/vendor/github.com/cilium/ebpf/internal/btf/types.go index a93039bd5..9e1fd8d0b 100644 --- a/vendor/github.com/cilium/ebpf/internal/btf/types.go +++ b/vendor/github.com/cilium/ebpf/internal/btf/types.go @@ -21,12 +21,14 @@ func (tid TypeID) ID() TypeID { type Type interface { ID() TypeID + String() string + // Make a copy of the type, without copying Type members. copy() Type // Enumerate all nested Types. Repeated calls must visit nested // types in the same order. - walk(*copyStack) + walk(*typeDeque) } // namedType is a type with a name. @@ -50,9 +52,10 @@ func (n Name) name() string { type Void struct{} func (v *Void) ID() TypeID { return 0 } +func (v *Void) String() string { return "void#0" } func (v *Void) size() uint32 { return 0 } func (v *Void) copy() Type { return (*Void)(nil) } -func (v *Void) walk(*copyStack) {} +func (v *Void) walk(*typeDeque) {} type IntEncoding byte @@ -78,21 +81,54 @@ type Int struct { var _ namedType = (*Int)(nil) +func (i *Int) String() string { + var s strings.Builder + + switch { + case i.Encoding&Char != 0: + s.WriteString("char") + case i.Encoding&Bool != 0: + s.WriteString("bool") + default: + if i.Encoding&Signed == 0 { + s.WriteRune('u') + } + s.WriteString("int") + fmt.Fprintf(&s, "%d", i.Size*8) + } + + fmt.Fprintf(&s, "#%d", i.TypeID) + + if i.Bits > 0 { + fmt.Fprintf(&s, "[bits=%d]", i.Bits) + } + + return s.String() +} + func (i *Int) size() uint32 { return i.Size } -func (i *Int) walk(*copyStack) {} +func (i *Int) walk(*typeDeque) {} func (i *Int) copy() Type { cpy := *i return &cpy } +func (i *Int) isBitfield() bool { + return i.Offset > 0 +} + // Pointer is a pointer to another type. type Pointer struct { TypeID Target Type } -func (p *Pointer) size() uint32 { return 8 } -func (p *Pointer) walk(cs *copyStack) { cs.push(&p.Target) } +func (p *Pointer) String() string { + return fmt.Sprintf("pointer#%d[target=#%d]", p.TypeID, p.Target.ID()) +} + +func (p *Pointer) size() uint32 { return 8 } +func (p *Pointer) walk(tdq *typeDeque) { tdq.push(&p.Target) } func (p *Pointer) copy() Type { cpy := *p return &cpy @@ -105,7 +141,11 @@ type Array struct { Nelems uint32 } -func (arr *Array) walk(cs *copyStack) { cs.push(&arr.Type) } +func (arr *Array) String() string { + return fmt.Sprintf("array#%d[type=#%d n=%d]", arr.TypeID, arr.Type.ID(), arr.Nelems) +} + +func (arr *Array) walk(tdq *typeDeque) { tdq.push(&arr.Type) } func (arr *Array) copy() Type { cpy := *arr return &cpy @@ -120,11 +160,15 @@ type Struct struct { Members []Member } +func (s *Struct) String() string { + return fmt.Sprintf("struct#%d[%q]", s.TypeID, s.Name) +} + func (s *Struct) size() uint32 { return s.Size } -func (s *Struct) walk(cs *copyStack) { +func (s *Struct) walk(tdq *typeDeque) { for i := range s.Members { - cs.push(&s.Members[i].Type) + tdq.push(&s.Members[i].Type) } } @@ -148,11 +192,15 @@ type Union struct { Members []Member } +func (u *Union) String() string { + return fmt.Sprintf("union#%d[%q]", u.TypeID, u.Name) +} + func (u *Union) size() uint32 { return u.Size } -func (u *Union) walk(cs *copyStack) { +func (u *Union) walk(tdq *typeDeque) { for i := range u.Members { - cs.push(&u.Members[i].Type) + tdq.push(&u.Members[i].Type) } } @@ -194,6 +242,10 @@ type Enum struct { Values []EnumValue } +func (e *Enum) String() string { + return fmt.Sprintf("enum#%d[%q]", e.TypeID, e.Name) +} + // EnumValue is part of an Enum // // Is is not a valid Type @@ -203,7 +255,7 @@ type EnumValue struct { } func (e *Enum) size() uint32 { return 4 } -func (e *Enum) walk(*copyStack) {} +func (e *Enum) walk(*typeDeque) {} func (e *Enum) copy() Type { cpy := *e cpy.Values = make([]EnumValue, len(e.Values)) @@ -211,13 +263,38 @@ func (e *Enum) copy() Type { return &cpy } +// FwdKind is the type of forward declaration. +type FwdKind int + +// Valid types of forward declaration. +const ( + FwdStruct FwdKind = iota + FwdUnion +) + +func (fk FwdKind) String() string { + switch fk { + case FwdStruct: + return "struct" + case FwdUnion: + return "union" + default: + return fmt.Sprintf("%T(%d)", fk, int(fk)) + } +} + // Fwd is a forward declaration of a Type. type Fwd struct { TypeID Name + Kind FwdKind } -func (f *Fwd) walk(*copyStack) {} +func (f *Fwd) String() string { + return fmt.Sprintf("fwd#%d[%s %q]", f.TypeID, f.Kind, f.Name) +} + +func (f *Fwd) walk(*typeDeque) {} func (f *Fwd) copy() Type { cpy := *f return &cpy @@ -230,7 +307,11 @@ type Typedef struct { Type Type } -func (td *Typedef) walk(cs *copyStack) { cs.push(&td.Type) } +func (td *Typedef) String() string { + return fmt.Sprintf("typedef#%d[%q #%d]", td.TypeID, td.Name, td.Type.ID()) +} + +func (td *Typedef) walk(tdq *typeDeque) { tdq.push(&td.Type) } func (td *Typedef) copy() Type { cpy := *td return &cpy @@ -242,8 +323,12 @@ type Volatile struct { Type Type } -func (v *Volatile) qualify() Type { return v.Type } -func (v *Volatile) walk(cs *copyStack) { cs.push(&v.Type) } +func (v *Volatile) String() string { + return fmt.Sprintf("volatile#%d[#%d]", v.TypeID, v.Type.ID()) +} + +func (v *Volatile) qualify() Type { return v.Type } +func (v *Volatile) walk(tdq *typeDeque) { tdq.push(&v.Type) } func (v *Volatile) copy() Type { cpy := *v return &cpy @@ -255,8 +340,12 @@ type Const struct { Type Type } -func (c *Const) qualify() Type { return c.Type } -func (c *Const) walk(cs *copyStack) { cs.push(&c.Type) } +func (c *Const) String() string { + return fmt.Sprintf("const#%d[#%d]", c.TypeID, c.Type.ID()) +} + +func (c *Const) qualify() Type { return c.Type } +func (c *Const) walk(tdq *typeDeque) { tdq.push(&c.Type) } func (c *Const) copy() Type { cpy := *c return &cpy @@ -268,8 +357,12 @@ type Restrict struct { Type Type } -func (r *Restrict) qualify() Type { return r.Type } -func (r *Restrict) walk(cs *copyStack) { cs.push(&r.Type) } +func (r *Restrict) String() string { + return fmt.Sprintf("restrict#%d[#%d]", r.TypeID, r.Type.ID()) +} + +func (r *Restrict) qualify() Type { return r.Type } +func (r *Restrict) walk(tdq *typeDeque) { tdq.push(&r.Type) } func (r *Restrict) copy() Type { cpy := *r return &cpy @@ -282,7 +375,11 @@ type Func struct { Type Type } -func (f *Func) walk(cs *copyStack) { cs.push(&f.Type) } +func (f *Func) String() string { + return fmt.Sprintf("func#%d[%q proto=#%d]", f.TypeID, f.Name, f.Type.ID()) +} + +func (f *Func) walk(tdq *typeDeque) { tdq.push(&f.Type) } func (f *Func) copy() Type { cpy := *f return &cpy @@ -295,10 +392,20 @@ type FuncProto struct { Params []FuncParam } -func (fp *FuncProto) walk(cs *copyStack) { - cs.push(&fp.Return) +func (fp *FuncProto) String() string { + var s strings.Builder + fmt.Fprintf(&s, "proto#%d[", fp.TypeID) + for _, param := range fp.Params { + fmt.Fprintf(&s, "%q=#%d, ", param.Name, param.Type.ID()) + } + fmt.Fprintf(&s, "return=#%d]", fp.Return.ID()) + return s.String() +} + +func (fp *FuncProto) walk(tdq *typeDeque) { + tdq.push(&fp.Return) for i := range fp.Params { - cs.push(&fp.Params[i].Type) + tdq.push(&fp.Params[i].Type) } } @@ -321,7 +428,12 @@ type Var struct { Type Type } -func (v *Var) walk(cs *copyStack) { cs.push(&v.Type) } +func (v *Var) String() string { + // TODO: Linkage + return fmt.Sprintf("var#%d[%q]", v.TypeID, v.Name) +} + +func (v *Var) walk(tdq *typeDeque) { tdq.push(&v.Type) } func (v *Var) copy() Type { cpy := *v return &cpy @@ -335,11 +447,15 @@ type Datasec struct { Vars []VarSecinfo } +func (ds *Datasec) String() string { + return fmt.Sprintf("section#%d[%q]", ds.TypeID, ds.Name) +} + func (ds *Datasec) size() uint32 { return ds.Size } -func (ds *Datasec) walk(cs *copyStack) { +func (ds *Datasec) walk(tdq *typeDeque) { for i := range ds.Vars { - cs.push(&ds.Vars[i].Type) + tdq.push(&ds.Vars[i].Type) } } @@ -351,6 +467,8 @@ func (ds *Datasec) copy() Type { } // VarSecinfo describes variable in a Datasec +// +// It is not a valid Type. type VarSecinfo struct { Type Type Offset uint32 @@ -438,7 +556,7 @@ func Sizeof(typ Type) (int, error) { func copyType(typ Type) Type { var ( copies = make(map[Type]Type) - work copyStack + work typeDeque ) for t := &typ; t != nil; t = work.pop() { @@ -459,34 +577,83 @@ func copyType(typ Type) Type { return typ } -// copyStack keeps track of pointers to types which still +// typeDeque keeps track of pointers to types which still // need to be visited. -type copyStack []*Type - -// push adds a type to the stack. -func (cs *copyStack) push(t *Type) { - *cs = append(*cs, t) +type typeDeque struct { + types []*Type + read, write uint64 + mask uint64 } -// pop returns the topmost Type, or nil. -func (cs *copyStack) pop() *Type { - n := len(*cs) - if n == 0 { +// push adds a type to the stack. +func (dq *typeDeque) push(t *Type) { + if dq.write-dq.read < uint64(len(dq.types)) { + dq.types[dq.write&dq.mask] = t + dq.write++ + return + } + + new := len(dq.types) * 2 + if new == 0 { + new = 8 + } + + types := make([]*Type, new) + pivot := dq.read & dq.mask + n := copy(types, dq.types[pivot:]) + n += copy(types[n:], dq.types[:pivot]) + types[n] = t + + dq.types = types + dq.mask = uint64(new) - 1 + dq.read, dq.write = 0, uint64(n+1) +} + +// shift returns the first element or null. +func (dq *typeDeque) shift() *Type { + if dq.read == dq.write { return nil } - t := (*cs)[n-1] - *cs = (*cs)[:n-1] + index := dq.read & dq.mask + t := dq.types[index] + dq.types[index] = nil + dq.read++ return t } +// pop returns the last element or null. +func (dq *typeDeque) pop() *Type { + if dq.read == dq.write { + return nil + } + + dq.write-- + index := dq.write & dq.mask + t := dq.types[index] + dq.types[index] = nil + return t +} + +// all returns all elements. +// +// The deque is empty after calling this method. +func (dq *typeDeque) all() []*Type { + length := dq.write - dq.read + types := make([]*Type, 0, length) + for t := dq.shift(); t != nil; t = dq.shift() { + types = append(types, t) + } + return types +} + // inflateRawTypes takes a list of raw btf types linked via type IDs, and turns // it into a graph of Types connected via pointers. // -// Returns a map of named types (so, where NameOff is non-zero). Since BTF ignores -// compilation units, multiple types may share the same name. A Type may form a -// cyclic graph by pointing at itself. -func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map[string][]namedType, err error) { +// Returns a map of named types (so, where NameOff is non-zero) and a slice of types +// indexed by TypeID. Since BTF ignores compilation units, multiple types may share +// the same name. A Type may form a cyclic graph by pointing at itself. +func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (types []Type, namedTypes map[string][]namedType, err error) { type fixupDef struct { id TypeID expectedKind btfKind @@ -523,7 +690,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map return members, nil } - types := make([]Type, 0, len(rawTypes)) + types = make([]Type, 0, len(rawTypes)) types = append(types, (*Void)(nil)) namedTypes = make(map[string][]namedType) @@ -537,7 +704,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map name, err := rawStrings.LookupName(raw.NameOff) if err != nil { - return nil, fmt.Errorf("can't get name for type id %d: %w", id, err) + return nil, nil, fmt.Errorf("get name for type id %d: %w", id, err) } switch raw.Kind() { @@ -562,14 +729,14 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map case kindStruct: members, err := convertMembers(raw.data.([]btfMember), raw.KindFlag()) if err != nil { - return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err) + return nil, nil, fmt.Errorf("struct %s (id %d): %w", name, id, err) } typ = &Struct{id, name, raw.Size(), members} case kindUnion: members, err := convertMembers(raw.data.([]btfMember), raw.KindFlag()) if err != nil { - return nil, fmt.Errorf("union %s (id %d): %w", name, id, err) + return nil, nil, fmt.Errorf("union %s (id %d): %w", name, id, err) } typ = &Union{id, name, raw.Size(), members} @@ -579,7 +746,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map for i, btfVal := range rawvals { name, err := rawStrings.LookupName(btfVal.NameOff) if err != nil { - return nil, fmt.Errorf("can't get name for enum value %d: %s", i, err) + return nil, nil, fmt.Errorf("get name for enum value %d: %s", i, err) } vals = append(vals, EnumValue{ Name: name, @@ -589,7 +756,11 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map typ = &Enum{id, name, vals} case kindForward: - typ = &Fwd{id, name} + if raw.KindFlag() { + typ = &Fwd{id, name, FwdUnion} + } else { + typ = &Fwd{id, name, FwdStruct} + } case kindTypedef: typedef := &Typedef{id, name, nil} @@ -622,7 +793,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map for i, param := range rawparams { name, err := rawStrings.LookupName(param.NameOff) if err != nil { - return nil, fmt.Errorf("can't get name for func proto parameter %d: %s", i, err) + return nil, nil, fmt.Errorf("get name for func proto parameter %d: %s", i, err) } params = append(params, FuncParam{ Name: name, @@ -656,7 +827,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map typ = &Datasec{id, name, raw.SizeType, vars} default: - return nil, fmt.Errorf("type id %d: unknown kind: %v", id, raw.Kind()) + return nil, nil, fmt.Errorf("type id %d: unknown kind: %v", id, raw.Kind()) } types = append(types, typ) @@ -671,7 +842,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map for _, fixup := range fixups { i := int(fixup.id) if i >= len(types) { - return nil, fmt.Errorf("reference to invalid type id: %d", fixup.id) + return nil, nil, fmt.Errorf("reference to invalid type id: %d", fixup.id) } // Default void (id 0) to unknown @@ -681,13 +852,13 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map } if expected := fixup.expectedKind; expected != kindUnknown && rawKind != expected { - return nil, fmt.Errorf("expected type id %d to have kind %s, found %s", fixup.id, expected, rawKind) + return nil, nil, fmt.Errorf("expected type id %d to have kind %s, found %s", fixup.id, expected, rawKind) } *fixup.typ = types[i] } - return namedTypes, nil + return types, namedTypes, nil } // essentialName returns name without a ___ suffix. diff --git a/vendor/github.com/cilium/ebpf/internal/elf.go b/vendor/github.com/cilium/ebpf/internal/elf.go new file mode 100644 index 000000000..c3f9ea0f8 --- /dev/null +++ b/vendor/github.com/cilium/ebpf/internal/elf.go @@ -0,0 +1,52 @@ +package internal + +import ( + "debug/elf" + "fmt" + "io" +) + +type SafeELFFile struct { + *elf.File +} + +// NewSafeELFFile reads an ELF safely. +// +// Any panic during parsing is turned into an error. This is necessary since +// there are a bunch of unfixed bugs in debug/elf. +// +// https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+debug%2Felf+in%3Atitle +func NewSafeELFFile(r io.ReaderAt) (safe *SafeELFFile, err error) { + defer func() { + r := recover() + if r == nil { + return + } + + safe = nil + err = fmt.Errorf("reading ELF file panicked: %s", r) + }() + + file, err := elf.NewFile(r) + if err != nil { + return nil, err + } + + return &SafeELFFile{file}, nil +} + +// Symbols is the safe version of elf.File.Symbols. +func (se *SafeELFFile) Symbols() (syms []elf.Symbol, err error) { + defer func() { + r := recover() + if r == nil { + return + } + + syms = nil + err = fmt.Errorf("reading ELF symbols panicked: %s", r) + }() + + syms, err = se.File.Symbols() + return +} diff --git a/vendor/github.com/cilium/ebpf/internal/feature.go b/vendor/github.com/cilium/ebpf/internal/feature.go index 8f282db64..ec62ed39b 100644 --- a/vendor/github.com/cilium/ebpf/internal/feature.go +++ b/vendor/github.com/cilium/ebpf/internal/feature.go @@ -32,7 +32,7 @@ func (ufe *UnsupportedFeatureError) Is(target error) bool { } type featureTest struct { - sync.Mutex + sync.RWMutex successful bool result error } @@ -42,10 +42,10 @@ type featureTest struct { // // The return values have the following semantics: // +// err == ErrNotSupported: the feature is not available +// err == nil: the feature is available // err != nil: the test couldn't be executed -// err == nil && available: the feature is available -// err == nil && !available: the feature isn't available -type FeatureTestFn func() (available bool, err error) +type FeatureTestFn func() error // FeatureTest wraps a function so that it is run at most once. // @@ -61,32 +61,40 @@ func FeatureTest(name, version string, fn FeatureTestFn) func() error { ft := new(featureTest) return func() error { + ft.RLock() + if ft.successful { + defer ft.RUnlock() + return ft.result + } + ft.RUnlock() ft.Lock() defer ft.Unlock() - + // check one more time on the off + // chance that two go routines + // were able to call into the write + // lock if ft.successful { return ft.result } - - available, err := fn() - if errors.Is(err, ErrNotSupported) { - // The feature test aborted because a dependent feature - // is missing, which we should cache. - available = false - } else if err != nil { - // We couldn't execute the feature test to a point - // where it could make a determination. - // Don't cache the result, just return it. - return fmt.Errorf("can't detect support for %s: %w", name, err) - } - - ft.successful = true - if !available { + err := fn() + switch { + case errors.Is(err, ErrNotSupported): ft.result = &UnsupportedFeatureError{ MinimumVersion: v, Name: name, } + fallthrough + + case err == nil: + ft.successful = true + + default: + // We couldn't execute the feature test to a point + // where it could make a determination. + // Don't cache the result, just return it. + return fmt.Errorf("detect support for %s: %w", name, err) } + return ft.result } } diff --git a/vendor/github.com/cilium/ebpf/internal/syscall.go b/vendor/github.com/cilium/ebpf/internal/syscall.go index 9c5e0b3ed..c80815131 100644 --- a/vendor/github.com/cilium/ebpf/internal/syscall.go +++ b/vendor/github.com/cilium/ebpf/internal/syscall.go @@ -91,6 +91,19 @@ func BPFProgDetach(attr *BPFProgDetachAttr) error { return err } +type BPFEnableStatsAttr struct { + StatsType uint32 +} + +func BPFEnableStats(attr *BPFEnableStatsAttr) (*FD, error) { + ptr, err := BPF(BPF_ENABLE_STATS, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) + if err != nil { + return nil, fmt.Errorf("enable stats: %w", err) + } + return NewFD(uint32(ptr)), nil + +} + type bpfObjAttr struct { fileName Pointer fd uint32 diff --git a/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go b/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go index d8135c183..86d2a10f9 100644 --- a/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go +++ b/vendor/github.com/cilium/ebpf/internal/unix/types_linux.go @@ -10,16 +10,21 @@ import ( ) const ( - ENOENT = linux.ENOENT - EEXIST = linux.EEXIST - EAGAIN = linux.EAGAIN - ENOSPC = linux.ENOSPC - EINVAL = linux.EINVAL - EPOLLIN = linux.EPOLLIN - EINTR = linux.EINTR - EPERM = linux.EPERM - ESRCH = linux.ESRCH - ENODEV = linux.ENODEV + ENOENT = linux.ENOENT + EEXIST = linux.EEXIST + EAGAIN = linux.EAGAIN + ENOSPC = linux.ENOSPC + EINVAL = linux.EINVAL + EPOLLIN = linux.EPOLLIN + EINTR = linux.EINTR + EPERM = linux.EPERM + ESRCH = linux.ESRCH + ENODEV = linux.ENODEV + // ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP + ENOTSUPP = syscall.Errno(0x20c) + + EBADF = linux.EBADF + BPF_F_NO_PREALLOC = linux.BPF_F_NO_PREALLOC BPF_F_NUMA_NODE = linux.BPF_F_NUMA_NODE BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG @@ -41,6 +46,7 @@ const ( PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC RLIM_INFINITY = linux.RLIM_INFINITY RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK + BPF_STATS_RUN_TIME = linux.BPF_STATS_RUN_TIME ) // Statfs_t is a wrapper diff --git a/vendor/github.com/cilium/ebpf/internal/unix/types_other.go b/vendor/github.com/cilium/ebpf/internal/unix/types_other.go index 416b8d2f5..8c291796a 100644 --- a/vendor/github.com/cilium/ebpf/internal/unix/types_other.go +++ b/vendor/github.com/cilium/ebpf/internal/unix/types_other.go @@ -11,15 +11,20 @@ import ( var errNonLinux = fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH) const ( - ENOENT = syscall.ENOENT - EEXIST = syscall.EEXIST - EAGAIN = syscall.EAGAIN - ENOSPC = syscall.ENOSPC - EINVAL = syscall.EINVAL - EINTR = syscall.EINTR - EPERM = syscall.EPERM - ESRCH = syscall.ESRCH - ENODEV = syscall.ENODEV + ENOENT = syscall.ENOENT + EEXIST = syscall.EEXIST + EAGAIN = syscall.EAGAIN + ENOSPC = syscall.ENOSPC + EINVAL = syscall.EINVAL + EINTR = syscall.EINTR + EPERM = syscall.EPERM + ESRCH = syscall.ESRCH + ENODEV = syscall.ENODEV + EBADF = syscall.Errno(0) + // ENOTSUPP is not the same as ENOTSUP or EOPNOTSUP + ENOTSUPP = syscall.Errno(0x20c) + + BPF_F_NO_PREALLOC = 0 BPF_F_NUMA_NODE = 0 BPF_F_RDONLY_PROG = 0 BPF_F_WRONLY_PROG = 0 @@ -42,6 +47,7 @@ const ( PERF_FLAG_FD_CLOEXEC = 0x8 RLIM_INFINITY = 0x7fffffffffffffff RLIMIT_MEMLOCK = 8 + BPF_STATS_RUN_TIME = 0 ) // Statfs_t is a wrapper diff --git a/vendor/github.com/cilium/ebpf/linker.go b/vendor/github.com/cilium/ebpf/linker.go index fc4ad9dff..f843bb25e 100644 --- a/vendor/github.com/cilium/ebpf/linker.go +++ b/vendor/github.com/cilium/ebpf/linker.go @@ -113,7 +113,7 @@ func fixupJumpsAndCalls(insns asm.Instructions) error { // Rewrite bpf to bpf call callOffset, ok := symbolOffsets[ins.Reference] if !ok { - return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference) + return fmt.Errorf("instruction %d: reference to missing symbol %q", i, ins.Reference) } ins.Constant = int64(callOffset - offset - 1) @@ -122,7 +122,7 @@ func fixupJumpsAndCalls(insns asm.Instructions) error { // Rewrite jump to label jumpOffset, ok := symbolOffsets[ins.Reference] if !ok { - return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference) + return fmt.Errorf("instruction %d: reference to missing symbol %q", i, ins.Reference) } ins.Offset = int16(jumpOffset - offset - 1) diff --git a/vendor/github.com/cilium/ebpf/map.go b/vendor/github.com/cilium/ebpf/map.go index 5c9028d9d..316fc37b1 100644 --- a/vendor/github.com/cilium/ebpf/map.go +++ b/vendor/github.com/cilium/ebpf/map.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "path/filepath" + "reflect" "strings" "github.com/cilium/ebpf/internal" @@ -90,20 +91,20 @@ type MapKV struct { func (ms *MapSpec) checkCompatibility(m *Map) error { switch { - case m.abi.Type != ms.Type: - return fmt.Errorf("expected type %v, got %v", ms.Type, m.abi.Type) + case m.typ != ms.Type: + return fmt.Errorf("expected type %v, got %v", ms.Type, m.typ) - case m.abi.KeySize != ms.KeySize: - return fmt.Errorf("expected key size %v, got %v", ms.KeySize, m.abi.KeySize) + case m.keySize != ms.KeySize: + return fmt.Errorf("expected key size %v, got %v", ms.KeySize, m.keySize) - case m.abi.ValueSize != ms.ValueSize: - return fmt.Errorf("expected value size %v, got %v", ms.ValueSize, m.abi.ValueSize) + case m.valueSize != ms.ValueSize: + return fmt.Errorf("expected value size %v, got %v", ms.ValueSize, m.valueSize) - case m.abi.MaxEntries != ms.MaxEntries: - return fmt.Errorf("expected max entries %v, got %v", ms.MaxEntries, m.abi.MaxEntries) + case m.maxEntries != ms.MaxEntries: + return fmt.Errorf("expected max entries %v, got %v", ms.MaxEntries, m.maxEntries) - case m.abi.Flags != ms.Flags: - return fmt.Errorf("expected flags %v, got %v", ms.Flags, m.abi.Flags) + case m.flags != ms.Flags: + return fmt.Errorf("expected flags %v, got %v", ms.Flags, m.flags) } return nil } @@ -118,9 +119,14 @@ func (ms *MapSpec) checkCompatibility(m *Map) error { // Implement encoding.BinaryMarshaler or encoding.BinaryUnmarshaler // if you require custom encoding. type Map struct { - name string - fd *internal.FD - abi MapABI + name string + fd *internal.FD + typ MapType + keySize uint32 + valueSize uint32 + maxEntries uint32 + flags uint32 + pinnedPath string // Per CPU maps return values larger than the size in the spec fullValueSize int } @@ -132,14 +138,18 @@ func NewMapFromFD(fd int) (*Map, error) { if fd < 0 { return nil, errors.New("invalid fd") } - bpfFd := internal.NewFD(uint32(fd)) - name, abi, err := newMapABIFromFd(bpfFd) + return newMapFromFD(internal.NewFD(uint32(fd))) +} + +func newMapFromFD(fd *internal.FD) (*Map, error) { + info, err := newMapInfoFromFd(fd) if err != nil { - bpfFd.Forget() - return nil, err + fd.Close() + return nil, fmt.Errorf("get map info: %s", err) } - return newMap(bpfFd, name, abi) + + return newMap(fd, info.Name, info.Type, info.KeySize, info.ValueSize, info.MaxEntries, info.Flags) } // NewMap creates a new Map. @@ -158,19 +168,13 @@ func NewMap(spec *MapSpec) (*Map, error) { // sufficiently high for locking memory during map creation. This can be done // by calling unix.Setrlimit with unix.RLIMIT_MEMLOCK prior to calling NewMapWithOptions. func NewMapWithOptions(spec *MapSpec, opts MapOptions) (*Map, error) { - if spec.BTF == nil { - return newMapWithBTF(spec, nil, opts) - } + btfs := make(btfHandleCache) + defer btfs.close() - handle, err := btf.NewHandle(btf.MapSpec(spec.BTF)) - if err != nil && !errors.Is(err, btf.ErrNotSupported) { - return nil, fmt.Errorf("can't load BTF: %w", err) - } - - return newMapWithBTF(spec, handle, opts) + return newMapWithOptions(spec, opts, btfs) } -func newMapWithBTF(spec *MapSpec, handle *btf.Handle, opts MapOptions) (*Map, error) { +func newMapWithOptions(spec *MapSpec, opts MapOptions, btfs btfHandleCache) (*Map, error) { switch spec.Pinning { case PinByName: if spec.Name == "" || opts.PinPath == "" { @@ -205,7 +209,11 @@ func newMapWithBTF(spec *MapSpec, handle *btf.Handle, opts MapOptions) (*Map, er return nil, fmt.Errorf("%s requires InnerMap", spec.Type) } - template, err := createMap(spec.InnerMap, nil, handle, opts) + if spec.InnerMap.Pinning != PinNone { + return nil, errors.New("inner maps cannot be pinned") + } + + template, err := createMap(spec.InnerMap, nil, opts, btfs) if err != nil { return nil, err } @@ -214,7 +222,7 @@ func newMapWithBTF(spec *MapSpec, handle *btf.Handle, opts MapOptions) (*Map, er innerFd = template.fd } - m, err := createMap(spec, innerFd, handle, opts) + m, err := createMap(spec, innerFd, opts, btfs) if err != nil { return nil, err } @@ -229,14 +237,14 @@ func newMapWithBTF(spec *MapSpec, handle *btf.Handle, opts MapOptions) (*Map, er return m, nil } -func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle, opts MapOptions) (_ *Map, err error) { +func createMap(spec *MapSpec, inner *internal.FD, opts MapOptions, btfs btfHandleCache) (_ *Map, err error) { closeOnError := func(closer io.Closer) { if err != nil { closer.Close() } } - abi := newMapABIFromSpec(spec) + spec = spec.Copy() switch spec.Type { case ArrayOfMaps: @@ -246,43 +254,43 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle, opts MapOp return nil, err } - if abi.ValueSize != 0 && abi.ValueSize != 4 { + if spec.ValueSize != 0 && spec.ValueSize != 4 { return nil, errors.New("ValueSize must be zero or four for map of map") } - abi.ValueSize = 4 + spec.ValueSize = 4 case PerfEventArray: - if abi.KeySize != 0 && abi.KeySize != 4 { + if spec.KeySize != 0 && spec.KeySize != 4 { return nil, errors.New("KeySize must be zero or four for perf event array") } - abi.KeySize = 4 + spec.KeySize = 4 - if abi.ValueSize != 0 && abi.ValueSize != 4 { + if spec.ValueSize != 0 && spec.ValueSize != 4 { return nil, errors.New("ValueSize must be zero or four for perf event array") } - abi.ValueSize = 4 + spec.ValueSize = 4 - if abi.MaxEntries == 0 { + if spec.MaxEntries == 0 { n, err := internal.PossibleCPUs() if err != nil { return nil, fmt.Errorf("perf event array: %w", err) } - abi.MaxEntries = uint32(n) + spec.MaxEntries = uint32(n) } } - if abi.Flags&(unix.BPF_F_RDONLY_PROG|unix.BPF_F_WRONLY_PROG) > 0 || spec.Freeze { + if spec.Flags&(unix.BPF_F_RDONLY_PROG|unix.BPF_F_WRONLY_PROG) > 0 || spec.Freeze { if err := haveMapMutabilityModifiers(); err != nil { return nil, fmt.Errorf("map create: %w", err) } } attr := bpfMapCreateAttr{ - mapType: abi.Type, - keySize: abi.KeySize, - valueSize: abi.ValueSize, - maxEntries: abi.MaxEntries, - flags: abi.Flags, + mapType: spec.Type, + keySize: spec.KeySize, + valueSize: spec.ValueSize, + maxEntries: spec.MaxEntries, + flags: spec.Flags, numaNode: spec.NumaNode, } @@ -294,25 +302,40 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle, opts MapOp } } - if handle != nil && spec.BTF != nil { - attr.btfFd = uint32(handle.FD()) - attr.btfKeyTypeID = btf.MapKey(spec.BTF).ID() - attr.btfValueTypeID = btf.MapValue(spec.BTF).ID() - } - if haveObjName() == nil { attr.mapName = newBPFObjName(spec.Name) } + var btfDisabled bool + if spec.BTF != nil { + handle, err := btfs.load(btf.MapSpec(spec.BTF)) + btfDisabled = errors.Is(err, btf.ErrNotSupported) + if err != nil && !btfDisabled { + return nil, fmt.Errorf("load BTF: %w", err) + } + + if handle != nil { + attr.btfFd = uint32(handle.FD()) + attr.btfKeyTypeID = btf.MapKey(spec.BTF).ID() + attr.btfValueTypeID = btf.MapValue(spec.BTF).ID() + } + } + fd, err := bpfMapCreate(&attr) if err != nil { + if errors.Is(err, unix.EPERM) { + return nil, fmt.Errorf("map create: RLIMIT_MEMLOCK may be too low: %w", err) + } + if btfDisabled { + return nil, fmt.Errorf("map create without BTF: %w", err) + } return nil, fmt.Errorf("map create: %w", err) } defer closeOnError(fd) - m, err := newMap(fd, spec.Name, abi) + m, err := newMap(fd, spec.Name, spec.Type, spec.KeySize, spec.ValueSize, spec.MaxEntries, spec.Flags) if err != nil { - return nil, err + return nil, fmt.Errorf("map create: %w", err) } if err := m.populate(spec.Contents); err != nil { @@ -328,15 +351,20 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle, opts MapOp return m, nil } -func newMap(fd *internal.FD, name string, abi *MapABI) (*Map, error) { +func newMap(fd *internal.FD, name string, typ MapType, keySize, valueSize, maxEntries, flags uint32) (*Map, error) { m := &Map{ name, fd, - *abi, - int(abi.ValueSize), + typ, + keySize, + valueSize, + maxEntries, + flags, + "", + int(valueSize), } - if !abi.Type.hasPerCPUValue() { + if !typ.hasPerCPUValue() { return m, nil } @@ -345,47 +373,45 @@ func newMap(fd *internal.FD, name string, abi *MapABI) (*Map, error) { return nil, err } - m.fullValueSize = align(int(abi.ValueSize), 8) * possibleCPUs + m.fullValueSize = align(int(valueSize), 8) * possibleCPUs return m, nil } func (m *Map) String() string { if m.name != "" { - return fmt.Sprintf("%s(%s)#%v", m.abi.Type, m.name, m.fd) + return fmt.Sprintf("%s(%s)#%v", m.typ, m.name, m.fd) } - return fmt.Sprintf("%s#%v", m.abi.Type, m.fd) + return fmt.Sprintf("%s#%v", m.typ, m.fd) } // Type returns the underlying type of the map. func (m *Map) Type() MapType { - return m.abi.Type + return m.typ } // KeySize returns the size of the map key in bytes. func (m *Map) KeySize() uint32 { - return m.abi.KeySize + return m.keySize } // ValueSize returns the size of the map value in bytes. func (m *Map) ValueSize() uint32 { - return m.abi.ValueSize + return m.valueSize } // MaxEntries returns the maximum number of elements the map can hold. func (m *Map) MaxEntries() uint32 { - return m.abi.MaxEntries + return m.maxEntries } // Flags returns the flags of the map. func (m *Map) Flags() uint32 { - return m.abi.Flags + return m.flags } -// ABI gets the ABI of the Map. -// -// Deprecated: use Type, KeySize, ValueSize, MaxEntries and Flags instead. -func (m *Map) ABI() MapABI { - return m.abi +// Info returns metadata about the map. +func (m *Map) Info() (*MapInfo, error) { + return newMapInfoFromFd(m.fd) } // Lookup retrieves a value from a Map. @@ -393,54 +419,14 @@ func (m *Map) ABI() MapABI { // Calls Close() on valueOut if it is of type **Map or **Program, // and *valueOut is not nil. // -// Returns an error if the key doesn't exist, see IsNotExist. +// Returns an error if the key doesn't exist, see ErrKeyNotExist. func (m *Map) Lookup(key, valueOut interface{}) error { valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize) - if err := m.lookup(key, valuePtr); err != nil { return err } - if valueBytes == nil { - return nil - } - - if m.abi.Type.hasPerCPUValue() { - return unmarshalPerCPUValue(valueOut, int(m.abi.ValueSize), valueBytes) - } - - switch value := valueOut.(type) { - case **Map: - m, err := unmarshalMap(valueBytes) - if err != nil { - return err - } - - (*value).Close() - *value = m - return nil - case *Map: - return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil)) - case Map: - return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil)) - - case **Program: - p, err := unmarshalProgram(valueBytes) - if err != nil { - return err - } - - (*value).Close() - *value = p - return nil - case *Program: - return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil)) - case Program: - return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil)) - - default: - return unmarshalBytes(valueOut, valueBytes) - } + return m.unmarshalValue(valueOut, valueBytes) } // LookupAndDelete retrieves and deletes a value from a Map. @@ -449,7 +435,7 @@ func (m *Map) Lookup(key, valueOut interface{}) error { func (m *Map) LookupAndDelete(key, valueOut interface{}) error { valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize) - keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) + keyPtr, err := m.marshalKey(key) if err != nil { return fmt.Errorf("can't marshal key: %w", err) } @@ -458,7 +444,7 @@ func (m *Map) LookupAndDelete(key, valueOut interface{}) error { return fmt.Errorf("lookup and delete failed: %w", err) } - return unmarshalBytes(valueOut, valueBytes) + return m.unmarshalValue(valueOut, valueBytes) } // LookupBytes gets a value from Map. @@ -477,7 +463,7 @@ func (m *Map) LookupBytes(key interface{}) ([]byte, error) { } func (m *Map) lookup(key interface{}, valueOut internal.Pointer) error { - keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) + keyPtr, err := m.marshalKey(key) if err != nil { return fmt.Errorf("can't marshal key: %w", err) } @@ -511,17 +497,12 @@ func (m *Map) Put(key, value interface{}) error { // Update changes the value of a key. func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error { - keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) + keyPtr, err := m.marshalKey(key) if err != nil { return fmt.Errorf("can't marshal key: %w", err) } - var valuePtr internal.Pointer - if m.abi.Type.hasPerCPUValue() { - valuePtr, err = marshalPerCPUValue(value, int(m.abi.ValueSize)) - } else { - valuePtr, err = marshalPtr(value, int(m.abi.ValueSize)) - } + valuePtr, err := m.marshalValue(value) if err != nil { return fmt.Errorf("can't marshal value: %w", err) } @@ -537,7 +518,7 @@ func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error { // // Returns ErrKeyNotExist if the key does not exist. func (m *Map) Delete(key interface{}) error { - keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) + keyPtr, err := m.marshalKey(key) if err != nil { return fmt.Errorf("can't marshal key: %w", err) } @@ -554,17 +535,13 @@ func (m *Map) Delete(key interface{}) error { // // Returns ErrKeyNotExist if there is no next key. func (m *Map) NextKey(key, nextKeyOut interface{}) error { - nextKeyPtr, nextKeyBytes := makeBuffer(nextKeyOut, int(m.abi.KeySize)) + nextKeyPtr, nextKeyBytes := makeBuffer(nextKeyOut, int(m.keySize)) if err := m.nextKey(key, nextKeyPtr); err != nil { return err } - if nextKeyBytes == nil { - return nil - } - - if err := unmarshalBytes(nextKeyOut, nextKeyBytes); err != nil { + if err := m.unmarshalKey(nextKeyOut, nextKeyBytes); err != nil { return fmt.Errorf("can't unmarshal next key: %w", err) } return nil @@ -578,7 +555,7 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error { // // Returns nil if there are no more keys. func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) { - nextKey := make([]byte, m.abi.KeySize) + nextKey := make([]byte, m.keySize) nextKeyPtr := internal.NewSlicePointer(nextKey) err := m.nextKey(key, nextKeyPtr) @@ -596,7 +573,7 @@ func (m *Map) nextKey(key interface{}, nextKeyOut internal.Pointer) error { ) if key != nil { - keyPtr, err = marshalPtr(key, int(m.abi.KeySize)) + keyPtr, err = m.marshalKey(key) if err != nil { return fmt.Errorf("can't marshal key: %w", err) } @@ -608,6 +585,158 @@ func (m *Map) nextKey(key interface{}, nextKeyOut internal.Pointer) error { return nil } +// BatchLookup looks up many elements in a map at once. +// +// "keysOut" and "valuesOut" must be of type slice, a pointer +// to a slice or buffer will not work. +// "prevKey" is the key to start the batch lookup from, it will +// *not* be included in the results. Use nil to start at the first key. +// +// ErrKeyNotExist is returned when the batch lookup has reached +// the end of all possible results, even when partial results +// are returned. It should be used to evaluate when lookup is "done". +func (m *Map) BatchLookup(prevKey, nextKeyOut, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { + return m.batchLookup(internal.BPF_MAP_LOOKUP_BATCH, prevKey, nextKeyOut, keysOut, valuesOut, opts) +} + +// BatchLookupAndDelete looks up many elements in a map at once, +// +// It then deletes all those elements. +// "keysOut" and "valuesOut" must be of type slice, a pointer +// to a slice or buffer will not work. +// "prevKey" is the key to start the batch lookup from, it will +// *not* be included in the results. Use nil to start at the first key. +// +// ErrKeyNotExist is returned when the batch lookup has reached +// the end of all possible results, even when partial results +// are returned. It should be used to evaluate when lookup is "done". +func (m *Map) BatchLookupAndDelete(prevKey, nextKeyOut, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { + return m.batchLookup(internal.BPF_MAP_LOOKUP_AND_DELETE_BATCH, prevKey, nextKeyOut, keysOut, valuesOut, opts) +} + +func (m *Map) batchLookup(cmd internal.BPFCmd, startKey, nextKeyOut, keysOut, valuesOut interface{}, opts *BatchOptions) (int, error) { + if err := haveBatchAPI(); err != nil { + return 0, err + } + if m.typ.hasPerCPUValue() { + return 0, ErrNotSupported + } + keysValue := reflect.ValueOf(keysOut) + if keysValue.Kind() != reflect.Slice { + return 0, fmt.Errorf("keys must be a slice") + } + valuesValue := reflect.ValueOf(valuesOut) + if valuesValue.Kind() != reflect.Slice { + return 0, fmt.Errorf("valuesOut must be a slice") + } + count := keysValue.Len() + if count != valuesValue.Len() { + return 0, fmt.Errorf("keysOut and valuesOut must be the same length") + } + keyBuf := make([]byte, count*int(m.keySize)) + keyPtr := internal.NewSlicePointer(keyBuf) + valueBuf := make([]byte, count*int(m.fullValueSize)) + valuePtr := internal.NewSlicePointer(valueBuf) + + var ( + startPtr internal.Pointer + err error + retErr error + ) + if startKey != nil { + startPtr, err = marshalPtr(startKey, int(m.keySize)) + if err != nil { + return 0, err + } + } + nextPtr, nextBuf := makeBuffer(nextKeyOut, int(m.keySize)) + + ct, err := bpfMapBatch(cmd, m.fd, startPtr, nextPtr, keyPtr, valuePtr, uint32(count), opts) + if err != nil { + if !errors.Is(err, ErrKeyNotExist) { + return 0, err + } + retErr = ErrKeyNotExist + } + + err = m.unmarshalKey(nextKeyOut, nextBuf) + if err != nil { + return 0, err + } + err = unmarshalBytes(keysOut, keyBuf) + if err != nil { + return 0, err + } + err = unmarshalBytes(valuesOut, valueBuf) + if err != nil { + retErr = err + } + return int(ct), retErr +} + +// BatchUpdate updates the map with multiple keys and values +// simultaneously. +// "keys" and "values" must be of type slice, a pointer +// to a slice or buffer will not work. +func (m *Map) BatchUpdate(keys, values interface{}, opts *BatchOptions) (int, error) { + if err := haveBatchAPI(); err != nil { + return 0, err + } + if m.typ.hasPerCPUValue() { + return 0, ErrNotSupported + } + keysValue := reflect.ValueOf(keys) + if keysValue.Kind() != reflect.Slice { + return 0, fmt.Errorf("keys must be a slice") + } + valuesValue := reflect.ValueOf(values) + if valuesValue.Kind() != reflect.Slice { + return 0, fmt.Errorf("values must be a slice") + } + var ( + count = keysValue.Len() + valuePtr internal.Pointer + err error + ) + if count != valuesValue.Len() { + return 0, fmt.Errorf("keys and values must be the same length") + } + keyPtr, err := marshalPtr(keys, count*int(m.keySize)) + if err != nil { + return 0, err + } + valuePtr, err = marshalPtr(values, count*int(m.valueSize)) + if err != nil { + return 0, err + } + var nilPtr internal.Pointer + ct, err := bpfMapBatch(internal.BPF_MAP_UPDATE_BATCH, m.fd, nilPtr, nilPtr, keyPtr, valuePtr, uint32(count), opts) + return int(ct), err +} + +// BatchDelete batch deletes entries in the map by keys. +// "keys" must be of type slice, a pointer to a slice or buffer will not work. +func (m *Map) BatchDelete(keys interface{}, opts *BatchOptions) (int, error) { + if err := haveBatchAPI(); err != nil { + return 0, err + } + if m.typ.hasPerCPUValue() { + return 0, ErrNotSupported + } + keysValue := reflect.ValueOf(keys) + if keysValue.Kind() != reflect.Slice { + return 0, fmt.Errorf("keys must be a slice") + } + count := keysValue.Len() + keyPtr, err := marshalPtr(keys, count*int(m.keySize)) + if err != nil { + return 0, fmt.Errorf("cannot marshal keys: %v", err) + } + var nilPtr internal.Pointer + ct, err := bpfMapBatch(internal.BPF_MAP_DELETE_BATCH, m.fd, nilPtr, nilPtr, keyPtr, nilPtr, uint32(count), opts) + return int(ct), err +} + // Iterate traverses a map. // // It's safe to create multiple iterators at the same time. @@ -647,6 +776,7 @@ func (m *Map) FD() int { // // Closing the duplicate does not affect the original, and vice versa. // Changes made to the map are reflected by both instances however. +// If the original map was pinned, the cloned map will not be pinned by default. // // Cloning a nil Map returns nil. func (m *Map) Clone() (*Map, error) { @@ -659,14 +789,53 @@ func (m *Map) Clone() (*Map, error) { return nil, fmt.Errorf("can't clone map: %w", err) } - return newMap(dup, m.name, &m.abi) + return &Map{ + m.name, + dup, + m.typ, + m.keySize, + m.valueSize, + m.maxEntries, + m.flags, + "", + m.fullValueSize, + }, nil } -// Pin persists the map past the lifetime of the process that created it. +// Pin persists the map on the BPF virtual file system past the lifetime of +// the process that created it . +// +// Calling Pin on a previously pinned map will override the path. +// You can Clone a map to pin it to a different path. // // This requires bpffs to be mounted above fileName. See https://docs.cilium.io/en/k8s-doc/admin/#admin-mount-bpffs func (m *Map) Pin(fileName string) error { - return internal.BPFObjPin(fileName, m.fd) + if err := pin(m.pinnedPath, fileName, m.fd); err != nil { + return err + } + m.pinnedPath = fileName + return nil +} + +// Unpin removes the persisted state for the map from the BPF virtual filesystem. +// +// Failed calls to Unpin will not alter the state returned by IsPinned. +// +// Unpinning an unpinned Map returns nil. +func (m *Map) Unpin() error { + if err := unpin(m.pinnedPath); err != nil { + return err + } + m.pinnedPath = "" + return nil +} + +// IsPinned returns true if the map has a non-empty pinned path. +func (m *Map) IsPinned() bool { + if m.pinnedPath == "" { + return false + } + return true } // Freeze prevents a map to be modified from user space. @@ -692,45 +861,147 @@ func (m *Map) populate(contents []MapKV) error { return nil } +func (m *Map) marshalKey(data interface{}) (internal.Pointer, error) { + if data == nil { + if m.keySize == 0 { + // Queues have a key length of zero, so passing nil here is valid. + return internal.NewPointer(nil), nil + } + return internal.Pointer{}, errors.New("can't use nil as key of map") + } + + return marshalPtr(data, int(m.keySize)) +} + +func (m *Map) unmarshalKey(data interface{}, buf []byte) error { + if buf == nil { + // This is from a makeBuffer call, nothing do do here. + return nil + } + + return unmarshalBytes(data, buf) +} + +func (m *Map) marshalValue(data interface{}) (internal.Pointer, error) { + if m.typ.hasPerCPUValue() { + return marshalPerCPUValue(data, int(m.valueSize)) + } + + var ( + buf []byte + err error + ) + + switch value := data.(type) { + case *Map: + if !m.typ.canStoreMap() { + return internal.Pointer{}, fmt.Errorf("can't store map in %s", m.typ) + } + buf, err = marshalMap(value, int(m.valueSize)) + + case *Program: + if !m.typ.canStoreProgram() { + return internal.Pointer{}, fmt.Errorf("can't store program in %s", m.typ) + } + buf, err = marshalProgram(value, int(m.valueSize)) + + default: + return marshalPtr(data, int(m.valueSize)) + } + + if err != nil { + return internal.Pointer{}, err + } + + return internal.NewSlicePointer(buf), nil +} + +func (m *Map) unmarshalValue(value interface{}, buf []byte) error { + if buf == nil { + // This is from a makeBuffer call, nothing do do here. + return nil + } + + if m.typ.hasPerCPUValue() { + return unmarshalPerCPUValue(value, int(m.valueSize), buf) + } + + switch value := value.(type) { + case **Map: + if !m.typ.canStoreMap() { + return fmt.Errorf("can't read a map from %s", m.typ) + } + + other, err := unmarshalMap(buf) + if err != nil { + return err + } + + (*value).Close() + *value = other + return nil + + case *Map: + if !m.typ.canStoreMap() { + return fmt.Errorf("can't read a map from %s", m.typ) + } + return errors.New("require pointer to *Map") + + case **Program: + if !m.typ.canStoreProgram() { + return fmt.Errorf("can't read a program from %s", m.typ) + } + + other, err := unmarshalProgram(buf) + if err != nil { + return err + } + + (*value).Close() + *value = other + return nil + + case *Program: + if !m.typ.canStoreProgram() { + return fmt.Errorf("can't read a program from %s", m.typ) + } + return errors.New("require pointer to *Program") + } + + return unmarshalBytes(value, buf) +} + // LoadPinnedMap load a Map from a BPF file. -// -// The function is not compatible with nested maps. -// Use LoadPinnedMapExplicit in these situations. func LoadPinnedMap(fileName string) (*Map, error) { fd, err := internal.BPFObjGet(fileName) if err != nil { return nil, err } - name, abi, err := newMapABIFromFd(fd) - if err != nil { - _ = fd.Close() - return nil, err + + m, err := newMapFromFD(fd) + if err == nil { + m.pinnedPath = fileName } - return newMap(fd, name, abi) -} - -// LoadPinnedMapExplicit loads a map with explicit parameters. -func LoadPinnedMapExplicit(fileName string, abi *MapABI) (*Map, error) { - fd, err := internal.BPFObjGet(fileName) - if err != nil { - return nil, err - } - return newMap(fd, "", abi) + + return m, err } +// unmarshalMap creates a map from a map ID encoded in host endianness. func unmarshalMap(buf []byte) (*Map, error) { if len(buf) != 4 { return nil, errors.New("map id requires 4 byte value") } - // Looking up an entry in a nested map or prog array returns an id, - // not an fd. id := internal.NativeEndian.Uint32(buf) return NewMapFromID(MapID(id)) } -// MarshalBinary implements BinaryMarshaler. -func (m *Map) MarshalBinary() ([]byte, error) { +// marshalMap marshals the fd of a map into a buffer in host endianness. +func marshalMap(m *Map, length int) ([]byte, error) { + if length != 4 { + return nil, fmt.Errorf("can't marshal map to %d bytes", length) + } + fd, err := m.fd.Value() if err != nil { return nil, err @@ -810,8 +1081,8 @@ type MapIterator struct { func newMapIterator(target *Map) *MapIterator { return &MapIterator{ target: target, - maxEntries: target.abi.MaxEntries, - prevBytes: make([]byte, int(target.abi.KeySize)), + maxEntries: target.maxEntries, + prevBytes: make([]byte, target.keySize), } } @@ -830,7 +1101,9 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool { return false } - for ; mi.count < mi.maxEntries; mi.count++ { + // For array-like maps NextKeyBytes returns nil only on after maxEntries + // iterations. + for mi.count <= mi.maxEntries { var nextBytes []byte nextBytes, mi.err = mi.target.NextKeyBytes(mi.prevKey) if mi.err != nil { @@ -849,6 +1122,7 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool { copy(mi.prevBytes, nextBytes) mi.prevKey = mi.prevBytes + mi.count++ mi.err = mi.target.Lookup(nextBytes, valueOut) if errors.Is(mi.err, ErrKeyNotExist) { // Even though the key should be valid, we couldn't look up @@ -865,7 +1139,7 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool { return false } - mi.err = unmarshalBytes(keyOut, nextBytes) + mi.err = mi.target.unmarshalKey(keyOut, nextBytes) return mi.err == nil } @@ -899,18 +1173,12 @@ func NewMapFromID(id MapID) (*Map, error) { return nil, err } - name, abi, err := newMapABIFromFd(fd) - if err != nil { - _ = fd.Close() - return nil, err - } - - return newMap(fd, name, abi) + return newMapFromFD(fd) } // ID returns the systemwide unique ID of the map. // -// Requires at least Linux 4.13. +// Deprecated: use MapInfo.ID() instead. func (m *Map) ID() (MapID, error) { info, err := bpfGetMapInfoByFD(m.fd) if err != nil { diff --git a/vendor/github.com/cilium/ebpf/marshalers.go b/vendor/github.com/cilium/ebpf/marshalers.go index c0db2f6b0..3ea1021a8 100644 --- a/vendor/github.com/cilium/ebpf/marshalers.go +++ b/vendor/github.com/cilium/ebpf/marshalers.go @@ -13,14 +13,12 @@ import ( "github.com/cilium/ebpf/internal" ) +// marshalPtr converts an arbitrary value into a pointer suitable +// to be passed to the kernel. +// +// As an optimization, it returns the original value if it is an +// unsafe.Pointer. func marshalPtr(data interface{}, length int) (internal.Pointer, error) { - if data == nil { - if length == 0 { - return internal.NewPointer(nil), nil - } - return internal.Pointer{}, errors.New("can't use nil as key of map") - } - if ptr, ok := data.(unsafe.Pointer); ok { return internal.NewPointer(ptr), nil } @@ -33,6 +31,13 @@ func marshalPtr(data interface{}, length int) (internal.Pointer, error) { return internal.NewSlicePointer(buf), nil } +// marshalBytes converts an arbitrary value into a byte buffer. +// +// Prefer using Map.marshalKey and Map.marshalValue if possible, since +// those have special cases that allow more types to be encoded. +// +// Returns an error if the given value isn't representable in exactly +// length bytes. func marshalBytes(data interface{}, length int) (buf []byte, err error) { switch value := data.(type) { case encoding.BinaryMarshaler: @@ -43,6 +48,8 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) { buf = value case unsafe.Pointer: err = errors.New("can't marshal from unsafe.Pointer") + case Map, *Map, Program, *Program: + err = fmt.Errorf("can't marshal %T", value) default: var wr bytes.Buffer err = binary.Write(&wr, internal.NativeEndian, value) @@ -70,6 +77,10 @@ func makeBuffer(dst interface{}, length int) (internal.Pointer, []byte) { return internal.NewSlicePointer(buf), buf } +// unmarshalBytes converts a byte buffer into an arbitrary value. +// +// Prefer using Map.unmarshalKey and Map.unmarshalValue if possible, since +// those have special cases that allow more types to be encoded. func unmarshalBytes(data interface{}, buf []byte) error { switch value := data.(type) { case unsafe.Pointer: @@ -83,6 +94,8 @@ func unmarshalBytes(data interface{}, buf []byte) error { copy(dst, buf) runtime.KeepAlive(value) return nil + case Map, *Map, Program, *Program: + return fmt.Errorf("can't unmarshal into %T", value) case encoding.BinaryUnmarshaler: return value.UnmarshalBinary(buf) case *string: diff --git a/vendor/github.com/cilium/ebpf/pinning.go b/vendor/github.com/cilium/ebpf/pinning.go new file mode 100644 index 000000000..78812364a --- /dev/null +++ b/vendor/github.com/cilium/ebpf/pinning.go @@ -0,0 +1,42 @@ +package ebpf + +import ( + "errors" + "fmt" + "os" + + "github.com/cilium/ebpf/internal" +) + +func pin(currentPath, newPath string, fd *internal.FD) error { + if newPath == "" { + return errors.New("given pinning path cannot be empty") + } + if currentPath == "" { + return internal.BPFObjPin(newPath, fd) + } + if currentPath == newPath { + return nil + } + var err error + // Object is now moved to the new pinning path. + if err = os.Rename(currentPath, newPath); err == nil { + return nil + } + if !os.IsNotExist(err) { + return fmt.Errorf("unable to move pinned object to new path %v: %w", newPath, err) + } + // Internal state not in sync with the file system so let's fix it. + return internal.BPFObjPin(newPath, fd) +} + +func unpin(pinnedPath string) error { + if pinnedPath == "" { + return nil + } + err := os.Remove(pinnedPath) + if err == nil || os.IsNotExist(err) { + return nil + } + return err +} diff --git a/vendor/github.com/cilium/ebpf/prog.go b/vendor/github.com/cilium/ebpf/prog.go index 67cfff67e..4b65f23b2 100644 --- a/vendor/github.com/cilium/ebpf/prog.go +++ b/vendor/github.com/cilium/ebpf/prog.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "math" + "path/filepath" "strings" "time" @@ -18,7 +19,7 @@ import ( // ErrNotSupported is returned whenever the kernel doesn't support a feature. var ErrNotSupported = internal.ErrNotSupported -// ProgramID represents the unique ID of an eBPF program +// ProgramID represents the unique ID of an eBPF program. type ProgramID uint32 const ( @@ -42,7 +43,7 @@ type ProgramOptions struct { LogSize int } -// ProgramSpec defines a Program +// ProgramSpec defines a Program. type ProgramSpec struct { // Name is passed to the kernel as a debug aid. Must only contain // alpha numeric and '_' characters. @@ -87,6 +88,13 @@ func (ps *ProgramSpec) Copy() *ProgramSpec { return &cpy } +// Tag calculates the kernel tag for a series of instructions. +// +// Use asm.Instructions.Tag if you need to calculate for non-native endianness. +func (ps *ProgramSpec) Tag() (string, error) { + return ps.Instructions.Tag(internal.NativeEndian) +} + // Program represents BPF program loaded into the kernel. // // It is not safe to close a Program which is used by other goroutines. @@ -97,8 +105,8 @@ type Program struct { fd *internal.FD name string - abi ProgramABI - attachType AttachType + pinnedPath string + typ ProgramType } // NewProgram creates a new Program. @@ -114,88 +122,13 @@ func NewProgram(spec *ProgramSpec) (*Program, error) { // Loading a program for the first time will perform // feature detection by loading small, temporary programs. func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, error) { - if spec.BTF == nil { - return newProgramWithBTF(spec, nil, opts) - } + btfs := make(btfHandleCache) + defer btfs.close() - handle, err := btf.NewHandle(btf.ProgramSpec(spec.BTF)) - if err != nil && !errors.Is(err, btf.ErrNotSupported) { - return nil, fmt.Errorf("can't load BTF: %w", err) - } - - return newProgramWithBTF(spec, handle, opts) + return newProgramWithOptions(spec, opts, btfs) } -func newProgramWithBTF(spec *ProgramSpec, btf *btf.Handle, opts ProgramOptions) (*Program, error) { - attr, err := convertProgramSpec(spec, btf) - if err != nil { - return nil, err - } - - logSize := DefaultVerifierLogSize - if opts.LogSize > 0 { - logSize = opts.LogSize - } - - var logBuf []byte - if opts.LogLevel > 0 { - logBuf = make([]byte, logSize) - attr.logLevel = opts.LogLevel - attr.logSize = uint32(len(logBuf)) - attr.logBuf = internal.NewSlicePointer(logBuf) - } - - fd, err := bpfProgLoad(attr) - if err == nil { - prog := newProgram(fd, spec.Name, &ProgramABI{spec.Type}) - prog.VerifierLog = internal.CString(logBuf) - return prog, nil - } - - logErr := err - if opts.LogLevel == 0 { - // Re-run with the verifier enabled to get better error messages. - logBuf = make([]byte, logSize) - attr.logLevel = 1 - attr.logSize = uint32(len(logBuf)) - attr.logBuf = internal.NewSlicePointer(logBuf) - - _, logErr = bpfProgLoad(attr) - } - - err = internal.ErrorWithLog(err, logBuf, logErr) - return nil, fmt.Errorf("can't load program: %w", err) -} - -// NewProgramFromFD creates a program from a raw fd. -// -// You should not use fd after calling this function. -// -// Requires at least Linux 4.11. -func NewProgramFromFD(fd int) (*Program, error) { - if fd < 0 { - return nil, errors.New("invalid fd") - } - bpfFd := internal.NewFD(uint32(fd)) - - name, abi, err := newProgramABIFromFd(bpfFd) - if err != nil { - bpfFd.Forget() - return nil, err - } - - return newProgram(bpfFd, name, abi), nil -} - -func newProgram(fd *internal.FD, name string, abi *ProgramABI) *Program { - return &Program{ - name: name, - fd: fd, - abi: *abi, - } -} - -func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr, error) { +func newProgramWithOptions(spec *ProgramSpec, opts ProgramOptions, btfs btfHandleCache) (*Program, error) { if len(spec.Instructions) == 0 { return nil, errors.New("Instructions cannot be empty") } @@ -236,24 +169,39 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr attr.progName = newBPFObjName(spec.Name) } - if handle != nil && spec.BTF != nil { - attr.progBTFFd = uint32(handle.FD()) - - recSize, bytes, err := btf.ProgramLineInfos(spec.BTF) - if err != nil { - return nil, fmt.Errorf("can't get BTF line infos: %w", err) + var btfDisabled bool + if spec.BTF != nil { + if relos, err := btf.ProgramRelocations(spec.BTF, nil); err != nil { + return nil, fmt.Errorf("CO-RE relocations: %s", err) + } else if len(relos) > 0 { + return nil, fmt.Errorf("applying CO-RE relocations: %w", ErrNotSupported) } - attr.lineInfoRecSize = recSize - attr.lineInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize)) - attr.lineInfo = internal.NewSlicePointer(bytes) - recSize, bytes, err = btf.ProgramFuncInfos(spec.BTF) - if err != nil { - return nil, fmt.Errorf("can't get BTF function infos: %w", err) + handle, err := btfs.load(btf.ProgramSpec(spec.BTF)) + btfDisabled = errors.Is(err, btf.ErrNotSupported) + if err != nil && !btfDisabled { + return nil, fmt.Errorf("load BTF: %w", err) + } + + if handle != nil { + attr.progBTFFd = uint32(handle.FD()) + + recSize, bytes, err := btf.ProgramLineInfos(spec.BTF) + if err != nil { + return nil, fmt.Errorf("get BTF line infos: %w", err) + } + attr.lineInfoRecSize = recSize + attr.lineInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize)) + attr.lineInfo = internal.NewSlicePointer(bytes) + + recSize, bytes, err = btf.ProgramFuncInfos(spec.BTF) + if err != nil { + return nil, fmt.Errorf("get BTF function infos: %w", err) + } + attr.funcInfoRecSize = recSize + attr.funcInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize)) + attr.funcInfo = internal.NewSlicePointer(bytes) } - attr.funcInfoRecSize = recSize - attr.funcInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize)) - attr.funcInfo = internal.NewSlicePointer(bytes) } if spec.AttachTo != "" { @@ -266,26 +214,100 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr } } - return attr, nil + logSize := DefaultVerifierLogSize + if opts.LogSize > 0 { + logSize = opts.LogSize + } + + var logBuf []byte + if opts.LogLevel > 0 { + logBuf = make([]byte, logSize) + attr.logLevel = opts.LogLevel + attr.logSize = uint32(len(logBuf)) + attr.logBuf = internal.NewSlicePointer(logBuf) + } + + fd, err := bpfProgLoad(attr) + if err == nil { + return &Program{internal.CString(logBuf), fd, spec.Name, "", spec.Type}, nil + } + + logErr := err + if opts.LogLevel == 0 { + // Re-run with the verifier enabled to get better error messages. + logBuf = make([]byte, logSize) + attr.logLevel = 1 + attr.logSize = uint32(len(logBuf)) + attr.logBuf = internal.NewSlicePointer(logBuf) + + _, logErr = bpfProgLoad(attr) + } + + if errors.Is(logErr, unix.EPERM) && logBuf[0] == 0 { + // EPERM due to RLIMIT_MEMLOCK happens before the verifier, so we can + // check that the log is empty to reduce false positives. + return nil, fmt.Errorf("load program: RLIMIT_MEMLOCK may be too low: %w", logErr) + } + + err = internal.ErrorWithLog(err, logBuf, logErr) + if btfDisabled { + return nil, fmt.Errorf("load program without BTF: %w", err) + } + return nil, fmt.Errorf("load program: %w", err) +} + +// NewProgramFromFD creates a program from a raw fd. +// +// You should not use fd after calling this function. +// +// Requires at least Linux 4.10. +func NewProgramFromFD(fd int) (*Program, error) { + if fd < 0 { + return nil, errors.New("invalid fd") + } + + return newProgramFromFD(internal.NewFD(uint32(fd))) +} + +// NewProgramFromID returns the program for a given id. +// +// Returns ErrNotExist, if there is no eBPF program with the given id. +func NewProgramFromID(id ProgramID) (*Program, error) { + fd, err := bpfObjGetFDByID(internal.BPF_PROG_GET_FD_BY_ID, uint32(id)) + if err != nil { + return nil, fmt.Errorf("get program by id: %w", err) + } + + return newProgramFromFD(fd) +} + +func newProgramFromFD(fd *internal.FD) (*Program, error) { + info, err := newProgramInfoFromFd(fd) + if err != nil { + fd.Close() + return nil, fmt.Errorf("discover program type: %w", err) + } + + return &Program{"", fd, "", "", info.Type}, nil } func (p *Program) String() string { if p.name != "" { - return fmt.Sprintf("%s(%s)#%v", p.abi.Type, p.name, p.fd) + return fmt.Sprintf("%s(%s)#%v", p.typ, p.name, p.fd) } - return fmt.Sprintf("%s#%v", p.abi.Type, p.fd) + return fmt.Sprintf("%s(%v)", p.typ, p.fd) } // Type returns the underlying type of the program. func (p *Program) Type() ProgramType { - return p.abi.Type + return p.typ } -// ABI gets the ABI of the Program. +// Info returns metadata about the program. // -// Deprecated: use Type instead. -func (p *Program) ABI() ProgramABI { - return p.abi +// Requires at least 4.10. +func (p *Program) Info() (*ProgramInfo, error) { + return newProgramInfoFromFd(p.fd) } // FD gets the file descriptor of the Program. @@ -317,19 +339,42 @@ func (p *Program) Clone() (*Program, error) { return nil, fmt.Errorf("can't clone program: %w", err) } - return newProgram(dup, p.name, &p.abi), nil + return &Program{p.VerifierLog, dup, p.name, "", p.typ}, nil } -// Pin persists the Program past the lifetime of the process that created it +// Pin persists the Program on the BPF virtual file system past the lifetime of +// the process that created it // // This requires bpffs to be mounted above fileName. See https://docs.cilium.io/en/k8s-doc/admin/#admin-mount-bpffs func (p *Program) Pin(fileName string) error { - if err := internal.BPFObjPin(fileName, p.fd); err != nil { - return fmt.Errorf("can't pin program: %w", err) + if err := pin(p.pinnedPath, fileName, p.fd); err != nil { + return err } + p.pinnedPath = fileName return nil } +// Unpin removes the persisted state for the Program from the BPF virtual filesystem. +// +// Failed calls to Unpin will not alter the state returned by IsPinned. +// +// Unpinning an unpinned Program returns nil. +func (p *Program) Unpin() error { + if err := unpin(p.pinnedPath); err != nil { + return err + } + p.pinnedPath = "" + return nil +} + +// IsPinned returns true if the Program has a non-empty pinned path. +func (p *Program) IsPinned() bool { + if p.pinnedPath == "" { + return false + } + return true +} + // Close unloads the program from the kernel. func (p *Program) Close() error { if p == nil { @@ -373,7 +418,7 @@ func (p *Program) Benchmark(in []byte, repeat int, reset func()) (uint32, time.D return ret, total, nil } -var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() (bool, error) { +var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() error { prog, err := NewProgram(&ProgramSpec{ Type: SocketFilter, Instructions: asm.Instructions{ @@ -384,7 +429,7 @@ var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() ( }) if err != nil { // This may be because we lack sufficient permissions, etc. - return false, err + return err } defer prog.Close() @@ -397,10 +442,16 @@ var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() ( } err = bpfProgTestRun(&attr) - - // Check for EINVAL specifically, rather than err != nil since we - // otherwise misdetect due to insufficient permissions. - return !errors.Is(err, unix.EINVAL), nil + if errors.Is(err, unix.EINVAL) { + // Check for EINVAL specifically, rather than err != nil since we + // otherwise misdetect due to insufficient permissions. + return internal.ErrNotSupported + } + if errors.Is(err, unix.EINTR) { + // We know that PROG_TEST_RUN is supported if we get EINTR. + return nil + } + return err }) func (p *Program) testRun(in []byte, repeat int, reset func()) (uint32, []byte, time.Duration, error) { @@ -479,8 +530,11 @@ func unmarshalProgram(buf []byte) (*Program, error) { return NewProgramFromID(ProgramID(id)) } -// MarshalBinary implements BinaryMarshaler. -func (p *Program) MarshalBinary() ([]byte, error) { +func marshalProgram(p *Program, length int) ([]byte, error) { + if length != 4 { + return nil, fmt.Errorf("can't marshal program to %d bytes", length) + } + value, err := p.fd.Value() if err != nil { return nil, err @@ -549,22 +603,22 @@ func LoadPinnedProgram(fileName string) (*Program, error) { return nil, err } - name, abi, err := newProgramABIFromFd(fd) + info, err := newProgramInfoFromFd(fd) if err != nil { _ = fd.Close() - return nil, fmt.Errorf("can't get ABI for %s: %w", fileName, err) + return nil, fmt.Errorf("info for %s: %w", fileName, err) } - return newProgram(fd, name, abi), nil + return &Program{"", fd, filepath.Base(fileName), "", info.Type}, nil } -// SanitizeName replaces all invalid characters in name. +// SanitizeName replaces all invalid characters in name with replacement. +// Passing a negative value for replacement will delete characters instead +// of replacing them. Use this to automatically generate valid names for maps +// and programs at runtime. // -// Use this to automatically generate valid names for maps and -// programs at run time. -// -// Passing a negative value for replacement will delete characters -// instead of replacing them. +// The set of allowed characters depends on the running kernel version. +// Dots are only allowed as of kernel 5.2. func SanitizeName(name string, replacement rune) string { return strings.Map(func(char rune) rune { if invalidBPFObjNameChar(char) { @@ -582,25 +636,9 @@ func ProgramGetNextID(startID ProgramID) (ProgramID, error) { return ProgramID(id), err } -// NewProgramFromID returns the program for a given id. -// -// Returns ErrNotExist, if there is no eBPF program with the given id. -func NewProgramFromID(id ProgramID) (*Program, error) { - fd, err := bpfObjGetFDByID(internal.BPF_PROG_GET_FD_BY_ID, uint32(id)) - if err != nil { - return nil, err - } - - name, abi, err := newProgramABIFromFd(fd) - if err != nil { - _ = fd.Close() - return nil, err - } - - return newProgram(fd, name, abi), nil -} - // ID returns the systemwide unique ID of the program. +// +// Deprecated: use ProgramInfo.ID() instead. func (p *Program) ID() (ProgramID, error) { info, err := bpfGetProgInfoByFD(p.fd) if err != nil { diff --git a/vendor/github.com/cilium/ebpf/readme.md b/vendor/github.com/cilium/ebpf/readme.md deleted file mode 100644 index 298db57c3..000000000 --- a/vendor/github.com/cilium/ebpf/readme.md +++ /dev/null @@ -1,29 +0,0 @@ -eBPF -------- -[![PkgGoDev](https://pkg.go.dev/badge/github.com/cilium/ebpf)](https://pkg.go.dev/github.com/cilium/ebpf) - -eBPF is a pure Go library that provides utilities for loading, compiling, and debugging eBPF programs. It has minimal external dependencies and is intended to be used in long running processes. - -* [asm](https://pkg.go.dev/github.com/cilium/ebpf/asm) contains a basic assembler. -* [link](https://pkg.go.dev/github.com/cilium/ebpf/link) allows attaching eBPF to various hooks. -* [perf](https://pkg.go.dev/github.com/cilium/ebpf/perf) allows reading from a PERF_EVENT_ARRAY. -* [cmd/bpf2go](https://pkg.go.dev/github.com/cilium/ebpf/cmd/bpf2go) allows embedding eBPF in Go. - -The library is maintained by [Cloudflare](https://www.cloudflare.com) and [Cilium](https://www.cilium.io). Feel free to [join](https://cilium.herokuapp.com/) the [libbpf-go](https://cilium.slack.com/messages/libbpf-go) channel on Slack. - -## Current status - -The package is production ready, but **the API is explicitly unstable -right now**. Expect to update your code if you want to follow along. - -## Requirements - -* A version of Go that is [supported by upstream](https://golang.org/doc/devel/release.html#policy) -* Linux 4.9, 4.19 or 5.4 (versions in-between should work, but are not tested) - -## Useful resources - -* [eBPF.io](https://ebpf.io) (recommended) -* [Cilium eBPF documentation](https://docs.cilium.io/en/latest/bpf/#bpf-guide) (recommended) -* [Linux documentation on BPF](http://elixir.free-electrons.com/linux/latest/source/Documentation/networking/filter.txt) -* [eBPF features by Linux version](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md) diff --git a/vendor/github.com/cilium/ebpf/run-tests.sh b/vendor/github.com/cilium/ebpf/run-tests.sh index bd349f951..647a61aab 100644 --- a/vendor/github.com/cilium/ebpf/run-tests.sh +++ b/vendor/github.com/cilium/ebpf/run-tests.sh @@ -16,16 +16,16 @@ if [[ "${1:-}" = "--in-vm" ]]; then export GOSUMDB=off export GOCACHE=/run/go-cache - elfs="" if [[ -d "/run/input/bpf" ]]; then - elfs="/run/input/bpf" + export KERNEL_SELFTESTS="/run/input/bpf" fi + readonly output="${1}" + shift + echo Running tests... - # TestLibBPFCompat runs separately to pass the "-elfs" flag only for it: https://github.com/cilium/ebpf/pull/119 - go test -v -count 1 -run TestLibBPFCompat -elfs "$elfs" - go test -v -count 1 ./... - touch "$1/success" + go test -v -coverpkg=./... -coverprofile="$output/coverage.txt" -count 1 ./... + touch "$output/success" exit 0 fi @@ -82,11 +82,9 @@ if [[ ! -e "${output}/success" ]]; then exit 1 else echo "Test successful on ${kernel_version}" -# if [[ -v CODECOV_TOKEN ]]; then -# curl --fail -s https://codecov.io/bash > "${tmp_dir}/codecov.sh" -# chmod +x "${tmp_dir}/codecov.sh" -# "${tmp_dir}/codecov.sh" -f "${output}/coverage.txt" -# fi + if [[ -v COVERALLS_TOKEN ]]; then + goveralls -coverprofile="${output}/coverage.txt" -service=semaphore -repotoken "$COVERALLS_TOKEN" + fi fi $sudo rm -r "${input}" diff --git a/vendor/github.com/cilium/ebpf/syscalls.go b/vendor/github.com/cilium/ebpf/syscalls.go index ff5c8e6c3..1cba1d747 100644 --- a/vendor/github.com/cilium/ebpf/syscalls.go +++ b/vendor/github.com/cilium/ebpf/syscalls.go @@ -3,7 +3,6 @@ package ebpf import ( "errors" "fmt" - "os" "unsafe" "github.com/cilium/ebpf/internal" @@ -12,9 +11,7 @@ import ( ) // Generic errors returned by BPF syscalls. -var ( - ErrNotExist = errors.New("requested object does not exist") -) +var ErrNotExist = errors.New("requested object does not exist") // bpfObjName is a null-terminated string made up of // 'A-Za-z0-9_' characters. @@ -27,18 +24,20 @@ func newBPFObjName(name string) bpfObjName { return result } +// invalidBPFObjNameChar returns true if char may not appear in +// a BPF object name. func invalidBPFObjNameChar(char rune) bool { dotAllowed := objNameAllowsDot() == nil switch { case char >= 'A' && char <= 'Z': - fallthrough + return false case char >= 'a' && char <= 'z': - fallthrough + return false case char >= '0' && char <= '9': - fallthrough + return false case dotAllowed && char == '.': - fallthrough + return false case char == '_': return false default: @@ -69,14 +68,32 @@ type bpfMapOpAttr struct { flags uint64 } +type bpfBatchMapOpAttr struct { + inBatch internal.Pointer + outBatch internal.Pointer + keys internal.Pointer + values internal.Pointer + count uint32 + mapFd uint32 + elemFlags uint64 + flags uint64 +} + type bpfMapInfo struct { - mapType uint32 - id uint32 - keySize uint32 - valueSize uint32 - maxEntries uint32 - flags uint32 - mapName bpfObjName // since 4.15 ad5b177bd73f + map_type uint32 // since 4.12 1e2709769086 + id uint32 + key_size uint32 + value_size uint32 + max_entries uint32 + map_flags uint32 + name bpfObjName // since 4.15 ad5b177bd73f + ifindex uint32 // since 4.16 52775b33bb50 + btf_vmlinux_value_type_id uint32 // since 5.6 85d33df357b6 + netns_dev uint64 // since 4.16 52775b33bb50 + netns_ino uint64 + btf_id uint32 // since 4.18 78958fca7ead + btf_key_type_id uint32 // since 4.18 9b2cf328b2ec + btf_value_type_id uint32 } type bpfProgLoadAttr struct { @@ -104,18 +121,40 @@ type bpfProgLoadAttr struct { } type bpfProgInfo struct { - progType uint32 - id uint32 - tag [unix.BPF_TAG_SIZE]byte - jitedLen uint32 - xlatedLen uint32 - jited internal.Pointer - xlated internal.Pointer - loadTime uint64 // since 4.15 cb4d2b3f03d8 - createdByUID uint32 - nrMapIDs uint32 - mapIds internal.Pointer - name bpfObjName + prog_type uint32 + id uint32 + tag [unix.BPF_TAG_SIZE]byte + jited_prog_len uint32 + xlated_prog_len uint32 + jited_prog_insns internal.Pointer + xlated_prog_insns internal.Pointer + load_time uint64 // since 4.15 cb4d2b3f03d8 + created_by_uid uint32 + nr_map_ids uint32 + map_ids internal.Pointer + name bpfObjName // since 4.15 067cae47771c + ifindex uint32 + gpl_compatible uint32 + netns_dev uint64 + netns_ino uint64 + nr_jited_ksyms uint32 + nr_jited_func_lens uint32 + jited_ksyms internal.Pointer + jited_func_lens internal.Pointer + btf_id uint32 + func_info_rec_size uint32 + func_info internal.Pointer + nr_func_info uint32 + nr_line_info uint32 + line_info internal.Pointer + jited_line_info internal.Pointer + nr_jited_line_info uint32 + line_info_rec_size uint32 + jited_line_info_rec_size uint32 + nr_prog_tags uint32 + prog_tags internal.Pointer + run_time_ns uint64 + run_cnt uint64 } type bpfProgTestRunAttr struct { @@ -168,10 +207,6 @@ func bpfProgTestRun(attr *bpfProgTestRunAttr) error { func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) { fd, err := internal.BPF(internal.BPF_MAP_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) - if errors.Is(err, os.ErrPermission) { - return nil, errors.New("permission denied or insufficient rlimit to lock memory for map") - } - if err != nil { return nil, err } @@ -179,35 +214,25 @@ func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) { return internal.NewFD(uint32(fd)), nil } -var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() (bool, error) { - inner, err := bpfMapCreate(&bpfMapCreateAttr{ - mapType: Array, - keySize: 4, - valueSize: 4, - maxEntries: 1, - }) - if err != nil { - return false, err - } - defer inner.Close() - - innerFd, _ := inner.Value() - nested, err := bpfMapCreate(&bpfMapCreateAttr{ +var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() error { + _, err := bpfMapCreate(&bpfMapCreateAttr{ mapType: ArrayOfMaps, keySize: 4, valueSize: 4, maxEntries: 1, - innerMapFd: innerFd, + // Invalid file descriptor. + innerMapFd: ^uint32(0), }) - if err != nil { - return false, nil + if errors.Is(err, unix.EINVAL) { + return internal.ErrNotSupported } - - _ = nested.Close() - return true, nil + if errors.Is(err, unix.EBADF) { + return nil + } + return err }) -var haveMapMutabilityModifiers = internal.FeatureTest("read- and write-only maps", "5.2", func() (bool, error) { +var haveMapMutabilityModifiers = internal.FeatureTest("read- and write-only maps", "5.2", func() error { // This checks BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG. Since // BPF_MAP_FREEZE appeared in 5.2 as well we don't do a separate check. m, err := bpfMapCreate(&bpfMapCreateAttr{ @@ -218,10 +243,10 @@ var haveMapMutabilityModifiers = internal.FeatureTest("read- and write-only maps flags: unix.BPF_F_RDONLY_PROG, }) if err != nil { - return false, nil + return internal.ErrNotSupported } _ = m.Close() - return true, nil + return nil }) func bpfMapLookupElem(m *internal.FD, key, valueOut internal.Pointer) error { @@ -307,6 +332,29 @@ func objGetNextID(cmd internal.BPFCmd, start uint32) (uint32, error) { return attr.nextID, wrapObjError(err) } +func bpfMapBatch(cmd internal.BPFCmd, m *internal.FD, inBatch, outBatch, keys, values internal.Pointer, count uint32, opts *BatchOptions) (uint32, error) { + fd, err := m.Value() + if err != nil { + return 0, err + } + + attr := bpfBatchMapOpAttr{ + inBatch: inBatch, + outBatch: outBatch, + keys: keys, + values: values, + count: count, + mapFd: fd, + } + if opts != nil { + attr.elemFlags = opts.ElemFlags + attr.flags = opts.Flags + } + _, err = internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) + // always return count even on an error, as things like update might partially be fulfilled. + return attr.count, wrapMapError(err) +} + func wrapObjError(err error) error { if err == nil { return nil @@ -331,6 +379,10 @@ func wrapMapError(err error) error { return ErrKeyExist } + if errors.Is(err, unix.ENOTSUPP) { + return ErrNotSupported + } + return errors.New(err.Error()) } @@ -364,7 +416,7 @@ func bpfGetMapInfoByFD(fd *internal.FD) (*bpfMapInfo, error) { return &info, nil } -var haveObjName = internal.FeatureTest("object names", "4.15", func() (bool, error) { +var haveObjName = internal.FeatureTest("object names", "4.15", func() error { attr := bpfMapCreateAttr{ mapType: Array, keySize: 4, @@ -375,16 +427,16 @@ var haveObjName = internal.FeatureTest("object names", "4.15", func() (bool, err fd, err := bpfMapCreate(&attr) if err != nil { - return false, nil + return internal.ErrNotSupported } _ = fd.Close() - return true, nil + return nil }) -var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func() (bool, error) { +var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func() error { if err := haveObjName(); err != nil { - return false, err + return err } attr := bpfMapCreateAttr{ @@ -397,11 +449,37 @@ var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func() fd, err := bpfMapCreate(&attr) if err != nil { - return false, nil + return internal.ErrNotSupported } _ = fd.Close() - return true, nil + return nil +}) + +var haveBatchAPI = internal.FeatureTest("map batch api", "5.6", func() error { + var maxEntries uint32 = 2 + attr := bpfMapCreateAttr{ + mapType: Hash, + keySize: 4, + valueSize: 4, + maxEntries: maxEntries, + } + + fd, err := bpfMapCreate(&attr) + if err != nil { + return internal.ErrNotSupported + } + defer fd.Close() + keys := []uint32{1, 2} + values := []uint32{3, 4} + kp, _ := marshalPtr(keys, 8) + vp, _ := marshalPtr(values, 8) + nilPtr := internal.NewPointer(nil) + _, err = bpfMapBatch(internal.BPF_MAP_UPDATE_BATCH, fd, nilPtr, nilPtr, kp, vp, maxEntries, nil) + if err != nil { + return internal.ErrNotSupported + } + return nil }) func bpfObjGetFDByID(cmd internal.BPFCmd, id uint32) (*internal.FD, error) { diff --git a/vendor/github.com/cilium/ebpf/types.go b/vendor/github.com/cilium/ebpf/types.go index dd82dfd4d..3191ba1e0 100644 --- a/vendor/github.com/cilium/ebpf/types.go +++ b/vendor/github.com/cilium/ebpf/types.go @@ -85,10 +85,19 @@ const ( // hasPerCPUValue returns true if the Map stores a value per CPU. func (mt MapType) hasPerCPUValue() bool { - if mt == PerCPUHash || mt == PerCPUArray || mt == LRUCPUHash { - return true - } - return false + return mt == PerCPUHash || mt == PerCPUArray || mt == LRUCPUHash +} + +// canStoreMap returns true if the map type accepts a map fd +// for update and returns a map id for lookup. +func (mt MapType) canStoreMap() bool { + return mt == ArrayOfMaps || mt == HashOfMaps +} + +// canStoreProgram returns true if the map type accepts a program fd +// for update and returns a program id for lookup. +func (mt MapType) canStoreProgram() bool { + return mt == ProgramArray } // ProgramType of the eBPF program @@ -134,7 +143,7 @@ const ( // Will cause invalid argument (EINVAL) at program load time if set incorrectly. type AttachType uint32 -// AttachNone is an alias for AttachCGroupInetIngress for readability reasons +// AttachNone is an alias for AttachCGroupInetIngress for readability reasons. const AttachNone AttachType = 0 const ( @@ -192,3 +201,13 @@ const ( // Pin an object by using its name as the filename. PinByName ) + +// BatchOptions batch map operations options +// +// Mirrors libbpf struct bpf_map_batch_opts +// Currently BPF_F_FLAG is the only supported +// flag (for ElemFlags). +type BatchOptions struct { + ElemFlags uint64 + Flags uint64 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 19d388865..0f2933e79 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2,7 +2,7 @@ ## explicit github.com/checkpoint-restore/go-criu/v5 github.com/checkpoint-restore/go-criu/v5/rpc -# github.com/cilium/ebpf v0.2.0 +# github.com/cilium/ebpf v0.4.0 ## explicit github.com/cilium/ebpf github.com/cilium/ebpf/asm