Commit 4fbc2416 authored by Brad Davidson's avatar Brad Davidson Committed by Brad Davidson

Update to forked protobuf 1.4.3-k3s1

parent 94266658
...@@ -19,11 +19,11 @@ replace ( ...@@ -19,11 +19,11 @@ replace (
github.com/docker/distribution => github.com/docker/distribution v2.7.1+incompatible github.com/docker/distribution => github.com/docker/distribution v2.7.1+incompatible
github.com/docker/docker => github.com/docker/docker v20.10.2+incompatible github.com/docker/docker => github.com/docker/docker v20.10.2+incompatible
github.com/docker/libnetwork => github.com/docker/libnetwork v0.8.0-dev.2.0.20190624125649-f0e46a78ea34 github.com/docker/libnetwork => github.com/docker/libnetwork v0.8.0-dev.2.0.20190624125649-f0e46a78ea34
github.com/golang/protobuf => github.com/golang/protobuf v1.3.5 github.com/golang/protobuf => github.com/k3s-io/protobuf v1.4.3-k3s1
github.com/juju/errors => github.com/k3s-io/nocode v0.0.0-20200630202308-cb097102c09f github.com/juju/errors => github.com/k3s-io/nocode v0.0.0-20200630202308-cb097102c09f
github.com/kubernetes-sigs/cri-tools => github.com/k3s-io/cri-tools v1.20.0-k3s1 github.com/kubernetes-sigs/cri-tools => github.com/k3s-io/cri-tools v1.21.0-k3s1
github.com/matryer/moq => github.com/rancher/moq v0.0.0-20190404221404-ee5226d43009 github.com/matryer/moq => github.com/rancher/moq v0.0.0-20190404221404-ee5226d43009
github.com/opencontainers/runc => github.com/opencontainers/runc v1.0.0-rc92 github.com/opencontainers/runc => github.com/opencontainers/runc v1.0.0-rc93.0.20210414171415-3397a09ee932
github.com/opencontainers/runtime-spec => github.com/opencontainers/runtime-spec v1.0.3-0.20210316141917-a8c4a9ee0f6b github.com/opencontainers/runtime-spec => github.com/opencontainers/runtime-spec v1.0.3-0.20210316141917-a8c4a9ee0f6b
go.etcd.io/etcd => github.com/k3s-io/etcd v0.5.0-alpha.5.0.20201208200253-50621aee4aea go.etcd.io/etcd => github.com/k3s-io/etcd v0.5.0-alpha.5.0.20201208200253-50621aee4aea
golang.org/x/crypto => golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 golang.org/x/crypto => golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
...@@ -71,10 +71,9 @@ require ( ...@@ -71,10 +71,9 @@ require (
github.com/containerd/fuse-overlayfs-snapshotter v1.0.2 github.com/containerd/fuse-overlayfs-snapshotter v1.0.2
github.com/coreos/go-iptables v0.4.5 github.com/coreos/go-iptables v0.4.5
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
github.com/docker/docker v20.10.2+incompatible github.com/docker/docker v20.10.5+incompatible
github.com/erikdubbelboer/gspt v0.0.0-20190125194910-e68493906b83 github.com/erikdubbelboer/gspt v0.0.0-20190125194910-e68493906b83
github.com/flannel-io/flannel v0.13.1-rc2.0.20210324170423-273b36ca57dc github.com/flannel-io/flannel v0.13.1-rc2.0.20210324170423-273b36ca57dc
github.com/frankban/quicktest v1.11.3 // indirect
github.com/go-bindata/go-bindata v3.1.2+incompatible github.com/go-bindata/go-bindata v3.1.2+incompatible
github.com/go-sql-driver/mysql v1.4.1 github.com/go-sql-driver/mysql v1.4.1
github.com/golangplus/testing v1.0.0 // indirect github.com/golangplus/testing v1.0.0 // indirect
...@@ -110,7 +109,7 @@ require ( ...@@ -110,7 +109,7 @@ require (
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 golang.org/x/net v0.0.0-20210224082022-3d97a244fca7
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073
google.golang.org/grpc v1.33.2 google.golang.org/grpc v1.37.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.21.0 k8s.io/api v0.21.0
k8s.io/apimachinery v0.21.0 k8s.io/apimachinery v0.21.0
......
language: go
dist: bionic
os:
- linux
go:
- "1.14.x"
- "1.13.x"
- tip
env:
# Run the tests with CRIU master and criu-dev
- CRIU_BRANCH="master"
- CRIU_BRANCH="criu-dev"
install:
- sudo apt-get update
- sudo apt-get install -y libprotobuf-dev libprotobuf-c0-dev protobuf-c-compiler protobuf-compiler python-protobuf libnl-3-dev libnet-dev libcap-dev
- make install.tools
- go get github.com/checkpoint-restore/go-criu
- git clone --single-branch -b ${CRIU_BRANCH} https://github.com/checkpoint-restore/criu.git
- cd criu; make
- sudo install -D -m 755 criu/criu /usr/sbin/
- cd ..
script:
# This builds the code without running the tests.
- make lint build phaul test/test test/phaul test/piggie
# Run actual test as root as it uses CRIU.
- sudo make test phaul-test
# This builds crit-go
- make -C crit-go/magic-gen lint build magicgen test
GO ?= go
CC ?= gcc
ifeq ($(GOPATH),)
export GOPATH := $(shell $(GO) env GOPATH)
endif
FIRST_GOPATH := $(firstword $(subst :, ,$(GOPATH)))
GOBIN := $(shell $(GO) env GOBIN)
ifeq ($(GOBIN),)
GOBIN := $(FIRST_GOPATH)/bin
endif
all: build test phaul phaul-test
lint:
@golint -set_exit_status . test phaul
build:
@$(GO) build -v
test/piggie: test/piggie.c
@$(CC) $^ -o $@
test/test: test/main.go
@$(GO) build -v -o test/test test/main.go
test: test/test test/piggie
mkdir -p image
test/piggie
test/test dump `pidof piggie` image
test/test restore image
pkill -9 piggie || :
phaul:
@cd phaul; go build -v
test/phaul: test/phaul-main.go
@$(GO) build -v -o test/phaul test/phaul-main.go
phaul-test: test/phaul test/piggie
rm -rf image
test/piggie
test/phaul `pidof piggie`
pkill -9 piggie || :
clean:
@rm -f test/test test/piggie test/phaul
@rm -rf image
@rm -f rpc/rpc.proto
install.tools:
if [ ! -x "$(GOBIN)/golint" ]; then \
$(GO) get -u golang.org/x/lint/golint; \
fi
rpc/rpc.proto:
curl -s https://raw.githubusercontent.com/checkpoint-restore/criu/master/images/rpc.proto -o $@
rpc/rpc.pb.go: rpc/rpc.proto
protoc --go_out=. $^
.PHONY: build test clean lint phaul
module github.com/checkpoint-restore/go-criu/v4
go 1.13
require github.com/golang/protobuf v1.3.5
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
test/test test/test
test/piggie test/piggie/piggie
test/phaul test/phaul
image image
rpc/rpc.proto rpc/rpc.proto
stats/stats.proto
run:
skip_dirs:
- rpc
- stats
linters:
disable-all: false
presets:
- bugs
- performance
- unused
- format
GO ?= go
CC ?= gcc
all: build test phaul-test
lint:
golangci-lint run ./...
build:
$(GO) build -v ./...
TEST_BINARIES := test/test test/piggie/piggie test/phaul/phaul
test-bin: $(TEST_BINARIES)
test/piggie/piggie: test/piggie/piggie.c
$(CC) $^ -o $@
test/test: test/*.go
$(GO) build -v -o $@ $^
test: $(TEST_BINARIES)
mkdir -p image
PID=$$(test/piggie/piggie) && { \
test/test dump $$PID image && \
test/test restore image; \
pkill -9 piggie; \
}
rm -rf image
test/phaul/phaul: test/phaul/*.go
$(GO) build -v -o $@ $^
phaul-test: $(TEST_BINARIES)
rm -rf image
PID=$$(test/piggie/piggie) && { \
test/phaul/phaul $$PID; \
pkill -9 piggie; \
}
clean:
@rm -f $(TEST_BINARIES)
@rm -rf image
@rm -f rpc/rpc.proto stats/stats.proto
rpc/rpc.proto:
curl -sSL https://raw.githubusercontent.com/checkpoint-restore/criu/master/images/rpc.proto -o $@
stats/stats.proto:
curl -sSL https://raw.githubusercontent.com/checkpoint-restore/criu/master/images/stats.proto -o $@
rpc/rpc.pb.go: rpc/rpc.proto
protoc --go_out=. $^
stats/stats.pb.go: stats/stats.proto
protoc --go_out=. $^
.PHONY: build test phaul-test test-bin clean lint
[![master](https://travis-ci.org/checkpoint-restore/go-criu.svg?branch=master)](https://travis-ci.org/checkpoint-restore/go-criu) [![test](https://github.com/checkpoint-restore/go-criu/workflows/ci/badge.svg?branch=master)](https://github.com/checkpoint-restore/go-criu/actions?query=workflow%3Aci)
[![verify](https://github.com/checkpoint-restore/go-criu/workflows/verify/badge.svg?branch=master)](https://github.com/checkpoint-restore/go-criu/actions?query=workflow%3Averify)
[![Go Reference](https://pkg.go.dev/badge/github.com/checkpoint-restore/go-criu.svg)](https://pkg.go.dev/github.com/checkpoint-restore/go-criu)
## go-criu -- Go bindings for [CRIU](https://criu.org/) ## go-criu -- Go bindings for CRIU
This repository provides Go bindings for CRIU. The code is based on the Go based PHaul This repository provides Go bindings for [CRIU](https://criu.org/). The code is based on the Go-based PHaul
implementation from the CRIU repository. For easier inclusion into other Go projects the implementation from the CRIU repository. For easier inclusion into other Go projects the
CRIU Go bindings have been moved to this repository. CRIU Go bindings have been moved to this repository.
...@@ -10,13 +12,26 @@ The Go bindings provide an easy way to use the CRIU RPC calls from Go without th ...@@ -10,13 +12,26 @@ The Go bindings provide an easy way to use the CRIU RPC calls from Go without th
to set up all the infrastructure to make the actual RPC connection to CRIU. to set up all the infrastructure to make the actual RPC connection to CRIU.
The following example would print the version of CRIU: The following example would print the version of CRIU:
``` ```go
import (
"log"
"github.com/checkpoint/restore/go-criu/v5"
)
func main() {
c := criu.MakeCriu() c := criu.MakeCriu()
version, err := c.GetCriuVersion() version, err := c.GetCriuVersion()
fmt.Println(version) if err != nil {
log.Fatalln(err)
}
log.Println(version)
}
``` ```
or to just check if at least a certain CRIU version is installed: or to just check if at least a certain CRIU version is installed:
```
```go
c := criu.MakeCriu() c := criu.MakeCriu()
result, err := c.IsCriuAtLeast(31100) result, err := c.IsCriuAtLeast(31100)
``` ```
...@@ -31,7 +46,12 @@ As go-criu is imported in other projects and as Go modules are expected ...@@ -31,7 +46,12 @@ As go-criu is imported in other projects and as Go modules are expected
to follow Semantic Versioning go-criu will also follow Semantic Versioning to follow Semantic Versioning go-criu will also follow Semantic Versioning
starting with the 4.0.0 release. starting with the 4.0.0 release.
4.0.0 is based on CRIU 3.14 The following table shows the relation between go-criu and criu versions:
| Major version | Latest release | CRIU version |
| -------------- | -------------- | ------------ |
| v5             | 5.0.0         | 3.15         |
| v4             | 4.1.0         | 3.14         |
## How to contribute ## How to contribute
......
module github.com/checkpoint-restore/go-criu/v5
go 1.13
require (
github.com/golang/protobuf v1.4.3
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
google.golang.org/protobuf v1.23.0
)
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
...@@ -8,8 +8,8 @@ import ( ...@@ -8,8 +8,8 @@ import (
"strconv" "strconv"
"syscall" "syscall"
"github.com/checkpoint-restore/go-criu/v4/rpc" "github.com/checkpoint-restore/go-criu/v5/rpc"
"github.com/golang/protobuf/proto" "google.golang.org/protobuf/proto"
) )
// Criu struct // Criu struct
...@@ -45,6 +45,7 @@ func (c *Criu) Prepare() error { ...@@ -45,6 +45,7 @@ func (c *Criu) Prepare() error {
defer srv.Close() defer srv.Close()
args := []string{"swrk", strconv.Itoa(fds[1])} args := []string{"swrk", strconv.Itoa(fds[1])}
// #nosec G204
cmd := exec.Command(c.swrkPath, args...) cmd := exec.Command(c.swrkPath, args...)
err = cmd.Start() err = cmd.Start()
...@@ -64,7 +65,7 @@ func (c *Criu) Cleanup() { ...@@ -64,7 +65,7 @@ func (c *Criu) Cleanup() {
if c.swrkCmd != nil { if c.swrkCmd != nil {
c.swrkSk.Close() c.swrkSk.Close()
c.swrkSk = nil c.swrkSk = nil
c.swrkCmd.Wait() _ = c.swrkCmd.Wait()
c.swrkCmd = nil c.swrkCmd = nil
} }
} }
...@@ -187,28 +188,28 @@ func (c *Criu) doSwrkWithResp(reqType rpc.CriuReqType, opts *rpc.CriuOpts, nfy N ...@@ -187,28 +188,28 @@ func (c *Criu) doSwrkWithResp(reqType rpc.CriuReqType, opts *rpc.CriuOpts, nfy N
} }
// Dump dumps a process // Dump dumps a process
func (c *Criu) Dump(opts rpc.CriuOpts, nfy Notify) error { func (c *Criu) Dump(opts *rpc.CriuOpts, nfy Notify) error {
return c.doSwrk(rpc.CriuReqType_DUMP, &opts, nfy) return c.doSwrk(rpc.CriuReqType_DUMP, opts, nfy)
} }
// Restore restores a process // Restore restores a process
func (c *Criu) Restore(opts rpc.CriuOpts, nfy Notify) error { func (c *Criu) Restore(opts *rpc.CriuOpts, nfy Notify) error {
return c.doSwrk(rpc.CriuReqType_RESTORE, &opts, nfy) return c.doSwrk(rpc.CriuReqType_RESTORE, opts, nfy)
} }
// PreDump does a pre-dump // PreDump does a pre-dump
func (c *Criu) PreDump(opts rpc.CriuOpts, nfy Notify) error { func (c *Criu) PreDump(opts *rpc.CriuOpts, nfy Notify) error {
return c.doSwrk(rpc.CriuReqType_PRE_DUMP, &opts, nfy) return c.doSwrk(rpc.CriuReqType_PRE_DUMP, opts, nfy)
} }
// StartPageServer starts the page server // StartPageServer starts the page server
func (c *Criu) StartPageServer(opts rpc.CriuOpts) error { func (c *Criu) StartPageServer(opts *rpc.CriuOpts) error {
return c.doSwrk(rpc.CriuReqType_PAGE_SERVER, &opts, nil) return c.doSwrk(rpc.CriuReqType_PAGE_SERVER, opts, nil)
} }
// StartPageServerChld starts the page server and returns PID and port // StartPageServerChld starts the page server and returns PID and port
func (c *Criu) StartPageServerChld(opts rpc.CriuOpts) (int, int, error) { func (c *Criu) StartPageServerChld(opts *rpc.CriuOpts) (int, int, error) {
resp, err := c.doSwrkWithResp(rpc.CriuReqType_PAGE_SERVER_CHLD, &opts, nil) resp, err := c.doSwrkWithResp(rpc.CriuReqType_PAGE_SERVER_CHLD, opts, nil)
if err != nil { if err != nil {
return 0, 0, err return 0, 0, err
} }
......
package criu package criu
//Notify interface // Notify interface
type Notify interface { type Notify interface {
PreDump() error PreDump() error
PostDump() error PostDump() error
...@@ -14,8 +14,7 @@ type Notify interface { ...@@ -14,8 +14,7 @@ type Notify interface {
} }
// NoNotify struct // NoNotify struct
type NoNotify struct { type NoNotify struct{}
}
// PreDump NoNotify // PreDump NoNotify
func (c NoNotify) PreDump() error { func (c NoNotify) PreDump() error {
......
---
Language: Cpp
BasedOnStyle: LLVM
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: true
AlignEscapedNewlines: DontAlign
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortFunctionsOnASingleLine: false
BreakBeforeBraces: Attach
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
TabWidth: 4
UseTab: ForContinuationAndIndentation
ColumnLimit: 1000
...
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
*.o
# Test binary, build with `go test -c` # Test binary, build with `go test -c`
*.test *.test
......
---
issues:
exclude-rules:
# syscall param structs will have unused fields in Go code.
- path: syscall.*.go
linters:
- structcheck
linters:
disable-all: true
enable:
- deadcode
- errcheck
- goimports
- gosimple
- govet
- ineffassign
- misspell
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
# Could be enabled later:
# - gocyclo
# - prealloc
# - maligned
# - gosec
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.
# 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
# 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 > "$@"
# 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
compiling and embedding eBPF programs in Go code
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.
## Getting Started
A small collection of Go and eBPF programs that serve as examples for building
your own tools can be found under [examples/](examples/).
Contributions are highly encouraged, as they highlight certain use cases of
eBPF and the library, and help shape the future of the project.
## 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
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 newProgramABIFromSpec(spec *ProgramSpec) *ProgramABI {
return &ProgramABI{
spec.Type,
}
}
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
}
}
...@@ -7,7 +7,7 @@ type BuiltinFunc int32 ...@@ -7,7 +7,7 @@ type BuiltinFunc int32
// eBPF built-in functions // eBPF built-in functions
// //
// You can renegerate this list using the following gawk script: // You can regenerate this list using the following gawk script:
// //
// /FN\(.+\),/ { // /FN\(.+\),/ {
// match($1, /\((.+)\)/, r) // match($1, /\((.+)\)/, r)
......
package asm package asm
import ( import (
"crypto/sha1"
"encoding/binary" "encoding/binary"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math" "math"
"strings" "strings"
"github.com/cilium/ebpf/internal/unix"
) )
// InstructionSize is the size of a BPF instruction in bytes // InstructionSize is the size of a BPF instruction in bytes
const InstructionSize = 8 const InstructionSize = 8
// RawInstructionOffset is an offset in units of raw BPF instructions.
type RawInstructionOffset uint64
// Bytes returns the offset of an instruction in bytes.
func (rio RawInstructionOffset) Bytes() uint64 {
return uint64(rio) * InstructionSize
}
// Instruction is a single eBPF instruction. // Instruction is a single eBPF instruction.
type Instruction struct { type Instruction struct {
OpCode OpCode OpCode OpCode
...@@ -151,10 +163,20 @@ func (ins *Instruction) mapOffset() uint32 { ...@@ -151,10 +163,20 @@ func (ins *Instruction) mapOffset() uint32 {
return uint32(uint64(ins.Constant) >> 32) 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 { func (ins *Instruction) isLoadFromMap() bool {
return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue) return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue)
} }
// IsFunctionCall returns true if the instruction calls another BPF function.
//
// This is not the same thing as a BPF helper call.
func (ins *Instruction) IsFunctionCall() bool {
return ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall
}
// Format implements fmt.Formatter. // Format implements fmt.Formatter.
func (ins Instruction) Format(f fmt.State, c rune) { func (ins Instruction) Format(f fmt.State, c rune) {
if c != 'v' { if c != 'v' {
...@@ -310,34 +332,12 @@ func (insns Instructions) ReferenceOffsets() map[string][]int { ...@@ -310,34 +332,12 @@ func (insns Instructions) ReferenceOffsets() map[string][]int {
return offsets return offsets
} }
func (insns Instructions) marshalledOffsets() (map[string]int, error) {
symbols := make(map[string]int)
marshalledPos := 0
for _, ins := range insns {
currentPos := marshalledPos
marshalledPos += ins.OpCode.marshalledInstructions()
if ins.Symbol == "" {
continue
}
if _, ok := symbols[ins.Symbol]; ok {
return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
}
symbols[ins.Symbol] = currentPos
}
return symbols, nil
}
// Format implements fmt.Formatter. // Format implements fmt.Formatter.
// //
// You can control indentation of symbols by // You can control indentation of symbols by
// specifying a width. Setting a precision controls the indentation of // specifying a width. Setting a precision controls the indentation of
// instructions. // instructions.
// The default character is a tab, which can be overriden by specifying // The default character is a tab, which can be overridden by specifying
// the ' ' space flag. // the ' ' space flag.
func (insns Instructions) Format(f fmt.State, c rune) { func (insns Instructions) Format(f fmt.State, c rune) {
if c != 's' && c != 'v' { if c != 's' && c != 'v' {
...@@ -370,63 +370,83 @@ func (insns Instructions) Format(f fmt.State, c rune) { ...@@ -370,63 +370,83 @@ func (insns Instructions) Format(f fmt.State, c rune) {
symIndent = strings.Repeat(" ", symPadding) symIndent = strings.Repeat(" ", symPadding)
} }
// Figure out how many digits we need to represent the highest // Guess how many digits we need at most, by assuming that all instructions
// offset. // are double wide.
highestOffset := 0 highestOffset := len(insns) * 2
for _, ins := range insns {
highestOffset += ins.OpCode.marshalledInstructions()
}
offsetWidth := int(math.Ceil(math.Log10(float64(highestOffset)))) offsetWidth := int(math.Ceil(math.Log10(float64(highestOffset))))
offset := 0 iter := insns.Iterate()
for _, ins := range insns { for iter.Next() {
if ins.Symbol != "" { if iter.Ins.Symbol != "" {
fmt.Fprintf(f, "%s%s:\n", symIndent, ins.Symbol) fmt.Fprintf(f, "%s%s:\n", symIndent, iter.Ins.Symbol)
} }
fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, offset, ins) fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, iter.Offset, iter.Ins)
offset += ins.OpCode.marshalledInstructions()
} }
return
} }
// Marshal encodes a BPF program into the kernel format. // Marshal encodes a BPF program into the kernel format.
func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error { func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
absoluteOffsets, err := insns.marshalledOffsets() for i, ins := range insns {
_, err := ins.Marshal(w, bo)
if err != nil { if err != nil {
return err return fmt.Errorf("instruction %d: %w", i, err)
} }
}
return nil
}
num := 0 // 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 { for i, ins := range insns {
switch { if ins.isLoadFromMap() {
case ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall && ins.Constant == -1: ins.Constant = 0
// Rewrite bpf to bpf call
offset, ok := absoluteOffsets[ins.Reference]
if !ok {
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
} }
_, err := ins.Marshal(h, bo)
ins.Constant = int64(offset - num - 1) if err != nil {
return "", fmt.Errorf("instruction %d: %w", i, err)
case ins.OpCode.Class() == JumpClass && ins.Offset == -1:
// Rewrite jump to label
offset, ok := absoluteOffsets[ins.Reference]
if !ok {
return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
} }
ins.Offset = int16(offset - num - 1)
} }
return hex.EncodeToString(h.Sum(nil)[:unix.BPF_TAG_SIZE]), nil
}
n, err := ins.Marshal(w, bo) // Iterate allows iterating a BPF program while keeping track of
if err != nil { // various offsets.
return fmt.Errorf("instruction %d: %w", i, err) //
// Modifying the instruction slice will lead to undefined behaviour.
func (insns Instructions) Iterate() *InstructionIterator {
return &InstructionIterator{insns: insns}
}
// InstructionIterator iterates over a BPF program.
type InstructionIterator struct {
insns Instructions
// The instruction in question.
Ins *Instruction
// The index of the instruction in the original instruction slice.
Index int
// The offset of the instruction in raw BPF instructions. This accounts
// for double-wide instructions.
Offset RawInstructionOffset
}
// Next returns true as long as there are any instructions remaining.
func (iter *InstructionIterator) Next() bool {
if len(iter.insns) == 0 {
return false
} }
num += int(n / InstructionSize) if iter.Ins != nil {
iter.Index++
iter.Offset += RawInstructionOffset(iter.Ins.OpCode.rawInstructions())
} }
return nil iter.Ins = &iter.insns[0]
iter.insns = iter.insns[1:]
return true
} }
type bpfInstruction struct { type bpfInstruction struct {
......
...@@ -66,10 +66,10 @@ type OpCode uint8 ...@@ -66,10 +66,10 @@ type OpCode uint8
// InvalidOpCode is returned by setters on OpCode // InvalidOpCode is returned by setters on OpCode
const InvalidOpCode OpCode = 0xff const InvalidOpCode OpCode = 0xff
// marshalledInstructions returns the number of BPF instructions required // rawInstructions returns the number of BPF instructions required
// to encode this opcode. // to encode this opcode.
func (op OpCode) marshalledInstructions() int { func (op OpCode) rawInstructions() int {
if op == LoadImmOp(DWord) { if op.isDWordLoad() {
return 2 return 2
} }
return 1 return 1
......
...@@ -12,6 +12,5 @@ ...@@ -12,6 +12,5 @@
// eBPF code should be compiled ahead of time using clang, and shipped with // eBPF code should be compiled ahead of time using clang, and shipped with
// your application as any other resource. // your application as any other resource.
// //
// This package doesn't include code required to attach eBPF to Linux // Use the link subpackage to attach a loaded program to a hook in the kernel.
// subsystems, since this varies per subsystem.
package ebpf package ebpf
// +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
}
module github.com/cilium/ebpf module github.com/cilium/ebpf
go 1.13 go 1.15
require golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 require (
github.com/frankban/quicktest v1.11.3
github.com/google/go-cmp v0.5.4
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
)
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk= github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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=
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
}
...@@ -31,12 +31,14 @@ const ( ...@@ -31,12 +31,14 @@ const (
kindDatasec kindDatasec
) )
// btfFuncLinkage describes BTF function linkage metadata.
type btfFuncLinkage uint8 type btfFuncLinkage uint8
// Equivalent of enum btf_func_linkage.
const ( const (
linkageStatic btfFuncLinkage = iota linkageStatic btfFuncLinkage = iota
linkageGlobal linkageGlobal
linkageExtern // linkageExtern // Currently unused in libbpf.
) )
const ( const (
...@@ -44,6 +46,8 @@ const ( ...@@ -44,6 +46,8 @@ const (
btfTypeKindLen = 4 btfTypeKindLen = 4
btfTypeVlenShift = 0 btfTypeVlenShift = 0
btfTypeVlenMask = 16 btfTypeVlenMask = 16
btfTypeKindFlagShift = 31
btfTypeKindFlagMask = 1
) )
// btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst. // btfType is equivalent to struct btf_type in Documentation/bpf/btf.rst.
...@@ -136,6 +140,10 @@ func (bt *btfType) SetVlen(vlen int) { ...@@ -136,6 +140,10 @@ func (bt *btfType) SetVlen(vlen int) {
bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift) bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift)
} }
func (bt *btfType) KindFlag() bool {
return bt.info(btfTypeKindFlagMask, btfTypeKindFlagShift) == 1
}
func (bt *btfType) Linkage() btfFuncLinkage { func (bt *btfType) Linkage() btfFuncLinkage {
return btfFuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift)) return btfFuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
} }
...@@ -257,3 +265,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) { ...@@ -257,3 +265,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
types = append(types, rawType{header, data}) types = append(types, rawType{header, data})
} }
} }
func intEncoding(raw uint32) (IntEncoding, uint32, byte) {
return IntEncoding((raw & 0x0f000000) >> 24), (raw & 0x00ff0000) >> 16, byte(raw & 0x000000ff)
}
package btf package btf
import ( import (
"bufio"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
...@@ -24,55 +25,82 @@ type btfExtHeader struct { ...@@ -24,55 +25,82 @@ type btfExtHeader struct {
LineInfoLen uint32 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 header btfExtHeader
var coreHeader btfExtCoreHeader
if err := binary.Read(r, bo, &header); err != nil { 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 { 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 { 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 { 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)) remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 { 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 // Of course, the .BTF.ext header has different semantics than the
// .BTF ext header. We need to ignore non-null values. // .BTF ext header. We need to ignore non-null values.
_, err = io.CopyN(ioutil.Discard, r, remainder) _, err = io.CopyN(ioutil.Discard, r, remainder)
if err != nil { 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 { 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)
} }
funcInfo, err = parseExtInfo(io.LimitReader(r, int64(header.FuncInfoLen)), bo, strings) buf := bufio.NewReader(io.LimitReader(r, int64(header.FuncInfoLen)))
funcInfo, err = parseExtInfo(buf, bo, strings)
if err != nil { 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 { 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)
} }
lineInfo, err = parseExtInfo(io.LimitReader(r, int64(header.LineInfoLen)), bo, strings) buf = bufio.NewReader(io.LimitReader(r, int64(header.LineInfoLen)))
lineInfo, err = parseExtInfo(buf, bo, strings)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("line info: %w", err) return nil, nil, nil, fmt.Errorf("line info: %w", err)
}
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)
} }
return funcInfo, lineInfo, nil 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 { type btfExtInfoSec struct {
...@@ -127,6 +155,8 @@ func (ei extInfo) MarshalBinary() ([]byte, error) { ...@@ -127,6 +155,8 @@ func (ei extInfo) MarshalBinary() ([]byte, error) {
} }
func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) { func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) {
const maxRecordSize = 256
var recordSize uint32 var recordSize uint32
if err := binary.Read(r, bo, &recordSize); err != nil { if err := binary.Read(r, bo, &recordSize); err != nil {
return nil, fmt.Errorf("can't read record size: %v", err) return nil, fmt.Errorf("can't read record size: %v", err)
...@@ -136,23 +166,15 @@ func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[st ...@@ -136,23 +166,15 @@ func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[st
// Need at least insnOff // Need at least insnOff
return nil, errors.New("record size too short") return nil, errors.New("record size too short")
} }
if recordSize > maxRecordSize {
return nil, fmt.Errorf("record size %v exceeds %v", recordSize, maxRecordSize)
}
result := make(map[string]extInfo) result := make(map[string]extInfo)
for { for {
var infoHeader btfExtInfoSec secName, infoHeader, err := parseExtInfoHeader(r, bo, strings)
if err := binary.Read(r, bo, &infoHeader); err == io.EOF { if errors.Is(err, io.EOF) {
return result, nil 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 var records []extInfoRecord
...@@ -180,3 +202,80 @@ func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[st ...@@ -180,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
}
// +build gofuzz
// Use with https://github.com/dvyukov/go-fuzz
package btf
import (
"bytes"
"encoding/binary"
"github.com/cilium/ebpf/internal"
)
func FuzzSpec(data []byte) int {
if len(data) < binary.Size(btfHeader{}) {
return -1
}
spec, err := loadNakedSpec(bytes.NewReader(data), internal.NativeEndian, nil, nil)
if err != nil {
if spec != nil {
panic("spec is not nil")
}
return 0
}
if spec == nil {
panic("spec is nil")
}
return 1
}
func FuzzExtInfo(data []byte) int {
if len(data) < binary.Size(btfExtHeader{}) {
return -1
}
table := stringTable("\x00foo\x00barfoo\x00")
info, err := parseExtInfo(bytes.NewReader(data), internal.NativeEndian, table)
if err != nil {
if info != nil {
panic("info is not nil")
}
return 0
}
if info == nil {
panic("info is nil")
}
return 1
}
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
}
...@@ -20,6 +20,9 @@ type UnsupportedFeatureError struct { ...@@ -20,6 +20,9 @@ type UnsupportedFeatureError struct {
} }
func (ufe *UnsupportedFeatureError) Error() string { func (ufe *UnsupportedFeatureError) Error() string {
if ufe.MinimumVersion.Unspecified() {
return fmt.Sprintf("%s not supported", ufe.Name)
}
return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion) return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion)
} }
...@@ -29,7 +32,7 @@ func (ufe *UnsupportedFeatureError) Is(target error) bool { ...@@ -29,7 +32,7 @@ func (ufe *UnsupportedFeatureError) Is(target error) bool {
} }
type featureTest struct { type featureTest struct {
sync.Mutex sync.RWMutex
successful bool successful bool
result error result error
} }
...@@ -39,10 +42,10 @@ type featureTest struct { ...@@ -39,10 +42,10 @@ type featureTest struct {
// //
// The return values have the following semantics: // 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: the test couldn't be executed
// err == nil && available: the feature is available type FeatureTestFn func() error
// err == nil && !available: the feature isn't available
type FeatureTestFn func() (available bool, err error)
// FeatureTest wraps a function so that it is run at most once. // FeatureTest wraps a function so that it is run at most once.
// //
...@@ -58,65 +61,40 @@ func FeatureTest(name, version string, fn FeatureTestFn) func() error { ...@@ -58,65 +61,40 @@ func FeatureTest(name, version string, fn FeatureTestFn) func() error {
ft := new(featureTest) ft := new(featureTest)
return func() error { return func() error {
ft.RLock()
if ft.successful {
defer ft.RUnlock()
return ft.result
}
ft.RUnlock()
ft.Lock() ft.Lock()
defer ft.Unlock() 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 { if ft.successful {
return ft.result return ft.result
} }
err := fn()
available, err := fn() switch {
if errors.Is(err, ErrNotSupported) { case 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 {
ft.result = &UnsupportedFeatureError{ ft.result = &UnsupportedFeatureError{
MinimumVersion: v, MinimumVersion: v,
Name: name, Name: name,
} }
} fallthrough
return ft.result
}
}
// A Version in the form Major.Minor.Patch. case err == nil:
type Version [3]uint16 ft.successful = true
// NewVersion creates a version from a string like "Major.Minor.Patch".
//
// Patch is optional.
func NewVersion(ver string) (Version, error) {
var major, minor, patch uint16
n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch)
if n < 2 {
return Version{}, fmt.Errorf("invalid version: %s", ver)
}
return Version{major, minor, patch}, nil
}
func (v Version) String() string { default:
if v[2] == 0 { // We couldn't execute the feature test to a point
return fmt.Sprintf("v%d.%d", v[0], v[1]) // 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 fmt.Sprintf("v%d.%d.%d", v[0], v[1], v[2])
}
// Less returns true if the version is less than another version. return ft.result
func (v Version) Less(other Version) bool {
for i, a := range v {
if a == other[i] {
continue
}
return a < other[i]
} }
return false
} }
package internal
import (
"errors"
"fmt"
"os"
"github.com/cilium/ebpf/internal/unix"
)
func Pin(currentPath, newPath string, fd *FD) error {
if newPath == "" {
return errors.New("given pinning path cannot be empty")
}
if currentPath == newPath {
return nil
}
if currentPath == "" {
return BPFObjPin(newPath, fd)
}
var err error
// Renameat2 is used instead of os.Rename to disallow the new path replacing
// an existing path.
if err = unix.Renameat2(unix.AT_FDCWD, currentPath, unix.AT_FDCWD, newPath, unix.RENAME_NOREPLACE); err == nil {
// Object is now moved to the new pinning path.
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 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
}
package internal package internal
import "unsafe" import (
"unsafe"
"github.com/cilium/ebpf/internal/unix"
)
// NewPointer creates a 64-bit pointer from an unsafe Pointer. // NewPointer creates a 64-bit pointer from an unsafe Pointer.
func NewPointer(ptr unsafe.Pointer) Pointer { func NewPointer(ptr unsafe.Pointer) Pointer {
...@@ -22,9 +26,10 @@ func NewStringPointer(str string) Pointer { ...@@ -22,9 +26,10 @@ func NewStringPointer(str string) Pointer {
return Pointer{} return Pointer{}
} }
// The kernel expects strings to be zero terminated p, err := unix.BytePtrFromString(str)
buf := make([]byte, len(str)+1) if err != nil {
copy(buf, str) return Pointer{}
}
return Pointer{ptr: unsafe.Pointer(&buf[0])} return Pointer{ptr: unsafe.Pointer(p)}
} }
...@@ -91,6 +91,19 @@ func BPFProgDetach(attr *BPFProgDetachAttr) error { ...@@ -91,6 +91,19 @@ func BPFProgDetach(attr *BPFProgDetachAttr) error {
return err 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 { type bpfObjAttr struct {
fileName Pointer fileName Pointer
fd uint32 fd uint32
...@@ -127,9 +140,10 @@ func BPFObjPin(fileName string, fd *FD) error { ...@@ -127,9 +140,10 @@ func BPFObjPin(fileName string, fd *FD) error {
} }
// BPFObjGet wraps BPF_OBJ_GET. // BPFObjGet wraps BPF_OBJ_GET.
func BPFObjGet(fileName string) (*FD, error) { func BPFObjGet(fileName string, flags uint32) (*FD, error) {
attr := bpfObjAttr{ attr := bpfObjAttr{
fileName: NewStringPointer(fileName), fileName: NewStringPointer(fileName),
fileFlags: flags,
} }
ptr, err := BPF(BPF_OBJ_GET, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) ptr, err := BPF(BPF_OBJ_GET, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil { if err != nil {
...@@ -137,3 +151,30 @@ func BPFObjGet(fileName string) (*FD, error) { ...@@ -137,3 +151,30 @@ func BPFObjGet(fileName string) (*FD, error) {
} }
return NewFD(uint32(ptr)), nil return NewFD(uint32(ptr)), nil
} }
type bpfObjGetInfoByFDAttr struct {
fd uint32
infoLen uint32
info Pointer
}
// BPFObjGetInfoByFD wraps BPF_OBJ_GET_INFO_BY_FD.
//
// Available from 4.13.
func BPFObjGetInfoByFD(fd *FD, info unsafe.Pointer, size uintptr) error {
value, err := fd.Value()
if err != nil {
return err
}
attr := bpfObjGetInfoByFDAttr{
fd: value,
infoLen: uint32(size),
info: NewPointer(info),
}
_, err = BPF(BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return fmt.Errorf("fd %v: %w", fd, err)
}
return nil
}
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
package unix package unix
import ( import (
"bytes"
"syscall" "syscall"
linux "golang.org/x/sys/unix" linux "golang.org/x/sys/unix"
...@@ -19,8 +20,17 @@ const ( ...@@ -19,8 +20,17 @@ const (
EPERM = linux.EPERM EPERM = linux.EPERM
ESRCH = linux.ESRCH ESRCH = linux.ESRCH
ENODEV = linux.ENODEV 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 = linux.BPF_F_RDONLY
BPF_F_WRONLY = linux.BPF_F_WRONLY
BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG
BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG
BPF_F_SLEEPABLE = linux.BPF_F_SLEEPABLE
BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN
BPF_TAG_SIZE = linux.BPF_TAG_SIZE BPF_TAG_SIZE = linux.BPF_TAG_SIZE
SYS_BPF = linux.SYS_BPF SYS_BPF = linux.SYS_BPF
...@@ -33,12 +43,21 @@ const ( ...@@ -33,12 +43,21 @@ const (
PROT_WRITE = linux.PROT_WRITE PROT_WRITE = linux.PROT_WRITE
MAP_SHARED = linux.MAP_SHARED MAP_SHARED = linux.MAP_SHARED
PERF_TYPE_SOFTWARE = linux.PERF_TYPE_SOFTWARE PERF_TYPE_SOFTWARE = linux.PERF_TYPE_SOFTWARE
PERF_TYPE_TRACEPOINT = linux.PERF_TYPE_TRACEPOINT
PERF_COUNT_SW_BPF_OUTPUT = linux.PERF_COUNT_SW_BPF_OUTPUT PERF_COUNT_SW_BPF_OUTPUT = linux.PERF_COUNT_SW_BPF_OUTPUT
PERF_EVENT_IOC_DISABLE = linux.PERF_EVENT_IOC_DISABLE
PERF_EVENT_IOC_ENABLE = linux.PERF_EVENT_IOC_ENABLE
PERF_EVENT_IOC_SET_BPF = linux.PERF_EVENT_IOC_SET_BPF
PerfBitWatermark = linux.PerfBitWatermark PerfBitWatermark = linux.PerfBitWatermark
PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW
PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC
RLIM_INFINITY = linux.RLIM_INFINITY RLIM_INFINITY = linux.RLIM_INFINITY
RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK
BPF_STATS_RUN_TIME = linux.BPF_STATS_RUN_TIME
PERF_RECORD_LOST = linux.PERF_RECORD_LOST
PERF_RECORD_SAMPLE = linux.PERF_RECORD_SAMPLE
AT_FDCWD = linux.AT_FDCWD
RENAME_NOREPLACE = linux.RENAME_NOREPLACE
) )
// Statfs_t is a wrapper // Statfs_t is a wrapper
...@@ -62,6 +81,11 @@ func FcntlInt(fd uintptr, cmd, arg int) (int, error) { ...@@ -62,6 +81,11 @@ func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
return linux.FcntlInt(fd, cmd, arg) return linux.FcntlInt(fd, cmd, arg)
} }
// IoctlSetInt is a wrapper
func IoctlSetInt(fd int, req uint, value int) error {
return linux.IoctlSetInt(fd, req, value)
}
// Statfs is a wrapper // Statfs is a wrapper
func Statfs(path string, buf *Statfs_t) (err error) { func Statfs(path string, buf *Statfs_t) (err error) {
return linux.Statfs(path, buf) return linux.Statfs(path, buf)
...@@ -148,3 +172,30 @@ func Gettid() int { ...@@ -148,3 +172,30 @@ func Gettid() int {
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) { func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
return linux.Tgkill(tgid, tid, sig) return linux.Tgkill(tgid, tid, sig)
} }
// BytePtrFromString is a wrapper
func BytePtrFromString(s string) (*byte, error) {
return linux.BytePtrFromString(s)
}
// ByteSliceToString is a wrapper
func ByteSliceToString(s []byte) string {
return linux.ByteSliceToString(s)
}
// Renameat2 is a wrapper
func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error {
return linux.Renameat2(olddirfd, oldpath, newdirfd, newpath, flags)
}
func KernelRelease() (string, error) {
var uname Utsname
err := Uname(&uname)
if err != nil {
return "", err
}
end := bytes.IndexByte(uname.Release[:], 0)
release := string(uname.Release[:end])
return release, nil
}
...@@ -20,8 +20,17 @@ const ( ...@@ -20,8 +20,17 @@ const (
EPERM = syscall.EPERM EPERM = syscall.EPERM
ESRCH = syscall.ESRCH ESRCH = syscall.ESRCH
ENODEV = syscall.ENODEV 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 = 0
BPF_F_WRONLY = 0
BPF_F_RDONLY_PROG = 0 BPF_F_RDONLY_PROG = 0
BPF_F_WRONLY_PROG = 0 BPF_F_WRONLY_PROG = 0
BPF_F_SLEEPABLE = 0
BPF_OBJ_NAME_LEN = 0x10 BPF_OBJ_NAME_LEN = 0x10
BPF_TAG_SIZE = 0x8 BPF_TAG_SIZE = 0x8
SYS_BPF = 321 SYS_BPF = 321
...@@ -35,12 +44,21 @@ const ( ...@@ -35,12 +44,21 @@ const (
PROT_WRITE = 0x2 PROT_WRITE = 0x2
MAP_SHARED = 0x1 MAP_SHARED = 0x1
PERF_TYPE_SOFTWARE = 0x1 PERF_TYPE_SOFTWARE = 0x1
PERF_TYPE_TRACEPOINT = 0
PERF_COUNT_SW_BPF_OUTPUT = 0xa PERF_COUNT_SW_BPF_OUTPUT = 0xa
PERF_EVENT_IOC_DISABLE = 0
PERF_EVENT_IOC_ENABLE = 0
PERF_EVENT_IOC_SET_BPF = 0
PerfBitWatermark = 0x4000 PerfBitWatermark = 0x4000
PERF_SAMPLE_RAW = 0x400 PERF_SAMPLE_RAW = 0x400
PERF_FLAG_FD_CLOEXEC = 0x8 PERF_FLAG_FD_CLOEXEC = 0x8
RLIM_INFINITY = 0x7fffffffffffffff RLIM_INFINITY = 0x7fffffffffffffff
RLIMIT_MEMLOCK = 8 RLIMIT_MEMLOCK = 8
BPF_STATS_RUN_TIME = 0
PERF_RECORD_LOST = 2
PERF_RECORD_SAMPLE = 9
AT_FDCWD = -0x2
RENAME_NOREPLACE = 0x1
) )
// Statfs_t is a wrapper // Statfs_t is a wrapper
...@@ -80,6 +98,11 @@ func FcntlInt(fd uintptr, cmd, arg int) (int, error) { ...@@ -80,6 +98,11 @@ func FcntlInt(fd uintptr, cmd, arg int) (int, error) {
return -1, errNonLinux return -1, errNonLinux
} }
// IoctlSetInt is a wrapper
func IoctlSetInt(fd int, req uint, value int) error {
return errNonLinux
}
// Statfs is a wrapper // Statfs is a wrapper
func Statfs(path string, buf *Statfs_t) error { func Statfs(path string, buf *Statfs_t) error {
return errNonLinux return errNonLinux
...@@ -194,6 +217,7 @@ func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int ...@@ -194,6 +217,7 @@ func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int
// Utsname is a wrapper // Utsname is a wrapper
type Utsname struct { type Utsname struct {
Release [65]byte Release [65]byte
Version [65]byte
} }
// Uname is a wrapper // Uname is a wrapper
...@@ -215,3 +239,22 @@ func Gettid() int { ...@@ -215,3 +239,22 @@ func Gettid() int {
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) { func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
return errNonLinux return errNonLinux
} }
// BytePtrFromString is a wrapper
func BytePtrFromString(s string) (*byte, error) {
return nil, errNonLinux
}
// ByteSliceToString is a wrapper
func ByteSliceToString(s []byte) string {
return ""
}
// Renameat2 is a wrapper
func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error {
return errNonLinux
}
func KernelRelease() (string, error) {
return "", errNonLinux
}
package internal
import (
"fmt"
"io/ioutil"
"regexp"
"sync"
"github.com/cilium/ebpf/internal/unix"
)
const (
// Version constant used in ELF binaries indicating that the loader needs to
// substitute the eBPF program's version with the value of the kernel's
// KERNEL_VERSION compile-time macro. Used for compatibility with BCC, gobpf
// and RedSift.
MagicKernelVersion = 0xFFFFFFFE
)
var (
// Match between one and three decimals separated by dots, with the last
// segment (patch level) being optional on some kernels.
// The x.y.z string must appear at the start of a string or right after
// whitespace to prevent sequences like 'x.y.z-a.b.c' from matching 'a.b.c'.
rgxKernelVersion = regexp.MustCompile(`(?:\A|\s)\d{1,3}\.\d{1,3}(?:\.\d{1,3})?`)
kernelVersion = struct {
once sync.Once
version Version
err error
}{}
)
// A Version in the form Major.Minor.Patch.
type Version [3]uint16
// NewVersion creates a version from a string like "Major.Minor.Patch".
//
// Patch is optional.
func NewVersion(ver string) (Version, error) {
var major, minor, patch uint16
n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch)
if n < 2 {
return Version{}, fmt.Errorf("invalid version: %s", ver)
}
return Version{major, minor, patch}, nil
}
func (v Version) String() string {
if v[2] == 0 {
return fmt.Sprintf("v%d.%d", v[0], v[1])
}
return fmt.Sprintf("v%d.%d.%d", v[0], v[1], v[2])
}
// Less returns true if the version is less than another version.
func (v Version) Less(other Version) bool {
for i, a := range v {
if a == other[i] {
continue
}
return a < other[i]
}
return false
}
// Unspecified returns true if the version is all zero.
func (v Version) Unspecified() bool {
return v[0] == 0 && v[1] == 0 && v[2] == 0
}
// Kernel implements the kernel's KERNEL_VERSION macro from linux/version.h.
// It represents the kernel version and patch level as a single value.
func (v Version) Kernel() uint32 {
// Kernels 4.4 and 4.9 have their SUBLEVEL clamped to 255 to avoid
// overflowing into PATCHLEVEL.
// See kernel commit 9b82f13e7ef3 ("kbuild: clamp SUBLEVEL to 255").
s := v[2]
if s > 255 {
s = 255
}
// Truncate members to uint8 to prevent them from spilling over into
// each other when overflowing 8 bits.
return uint32(uint8(v[0]))<<16 | uint32(uint8(v[1]))<<8 | uint32(uint8(s))
}
// KernelVersion returns the version of the currently running kernel.
func KernelVersion() (Version, error) {
kernelVersion.once.Do(func() {
kernelVersion.version, kernelVersion.err = detectKernelVersion()
})
if kernelVersion.err != nil {
return Version{}, kernelVersion.err
}
return kernelVersion.version, nil
}
// detectKernelVersion returns the version of the running kernel. It scans the
// following sources in order: /proc/version_signature, uname -v, uname -r.
// In each of those locations, the last-appearing x.y(.z) value is selected
// for parsing. The first location that yields a usable version number is
// returned.
func detectKernelVersion() (Version, error) {
// Try reading /proc/version_signature for Ubuntu compatibility.
// Example format: Ubuntu 4.15.0-91.92-generic 4.15.18
// This method exists in the kernel itself, see d18acd15c
// ("perf tools: Fix kernel version error in ubuntu").
if pvs, err := ioutil.ReadFile("/proc/version_signature"); err == nil {
// If /proc/version_signature exists, failing to parse it is an error.
// It only exists on Ubuntu, where the real patch level is not obtainable
// through any other method.
v, err := findKernelVersion(string(pvs))
if err != nil {
return Version{}, err
}
return v, nil
}
var uname unix.Utsname
if err := unix.Uname(&uname); err != nil {
return Version{}, fmt.Errorf("calling uname: %w", err)
}
// Debian puts the version including the patch level in uname.Version.
// It is not an error if there's no version number in uname.Version,
// as most distributions don't use it. Parsing can continue on uname.Release.
// Example format: #1 SMP Debian 4.19.37-5+deb10u2 (2019-08-08)
if v, err := findKernelVersion(unix.ByteSliceToString(uname.Version[:])); err == nil {
return v, nil
}
// Most other distributions have the full kernel version including patch
// level in uname.Release.
// Example format: 4.19.0-5-amd64, 5.5.10-arch1-1
v, err := findKernelVersion(unix.ByteSliceToString(uname.Release[:]))
if err != nil {
return Version{}, err
}
return v, nil
}
// findKernelVersion matches s against rgxKernelVersion and parses the result
// into a Version. If s contains multiple matches, the last entry is selected.
func findKernelVersion(s string) (Version, error) {
m := rgxKernelVersion.FindAllString(s, -1)
if m == nil {
return Version{}, fmt.Errorf("no kernel version in string: %s", s)
}
// Pick the last match of the string in case there are multiple.
s = m[len(m)-1]
v, err := NewVersion(s)
if err != nil {
return Version{}, fmt.Errorf("parsing version string %s: %w", s, err)
}
return v, nil
}
package link
import (
"errors"
"fmt"
"os"
"github.com/cilium/ebpf"
)
type cgroupAttachFlags uint32
// cgroup attach flags
const (
flagAllowOverride cgroupAttachFlags = 1 << iota
flagAllowMulti
flagReplace
)
type CgroupOptions struct {
// Path to a cgroupv2 folder.
Path string
// One of the AttachCgroup* constants
Attach ebpf.AttachType
// Program must be of type CGroup*, and the attach type must match Attach.
Program *ebpf.Program
}
// AttachCgroup links a BPF program to a cgroup.
func AttachCgroup(opts CgroupOptions) (Link, error) {
cgroup, err := os.Open(opts.Path)
if err != nil {
return nil, fmt.Errorf("can't open cgroup: %s", err)
}
clone, err := opts.Program.Clone()
if err != nil {
cgroup.Close()
return nil, err
}
var cg Link
cg, err = newLinkCgroup(cgroup, opts.Attach, clone)
if errors.Is(err, ErrNotSupported) {
cg, err = newProgAttachCgroup(cgroup, opts.Attach, clone, flagAllowMulti)
}
if errors.Is(err, ErrNotSupported) {
cg, err = newProgAttachCgroup(cgroup, opts.Attach, clone, flagAllowOverride)
}
if err != nil {
cgroup.Close()
clone.Close()
return nil, err
}
return cg, nil
}
// LoadPinnedCgroup loads a pinned cgroup from a bpffs.
func LoadPinnedCgroup(fileName string, opts *ebpf.LoadPinOptions) (Link, error) {
link, err := LoadPinnedRawLink(fileName, CgroupType, opts)
if err != nil {
return nil, err
}
return &linkCgroup{*link}, nil
}
type progAttachCgroup struct {
cgroup *os.File
current *ebpf.Program
attachType ebpf.AttachType
flags cgroupAttachFlags
}
var _ Link = (*progAttachCgroup)(nil)
func (cg *progAttachCgroup) isLink() {}
func newProgAttachCgroup(cgroup *os.File, attach ebpf.AttachType, prog *ebpf.Program, flags cgroupAttachFlags) (*progAttachCgroup, error) {
if flags&flagAllowMulti > 0 {
if err := haveProgAttachReplace(); err != nil {
return nil, fmt.Errorf("can't support multiple programs: %w", err)
}
}
err := RawAttachProgram(RawAttachProgramOptions{
Target: int(cgroup.Fd()),
Program: prog,
Flags: uint32(flags),
Attach: attach,
})
if err != nil {
return nil, fmt.Errorf("cgroup: %w", err)
}
return &progAttachCgroup{cgroup, prog, attach, flags}, nil
}
func (cg *progAttachCgroup) Close() error {
defer cg.cgroup.Close()
defer cg.current.Close()
err := RawDetachProgram(RawDetachProgramOptions{
Target: int(cg.cgroup.Fd()),
Program: cg.current,
Attach: cg.attachType,
})
if err != nil {
return fmt.Errorf("close cgroup: %s", err)
}
return nil
}
func (cg *progAttachCgroup) Update(prog *ebpf.Program) error {
new, err := prog.Clone()
if err != nil {
return err
}
args := RawAttachProgramOptions{
Target: int(cg.cgroup.Fd()),
Program: prog,
Attach: cg.attachType,
Flags: uint32(cg.flags),
}
if cg.flags&flagAllowMulti > 0 {
// Atomically replacing multiple programs requires at least
// 5.5 (commit 7dd68b3279f17921 "bpf: Support replacing cgroup-bpf
// program in MULTI mode")
args.Flags |= uint32(flagReplace)
args.Replace = cg.current
}
if err := RawAttachProgram(args); err != nil {
new.Close()
return fmt.Errorf("can't update cgroup: %s", err)
}
cg.current.Close()
cg.current = new
return nil
}
func (cg *progAttachCgroup) Pin(string) error {
return fmt.Errorf("can't pin cgroup: %w", ErrNotSupported)
}
func (cg *progAttachCgroup) Unpin() error {
return fmt.Errorf("can't pin cgroup: %w", ErrNotSupported)
}
type linkCgroup struct {
RawLink
}
var _ Link = (*linkCgroup)(nil)
func newLinkCgroup(cgroup *os.File, attach ebpf.AttachType, prog *ebpf.Program) (*linkCgroup, error) {
link, err := AttachRawLink(RawLinkOptions{
Target: int(cgroup.Fd()),
Program: prog,
Attach: attach,
})
if err != nil {
return nil, err
}
return &linkCgroup{*link}, err
}
// Package link allows attaching eBPF programs to various kernel hooks.
package link
package link
import (
"fmt"
"io"
"github.com/cilium/ebpf"
)
type IterOptions struct {
// Program must be of type Tracing with attach type
// AttachTraceIter. The kind of iterator to attach to is
// determined at load time via the AttachTo field.
//
// AttachTo requires the kernel to include BTF of itself,
// and it to be compiled with a recent pahole (>= 1.16).
Program *ebpf.Program
}
// AttachIter attaches a BPF seq_file iterator.
func AttachIter(opts IterOptions) (*Iter, error) {
link, err := AttachRawLink(RawLinkOptions{
Program: opts.Program,
Attach: ebpf.AttachTraceIter,
})
if err != nil {
return nil, fmt.Errorf("can't link iterator: %w", err)
}
return &Iter{*link}, err
}
// LoadPinnedIter loads a pinned iterator from a bpffs.
func LoadPinnedIter(fileName string, opts *ebpf.LoadPinOptions) (*Iter, error) {
link, err := LoadPinnedRawLink(fileName, IterType, opts)
if err != nil {
return nil, err
}
return &Iter{*link}, err
}
// Iter represents an attached bpf_iter.
type Iter struct {
RawLink
}
// Open creates a new instance of the iterator.
//
// Reading from the returned reader triggers the BPF program.
func (it *Iter) Open() (io.ReadCloser, error) {
linkFd, err := it.fd.Value()
if err != nil {
return nil, err
}
attr := &bpfIterCreateAttr{
linkFd: linkFd,
}
fd, err := bpfIterCreate(attr)
if err != nil {
return nil, fmt.Errorf("can't create iterator: %w", err)
}
return fd.File("bpf_iter"), nil
}
package link
import (
"crypto/rand"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/unix"
)
var (
kprobeEventsPath = filepath.Join(tracefsPath, "kprobe_events")
)
// Kprobe attaches the given eBPF program to a perf event that fires when the
// given kernel symbol starts executing. See /proc/kallsyms for available
// symbols. For example, printk():
//
// Kprobe("printk")
//
// The resulting Link must be Closed during program shutdown to avoid leaking
// system resources.
func Kprobe(symbol string, prog *ebpf.Program) (Link, error) {
k, err := kprobe(symbol, prog, false)
if err != nil {
return nil, err
}
err = k.attach(prog)
if err != nil {
k.Close()
return nil, err
}
return k, nil
}
// Kretprobe attaches the given eBPF program to a perf event that fires right
// before the given kernel symbol exits, with the function stack left intact.
// See /proc/kallsyms for available symbols. For example, printk():
//
// Kretprobe("printk")
//
// The resulting Link must be Closed during program shutdown to avoid leaking
// system resources.
func Kretprobe(symbol string, prog *ebpf.Program) (Link, error) {
k, err := kprobe(symbol, prog, true)
if err != nil {
return nil, err
}
err = k.attach(prog)
if err != nil {
k.Close()
return nil, err
}
return k, nil
}
// kprobe opens a perf event on the given symbol and attaches prog to it.
// If ret is true, create a kretprobe.
func kprobe(symbol string, prog *ebpf.Program, ret bool) (*perfEvent, error) {
if symbol == "" {
return nil, fmt.Errorf("symbol name cannot be empty: %w", errInvalidInput)
}
if prog == nil {
return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput)
}
if !rgxTraceEvent.MatchString(symbol) {
return nil, fmt.Errorf("symbol '%s' must be alphanumeric or underscore: %w", symbol, errInvalidInput)
}
if prog.Type() != ebpf.Kprobe {
return nil, fmt.Errorf("eBPF program type %s is not a Kprobe: %w", prog.Type(), errInvalidInput)
}
// Use kprobe PMU if the kernel has it available.
tp, err := pmuKprobe(symbol, ret)
if err == nil {
return tp, nil
}
if err != nil && !errors.Is(err, ErrNotSupported) {
return nil, fmt.Errorf("creating perf_kprobe PMU: %w", err)
}
// Use tracefs if kprobe PMU is missing.
tp, err = tracefsKprobe(symbol, ret)
if err != nil {
return nil, fmt.Errorf("creating trace event '%s' in tracefs: %w", symbol, err)
}
return tp, nil
}
// pmuKprobe opens a perf event based on a Performance Monitoring Unit.
// Requires at least 4.17 (e12f03d7031a "perf/core: Implement the
// 'perf_kprobe' PMU").
// Returns ErrNotSupported if the kernel doesn't support perf_kprobe PMU,
// or os.ErrNotExist if the given symbol does not exist in the kernel.
func pmuKprobe(symbol string, ret bool) (*perfEvent, error) {
// Getting the PMU type will fail if the kernel doesn't support
// the perf_kprobe PMU.
et, err := getPMUEventType("kprobe")
if err != nil {
return nil, err
}
// Create a pointer to a NUL-terminated string for the kernel.
sp, err := unsafeStringPtr(symbol)
if err != nil {
return nil, err
}
// TODO: Parse the position of the bit from /sys/bus/event_source/devices/%s/format/retprobe.
config := 0
if ret {
config = 1
}
attr := unix.PerfEventAttr{
Type: uint32(et), // PMU event type read from sysfs
Ext1: uint64(uintptr(sp)), // Kernel symbol to trace
Config: uint64(config), // perf_kprobe PMU treats config as flags
}
fd, err := unix.PerfEventOpen(&attr, perfAllThreads, 0, -1, unix.PERF_FLAG_FD_CLOEXEC)
// Since commit 97c753e62e6c, ENOENT is correctly returned instead of EINVAL
// when trying to create a kretprobe for a missing symbol. Make sure ENOENT
// is returned to the caller.
if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
return nil, fmt.Errorf("symbol '%s' not found: %w", symbol, os.ErrNotExist)
}
if err != nil {
return nil, fmt.Errorf("opening perf event: %w", err)
}
// Ensure the string pointer is not collected before PerfEventOpen returns.
runtime.KeepAlive(sp)
// Kernel has perf_kprobe PMU available, initialize perf event.
return &perfEvent{
fd: internal.NewFD(uint32(fd)),
pmuID: et,
name: symbol,
ret: ret,
progType: ebpf.Kprobe,
}, nil
}
// tracefsKprobe creates a trace event by writing an entry to <tracefs>/kprobe_events.
// A new trace event group name is generated on every call to support creating
// multiple trace events for the same kernel symbol. A perf event is then opened
// on the newly-created trace event and returned to the caller.
func tracefsKprobe(symbol string, ret bool) (*perfEvent, error) {
// Generate a random string for each trace event we attempt to create.
// This value is used as the 'group' token in tracefs to allow creating
// multiple kprobe trace events with the same name.
group, err := randomGroup("ebpf")
if err != nil {
return nil, fmt.Errorf("randomizing group name: %w", err)
}
// Before attempting to create a trace event through tracefs,
// check if an event with the same group and name already exists.
// Kernels 4.x and earlier don't return os.ErrExist on writing a duplicate
// entry, so we need to rely on reads for detecting uniqueness.
_, err = getTraceEventID(group, symbol)
if err == nil {
return nil, fmt.Errorf("trace event already exists: %s/%s", group, symbol)
}
// The read is expected to fail with ErrNotSupported due to a non-existing event.
if err != nil && !errors.Is(err, ErrNotSupported) {
return nil, fmt.Errorf("checking trace event %s/%s: %w", group, symbol, err)
}
// Create the kprobe trace event using tracefs.
if err := createTraceFSKprobeEvent(group, symbol, ret); err != nil {
return nil, fmt.Errorf("creating kprobe event on tracefs: %w", err)
}
// Get the newly-created trace event's id.
tid, err := getTraceEventID(group, symbol)
if err != nil {
return nil, fmt.Errorf("getting trace event id: %w", err)
}
// Kprobes are ephemeral tracepoints and share the same perf event type.
fd, err := openTracepointPerfEvent(tid)
if err != nil {
return nil, err
}
return &perfEvent{
fd: fd,
group: group,
name: symbol,
ret: ret,
tracefsID: tid,
progType: ebpf.Kprobe, // kernel only allows attaching kprobe programs to kprobe events
}, nil
}
// createTraceFSKprobeEvent creates a new ephemeral trace event by writing to
// <tracefs>/kprobe_events. Returns ErrNotSupported if symbol is not a valid
// kernel symbol, or if it is not traceable with kprobes.
func createTraceFSKprobeEvent(group, symbol string, ret bool) error {
// Open the kprobe_events file in tracefs.
f, err := os.OpenFile(kprobeEventsPath, os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
return fmt.Errorf("error opening kprobe_events: %w", err)
}
defer f.Close()
// The kprobe_events syntax is as follows (see Documentation/trace/kprobetrace.txt):
// p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : Set a probe
// r[MAXACTIVE][:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] : Set a return probe
// -:[GRP/]EVENT : Clear a probe
//
// Some examples:
// r:ebpf_1234/r_my_kretprobe nf_conntrack_destroy
// p:ebpf_5678/p_my_kprobe __x64_sys_execve
//
// Leaving the kretprobe's MAXACTIVE set to 0 (or absent) will make the
// kernel default to NR_CPUS. This is desired in most eBPF cases since
// subsampling or rate limiting logic can be more accurately implemented in
// the eBPF program itself. See Documentation/kprobes.txt for more details.
pe := fmt.Sprintf("%s:%s/%s %s", kprobePrefix(ret), group, symbol, symbol)
_, err = f.WriteString(pe)
// Since commit 97c753e62e6c, ENOENT is correctly returned instead of EINVAL
// when trying to create a kretprobe for a missing symbol. Make sure ENOENT
// is returned to the caller.
if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) {
return fmt.Errorf("kernel symbol %s not found: %w", symbol, os.ErrNotExist)
}
if err != nil {
return fmt.Errorf("writing '%s' to kprobe_events: %w", pe, err)
}
return nil
}
// closeTraceFSKprobeEvent removes the kprobe with the given group, symbol and kind
// from <tracefs>/kprobe_events.
func closeTraceFSKprobeEvent(group, symbol string) error {
f, err := os.OpenFile(kprobeEventsPath, os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
return fmt.Errorf("error opening kprobe_events: %w", err)
}
defer f.Close()
// See kprobe_events syntax above. Kprobe type does not need to be specified
// for removals.
pe := fmt.Sprintf("-:%s/%s", group, symbol)
if _, err = f.WriteString(pe); err != nil {
return fmt.Errorf("writing '%s' to kprobe_events: %w", pe, err)
}
return nil
}
// randomGroup generates a pseudorandom string for use as a tracefs group name.
// Returns an error when the output string would exceed 63 characters (kernel
// limitation), when rand.Read() fails or when prefix contains characters not
// allowed by rgxTraceEvent.
func randomGroup(prefix string) (string, error) {
if !rgxTraceEvent.MatchString(prefix) {
return "", fmt.Errorf("prefix '%s' must be alphanumeric or underscore: %w", prefix, errInvalidInput)
}
b := make([]byte, 8)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("reading random bytes: %w", err)
}
group := fmt.Sprintf("%s_%x", prefix, b)
if len(group) > 63 {
return "", fmt.Errorf("group name '%s' cannot be longer than 63 characters: %w", group, errInvalidInput)
}
return group, nil
}
func kprobePrefix(ret bool) string {
if ret {
return "r"
}
return "p"
}
package link
import (
"fmt"
"unsafe"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
)
var ErrNotSupported = internal.ErrNotSupported
// Link represents a Program attached to a BPF hook.
type Link interface {
// Replace the current program with a new program.
//
// Passing a nil program is an error. May return an error wrapping ErrNotSupported.
Update(*ebpf.Program) error
// Persist a link by pinning it into a bpffs.
//
// May return an error wrapping ErrNotSupported.
Pin(string) error
// Undo a previous call to Pin.
//
// May return an error wrapping ErrNotSupported.
Unpin() error
// Close frees resources.
//
// The link will be broken unless it has been pinned. A link
// may continue past the lifetime of the process if Close is
// not called.
Close() error
// Prevent external users from implementing this interface.
isLink()
}
// ID uniquely identifies a BPF link.
type ID uint32
// RawLinkOptions control the creation of a raw link.
type RawLinkOptions struct {
// File descriptor to attach to. This differs for each attach type.
Target int
// Program to attach.
Program *ebpf.Program
// Attach must match the attach type of Program.
Attach ebpf.AttachType
}
// RawLinkInfo contains metadata on a link.
type RawLinkInfo struct {
Type Type
ID ID
Program ebpf.ProgramID
}
// RawLink is the low-level API to bpf_link.
//
// You should consider using the higher level interfaces in this
// package instead.
type RawLink struct {
fd *internal.FD
pinnedPath string
}
// AttachRawLink creates a raw link.
func AttachRawLink(opts RawLinkOptions) (*RawLink, error) {
if err := haveBPFLink(); err != nil {
return nil, err
}
if opts.Target < 0 {
return nil, fmt.Errorf("invalid target: %s", internal.ErrClosedFd)
}
progFd := opts.Program.FD()
if progFd < 0 {
return nil, fmt.Errorf("invalid program: %s", internal.ErrClosedFd)
}
attr := bpfLinkCreateAttr{
targetFd: uint32(opts.Target),
progFd: uint32(progFd),
attachType: opts.Attach,
}
fd, err := bpfLinkCreate(&attr)
if err != nil {
return nil, fmt.Errorf("can't create link: %s", err)
}
return &RawLink{fd, ""}, nil
}
// LoadPinnedRawLink loads a persisted link from a bpffs.
//
// Returns an error if the pinned link type doesn't match linkType. Pass
// UnspecifiedType to disable this behaviour.
func LoadPinnedRawLink(fileName string, linkType Type, opts *ebpf.LoadPinOptions) (*RawLink, error) {
fd, err := internal.BPFObjGet(fileName, opts.Marshal())
if err != nil {
return nil, fmt.Errorf("load pinned link: %w", err)
}
link := &RawLink{fd, fileName}
if linkType == UnspecifiedType {
return link, nil
}
info, err := link.Info()
if err != nil {
link.Close()
return nil, fmt.Errorf("get pinned link info: %s", err)
}
if info.Type != linkType {
link.Close()
return nil, fmt.Errorf("link type %v doesn't match %v", info.Type, linkType)
}
return link, nil
}
func (l *RawLink) isLink() {}
// FD returns the raw file descriptor.
func (l *RawLink) FD() int {
fd, err := l.fd.Value()
if err != nil {
return -1
}
return int(fd)
}
// Close breaks the link.
//
// Use Pin if you want to make the link persistent.
func (l *RawLink) Close() error {
return l.fd.Close()
}
// Pin persists a link past the lifetime of the process.
//
// Calling Close on a pinned Link will not break the link
// until the pin is removed.
func (l *RawLink) Pin(fileName string) error {
if err := internal.Pin(l.pinnedPath, fileName, l.fd); err != nil {
return err
}
l.pinnedPath = fileName
return nil
}
// Unpin implements the Link interface.
func (l *RawLink) Unpin() error {
if err := internal.Unpin(l.pinnedPath); err != nil {
return err
}
l.pinnedPath = ""
return nil
}
// Update implements the Link interface.
func (l *RawLink) Update(new *ebpf.Program) error {
return l.UpdateArgs(RawLinkUpdateOptions{
New: new,
})
}
// RawLinkUpdateOptions control the behaviour of RawLink.UpdateArgs.
type RawLinkUpdateOptions struct {
New *ebpf.Program
Old *ebpf.Program
Flags uint32
}
// UpdateArgs updates a link based on args.
func (l *RawLink) UpdateArgs(opts RawLinkUpdateOptions) error {
newFd := opts.New.FD()
if newFd < 0 {
return fmt.Errorf("invalid program: %s", internal.ErrClosedFd)
}
var oldFd int
if opts.Old != nil {
oldFd = opts.Old.FD()
if oldFd < 0 {
return fmt.Errorf("invalid replacement program: %s", internal.ErrClosedFd)
}
}
linkFd, err := l.fd.Value()
if err != nil {
return fmt.Errorf("can't update link: %s", err)
}
attr := bpfLinkUpdateAttr{
linkFd: linkFd,
newProgFd: uint32(newFd),
oldProgFd: uint32(oldFd),
flags: opts.Flags,
}
return bpfLinkUpdate(&attr)
}
// struct bpf_link_info
type bpfLinkInfo struct {
typ uint32
id uint32
prog_id uint32
}
// Info returns metadata about the link.
func (l *RawLink) Info() (*RawLinkInfo, error) {
var info bpfLinkInfo
err := internal.BPFObjGetInfoByFD(l.fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
if err != nil {
return nil, fmt.Errorf("link info: %s", err)
}
return &RawLinkInfo{
Type(info.typ),
ID(info.id),
ebpf.ProgramID(info.prog_id),
}, nil
}
package link
import (
"fmt"
"github.com/cilium/ebpf"
)
// NetNsInfo contains metadata about a network namespace link.
type NetNsInfo struct {
RawLinkInfo
}
// NetNsLink is a program attached to a network namespace.
type NetNsLink struct {
*RawLink
}
// AttachNetNs attaches a program to a network namespace.
func AttachNetNs(ns int, prog *ebpf.Program) (*NetNsLink, error) {
var attach ebpf.AttachType
switch t := prog.Type(); t {
case ebpf.FlowDissector:
attach = ebpf.AttachFlowDissector
case ebpf.SkLookup:
attach = ebpf.AttachSkLookup
default:
return nil, fmt.Errorf("can't attach %v to network namespace", t)
}
link, err := AttachRawLink(RawLinkOptions{
Target: ns,
Program: prog,
Attach: attach,
})
if err != nil {
return nil, err
}
return &NetNsLink{link}, nil
}
// LoadPinnedNetNs loads a network namespace link from bpffs.
func LoadPinnedNetNs(fileName string, opts *ebpf.LoadPinOptions) (*NetNsLink, error) {
link, err := LoadPinnedRawLink(fileName, NetNsType, opts)
if err != nil {
return nil, err
}
return &NetNsLink{link}, nil
}
// Info returns information about the link.
func (nns *NetNsLink) Info() (*NetNsInfo, error) {
info, err := nns.RawLink.Info()
if err != nil {
return nil, err
}
return &NetNsInfo{*info}, nil
}
package link
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"unsafe"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/unix"
)
// Getting the terminology right is usually the hardest part. For posterity and
// for staying sane during implementation:
//
// - trace event: Representation of a kernel runtime hook. Filesystem entries
// under <tracefs>/events. Can be tracepoints (static), kprobes or uprobes.
// Can be instantiated into perf events (see below).
// - tracepoint: A predetermined hook point in the kernel. Exposed as trace
// events in (sub)directories under <tracefs>/events. Cannot be closed or
// removed, they are static.
// - k(ret)probe: Ephemeral trace events based on entry or exit points of
// exported kernel symbols. kprobe-based (tracefs) trace events can be
// created system-wide by writing to the <tracefs>/kprobe_events file, or
// they can be scoped to the current process by creating PMU perf events.
// - perf event: An object instantiated based on an existing trace event or
// kernel symbol. Referred to by fd in userspace.
// Exactly one eBPF program can be attached to a perf event. Multiple perf
// events can be created from a single trace event. Closing a perf event
// stops any further invocations of the attached eBPF program.
var (
tracefsPath = "/sys/kernel/debug/tracing"
// Trace event groups, names and kernel symbols must adhere to this set
// of characters. Non-empty, first character must not be a number, all
// characters must be alphanumeric or underscore.
rgxTraceEvent = regexp.MustCompile("^[a-zA-Z_][0-9a-zA-Z_]*$")
errInvalidInput = errors.New("invalid input")
)
const (
perfAllThreads = -1
)
// A perfEvent represents a perf event kernel object. Exactly one eBPF program
// can be attached to it. It is created based on a tracefs trace event or a
// Performance Monitoring Unit (PMU).
type perfEvent struct {
// Group and name of the tracepoint/kprobe/uprobe.
group string
name string
// PMU event ID read from sysfs. Valid IDs are non-zero.
pmuID uint64
// ID of the trace event read from tracefs. Valid IDs are non-zero.
tracefsID uint64
// True for kretprobes/uretprobes.
ret bool
fd *internal.FD
progType ebpf.ProgramType
}
func (pe *perfEvent) isLink() {}
func (pe *perfEvent) Pin(string) error {
return fmt.Errorf("pin perf event: %w", ErrNotSupported)
}
func (pe *perfEvent) Unpin() error {
return fmt.Errorf("unpin perf event: %w", ErrNotSupported)
}
// Since 4.15 (e87c6bc3852b "bpf: permit multiple bpf attachments for a single perf event"),
// calling PERF_EVENT_IOC_SET_BPF appends the given program to a prog_array
// owned by the perf event, which means multiple programs can be attached
// simultaneously.
//
// Before 4.15, calling PERF_EVENT_IOC_SET_BPF more than once on a perf event
// returns EEXIST.
//
// Detaching a program from a perf event is currently not possible, so a
// program replacement mechanism cannot be implemented for perf events.
func (pe *perfEvent) Update(prog *ebpf.Program) error {
return fmt.Errorf("can't replace eBPF program in perf event: %w", ErrNotSupported)
}
func (pe *perfEvent) Close() error {
if pe.fd == nil {
return nil
}
pfd, err := pe.fd.Value()
if err != nil {
return fmt.Errorf("getting perf event fd: %w", err)
}
err = unix.IoctlSetInt(int(pfd), unix.PERF_EVENT_IOC_DISABLE, 0)
if err != nil {
return fmt.Errorf("disabling perf event: %w", err)
}
err = pe.fd.Close()
if err != nil {
return fmt.Errorf("closing perf event fd: %w", err)
}
switch t := pe.progType; t {
case ebpf.Kprobe:
// For kprobes created using tracefs, clean up the <tracefs>/kprobe_events entry.
if pe.tracefsID != 0 {
return closeTraceFSKprobeEvent(pe.group, pe.name)
}
case ebpf.TracePoint:
// Tracepoint trace events don't hold any extra resources.
return nil
}
return nil
}
// attach the given eBPF prog to the perf event stored in pe.
// pe must contain a valid perf event fd.
// prog's type must match the program type stored in pe.
func (pe *perfEvent) attach(prog *ebpf.Program) error {
if prog == nil {
return errors.New("cannot attach a nil program")
}
if pe.fd == nil {
return errors.New("cannot attach to nil perf event")
}
if t := prog.Type(); t != pe.progType {
return fmt.Errorf("invalid program type (expected %s): %s", pe.progType, t)
}
if prog.FD() < 0 {
return fmt.Errorf("invalid program: %w", internal.ErrClosedFd)
}
// The ioctl below will fail when the fd is invalid.
kfd, _ := pe.fd.Value()
// Assign the eBPF program to the perf event.
err := unix.IoctlSetInt(int(kfd), unix.PERF_EVENT_IOC_SET_BPF, prog.FD())
if err != nil {
return fmt.Errorf("setting perf event bpf program: %w", err)
}
// PERF_EVENT_IOC_ENABLE and _DISABLE ignore their given values.
if err := unix.IoctlSetInt(int(kfd), unix.PERF_EVENT_IOC_ENABLE, 0); err != nil {
return fmt.Errorf("enable perf event: %s", err)
}
// Close the perf event when its reference is lost to avoid leaking system resources.
runtime.SetFinalizer(pe, (*perfEvent).Close)
return nil
}
// unsafeStringPtr returns an unsafe.Pointer to a NUL-terminated copy of str.
func unsafeStringPtr(str string) (unsafe.Pointer, error) {
p, err := unix.BytePtrFromString(str)
if err != nil {
return nil, err
}
return unsafe.Pointer(p), nil
}
// getTraceEventID reads a trace event's ID from tracefs given its group and name.
// group and name must be alphanumeric or underscore, as required by the kernel.
func getTraceEventID(group, name string) (uint64, error) {
tid, err := uint64FromFile(tracefsPath, "events", group, name, "id")
if errors.Is(err, ErrNotSupported) {
return 0, fmt.Errorf("trace event %s/%s: %w", group, name, ErrNotSupported)
}
if err != nil {
return 0, fmt.Errorf("reading trace event ID of %s/%s: %w", group, name, err)
}
return tid, nil
}
// getPMUEventType reads a Performance Monitoring Unit's type (numeric identifier)
// from /sys/bus/event_source/devices/<pmu>/type.
func getPMUEventType(pmu string) (uint64, error) {
et, err := uint64FromFile("/sys/bus/event_source/devices", pmu, "type")
if errors.Is(err, ErrNotSupported) {
return 0, fmt.Errorf("pmu type %s: %w", pmu, ErrNotSupported)
}
if err != nil {
return 0, fmt.Errorf("reading pmu type %s: %w", pmu, err)
}
return et, nil
}
// openTracepointPerfEvent opens a tracepoint-type perf event. System-wide
// kprobes created by writing to <tracefs>/kprobe_events are tracepoints
// behind the scenes, and can be attached to using these perf events.
func openTracepointPerfEvent(tid uint64) (*internal.FD, error) {
attr := unix.PerfEventAttr{
Type: unix.PERF_TYPE_TRACEPOINT,
Config: tid,
Sample_type: unix.PERF_SAMPLE_RAW,
Sample: 1,
Wakeup: 1,
}
fd, err := unix.PerfEventOpen(&attr, perfAllThreads, 0, -1, unix.PERF_FLAG_FD_CLOEXEC)
if err != nil {
return nil, fmt.Errorf("opening tracepoint perf event: %w", err)
}
return internal.NewFD(uint32(fd)), nil
}
// uint64FromFile reads a uint64 from a file. All elements of path are sanitized
// and joined onto base. Returns error if base no longer prefixes the path after
// joining all components.
func uint64FromFile(base string, path ...string) (uint64, error) {
// Resolve leaf path separately for error feedback. Makes the join onto
// base more readable (can't mix with variadic args).
l := filepath.Join(path...)
p := filepath.Join(base, l)
if !strings.HasPrefix(p, base) {
return 0, fmt.Errorf("path '%s' attempts to escape base path '%s': %w", l, base, errInvalidInput)
}
data, err := ioutil.ReadFile(p)
if os.IsNotExist(err) {
// Only echo leaf path, the base path can be prepended at the call site
// if more verbosity is required.
return 0, fmt.Errorf("symbol %s: %w", l, ErrNotSupported)
}
if err != nil {
return 0, fmt.Errorf("reading file %s: %w", p, err)
}
et := bytes.TrimSpace(data)
return strconv.ParseUint(string(et), 10, 64)
}
package link
import (
"fmt"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
)
type RawAttachProgramOptions struct {
// File descriptor to attach to. This differs for each attach type.
Target int
// Program to attach.
Program *ebpf.Program
// Program to replace (cgroups).
Replace *ebpf.Program
// Attach must match the attach type of Program (and Replace).
Attach ebpf.AttachType
// Flags control the attach behaviour. This differs for each attach type.
Flags uint32
}
// RawAttachProgram is a low level wrapper around BPF_PROG_ATTACH.
//
// You should use one of the higher level abstractions available in this
// package if possible.
func RawAttachProgram(opts RawAttachProgramOptions) error {
if err := haveProgAttach(); err != nil {
return err
}
var replaceFd uint32
if opts.Replace != nil {
replaceFd = uint32(opts.Replace.FD())
}
attr := internal.BPFProgAttachAttr{
TargetFd: uint32(opts.Target),
AttachBpfFd: uint32(opts.Program.FD()),
ReplaceBpfFd: replaceFd,
AttachType: uint32(opts.Attach),
AttachFlags: uint32(opts.Flags),
}
if err := internal.BPFProgAttach(&attr); err != nil {
return fmt.Errorf("can't attach program: %s", err)
}
return nil
}
type RawDetachProgramOptions struct {
Target int
Program *ebpf.Program
Attach ebpf.AttachType
}
// RawDetachProgram is a low level wrapper around BPF_PROG_DETACH.
//
// You should use one of the higher level abstractions available in this
// package if possible.
func RawDetachProgram(opts RawDetachProgramOptions) error {
if err := haveProgAttach(); err != nil {
return err
}
attr := internal.BPFProgDetachAttr{
TargetFd: uint32(opts.Target),
AttachBpfFd: uint32(opts.Program.FD()),
AttachType: uint32(opts.Attach),
}
if err := internal.BPFProgDetach(&attr); err != nil {
return fmt.Errorf("can't detach program: %s", err)
}
return nil
}
package link
import (
"fmt"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/internal"
)
type RawTracepointOptions struct {
// Tracepoint name.
Name string
// Program must be of type RawTracepoint*
Program *ebpf.Program
}
// AttachRawTracepoint links a BPF program to a raw_tracepoint.
//
// Requires at least Linux 4.17.
func AttachRawTracepoint(opts RawTracepointOptions) (Link, error) {
if t := opts.Program.Type(); t != ebpf.RawTracepoint && t != ebpf.RawTracepointWritable {
return nil, fmt.Errorf("invalid program type %s, expected RawTracepoint(Writable)", t)
}
if opts.Program.FD() < 0 {
return nil, fmt.Errorf("invalid program: %w", internal.ErrClosedFd)
}
fd, err := bpfRawTracepointOpen(&bpfRawTracepointOpenAttr{
name: internal.NewStringPointer(opts.Name),
fd: uint32(opts.Program.FD()),
})
if err != nil {
return nil, err
}
return &progAttachRawTracepoint{fd: fd}, nil
}
type progAttachRawTracepoint struct {
fd *internal.FD
}
var _ Link = (*progAttachRawTracepoint)(nil)
func (rt *progAttachRawTracepoint) isLink() {}
func (rt *progAttachRawTracepoint) Close() error {
return rt.fd.Close()
}
func (rt *progAttachRawTracepoint) Update(_ *ebpf.Program) error {
return fmt.Errorf("can't update raw_tracepoint: %w", ErrNotSupported)
}
func (rt *progAttachRawTracepoint) Pin(_ string) error {
return fmt.Errorf("can't pin raw_tracepoint: %w", ErrNotSupported)
}
func (rt *progAttachRawTracepoint) Unpin() error {
return fmt.Errorf("unpin raw_tracepoint: %w", ErrNotSupported)
}
package link
import (
"errors"
"unsafe"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/unix"
)
// Type is the kind of link.
type Type uint32
// Valid link types.
//
// Equivalent to enum bpf_link_type.
const (
UnspecifiedType Type = iota
RawTracepointType
TracingType
CgroupType
IterType
NetNsType
XDPType
)
var haveProgAttach = internal.FeatureTest("BPF_PROG_ATTACH", "4.10", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Type: ebpf.CGroupSKB,
AttachType: ebpf.AttachCGroupInetIngress,
License: "MIT",
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
},
})
if err != nil {
return internal.ErrNotSupported
}
// BPF_PROG_ATTACH was introduced at the same time as CGgroupSKB,
// so being able to load the program is enough to infer that we
// have the syscall.
prog.Close()
return nil
})
var haveProgAttachReplace = internal.FeatureTest("BPF_PROG_ATTACH atomic replacement", "5.5", func() error {
if err := haveProgAttach(); err != nil {
return err
}
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Type: ebpf.CGroupSKB,
AttachType: ebpf.AttachCGroupInetIngress,
License: "MIT",
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
},
})
if err != nil {
return internal.ErrNotSupported
}
defer prog.Close()
// We know that we have BPF_PROG_ATTACH since we can load CGroupSKB programs.
// If passing BPF_F_REPLACE gives us EINVAL we know that the feature isn't
// present.
attr := internal.BPFProgAttachAttr{
// We rely on this being checked after attachFlags.
TargetFd: ^uint32(0),
AttachBpfFd: uint32(prog.FD()),
AttachType: uint32(ebpf.AttachCGroupInetIngress),
AttachFlags: uint32(flagReplace),
}
err = internal.BPFProgAttach(&attr)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
if errors.Is(err, unix.EBADF) {
return nil
}
return err
})
type bpfLinkCreateAttr struct {
progFd uint32
targetFd uint32
attachType ebpf.AttachType
flags uint32
}
func bpfLinkCreate(attr *bpfLinkCreateAttr) (*internal.FD, error) {
ptr, err := internal.BPF(internal.BPF_LINK_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err != nil {
return nil, err
}
return internal.NewFD(uint32(ptr)), nil
}
type bpfLinkUpdateAttr struct {
linkFd uint32
newProgFd uint32
flags uint32
oldProgFd uint32
}
func bpfLinkUpdate(attr *bpfLinkUpdateAttr) error {
_, err := internal.BPF(internal.BPF_LINK_UPDATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
var haveBPFLink = internal.FeatureTest("bpf_link", "5.7", func() error {
prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
Type: ebpf.CGroupSKB,
AttachType: ebpf.AttachCGroupInetIngress,
License: "MIT",
Instructions: asm.Instructions{
asm.Mov.Imm(asm.R0, 0),
asm.Return(),
},
})
if err != nil {
return internal.ErrNotSupported
}
defer prog.Close()
attr := bpfLinkCreateAttr{
// This is a hopefully invalid file descriptor, which triggers EBADF.
targetFd: ^uint32(0),
progFd: uint32(prog.FD()),
attachType: ebpf.AttachCGroupInetIngress,
}
_, err = bpfLinkCreate(&attr)
if errors.Is(err, unix.EINVAL) {
return internal.ErrNotSupported
}
if errors.Is(err, unix.EBADF) {
return nil
}
return err
})
type bpfIterCreateAttr struct {
linkFd uint32
flags uint32
}
func bpfIterCreate(attr *bpfIterCreateAttr) (*internal.FD, error) {
ptr, err := internal.BPF(internal.BPF_ITER_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err == nil {
return internal.NewFD(uint32(ptr)), nil
}
return nil, err
}
type bpfRawTracepointOpenAttr struct {
name internal.Pointer
fd uint32
_ uint32
}
func bpfRawTracepointOpen(attr *bpfRawTracepointOpenAttr) (*internal.FD, error) {
ptr, err := internal.BPF(internal.BPF_RAW_TRACEPOINT_OPEN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if err == nil {
return internal.NewFD(uint32(ptr)), nil
}
return nil, err
}
package link
import (
"fmt"
"github.com/cilium/ebpf"
)
// Tracepoint attaches the given eBPF program to the tracepoint with the given
// group and name. See /sys/kernel/debug/tracing/events to find available
// tracepoints. The top-level directory is the group, the event's subdirectory
// is the name. Example:
//
// Tracepoint("syscalls", "sys_enter_fork")
//
// Note that attaching eBPF programs to syscalls (sys_enter_*/sys_exit_*) is
// only possible as of kernel 4.14 (commit cf5f5ce).
func Tracepoint(group, name string, prog *ebpf.Program) (Link, error) {
if group == "" || name == "" {
return nil, fmt.Errorf("group and name cannot be empty: %w", errInvalidInput)
}
if prog == nil {
return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput)
}
if !rgxTraceEvent.MatchString(group) || !rgxTraceEvent.MatchString(name) {
return nil, fmt.Errorf("group and name '%s/%s' must be alphanumeric or underscore: %w", group, name, errInvalidInput)
}
if prog.Type() != ebpf.TracePoint {
return nil, fmt.Errorf("eBPF program type %s is not a Tracepoint: %w", prog.Type(), errInvalidInput)
}
tid, err := getTraceEventID(group, name)
if err != nil {
return nil, err
}
fd, err := openTracepointPerfEvent(tid)
if err != nil {
return nil, err
}
pe := &perfEvent{
fd: fd,
tracefsID: tid,
group: group,
name: name,
progType: ebpf.TracePoint,
}
if err := pe.attach(prog); err != nil {
pe.Close()
return nil, err
}
return pe, nil
}
...@@ -84,3 +84,50 @@ func needSection(insns, section asm.Instructions) (bool, error) { ...@@ -84,3 +84,50 @@ func needSection(insns, section asm.Instructions) (bool, error) {
// None of the functions in the section are called. // None of the functions in the section are called.
return false, nil return false, nil
} }
func fixupJumpsAndCalls(insns asm.Instructions) error {
symbolOffsets := make(map[string]asm.RawInstructionOffset)
iter := insns.Iterate()
for iter.Next() {
ins := iter.Ins
if ins.Symbol == "" {
continue
}
if _, ok := symbolOffsets[ins.Symbol]; ok {
return fmt.Errorf("duplicate symbol %s", ins.Symbol)
}
symbolOffsets[ins.Symbol] = iter.Offset
}
iter = insns.Iterate()
for iter.Next() {
i := iter.Index
offset := iter.Offset
ins := iter.Ins
switch {
case ins.IsFunctionCall() && ins.Constant == -1:
// Rewrite bpf to bpf call
callOffset, ok := symbolOffsets[ins.Reference]
if !ok {
return fmt.Errorf("instruction %d: reference to missing symbol %q", i, ins.Reference)
}
ins.Constant = int64(callOffset - offset - 1)
case ins.OpCode.Class() == asm.JumpClass && ins.Offset == -1:
// Rewrite jump to label
jumpOffset, ok := symbolOffsets[ins.Reference]
if !ok {
return fmt.Errorf("instruction %d: reference to missing symbol %q", i, ins.Reference)
}
ins.Offset = int16(jumpOffset - offset - 1)
}
}
return nil
}
...@@ -13,14 +13,12 @@ import ( ...@@ -13,14 +13,12 @@ import (
"github.com/cilium/ebpf/internal" "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) { 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 { if ptr, ok := data.(unsafe.Pointer); ok {
return internal.NewPointer(ptr), nil return internal.NewPointer(ptr), nil
} }
...@@ -33,6 +31,13 @@ func marshalPtr(data interface{}, length int) (internal.Pointer, error) { ...@@ -33,6 +31,13 @@ func marshalPtr(data interface{}, length int) (internal.Pointer, error) {
return internal.NewSlicePointer(buf), nil 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) { func marshalBytes(data interface{}, length int) (buf []byte, err error) {
switch value := data.(type) { switch value := data.(type) {
case encoding.BinaryMarshaler: case encoding.BinaryMarshaler:
...@@ -43,6 +48,8 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) { ...@@ -43,6 +48,8 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) {
buf = value buf = value
case unsafe.Pointer: case unsafe.Pointer:
err = errors.New("can't marshal from 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: default:
var wr bytes.Buffer var wr bytes.Buffer
err = binary.Write(&wr, internal.NativeEndian, value) err = binary.Write(&wr, internal.NativeEndian, value)
...@@ -70,10 +77,16 @@ func makeBuffer(dst interface{}, length int) (internal.Pointer, []byte) { ...@@ -70,10 +77,16 @@ func makeBuffer(dst interface{}, length int) (internal.Pointer, []byte) {
return internal.NewSlicePointer(buf), buf 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 { func unmarshalBytes(data interface{}, buf []byte) error {
switch value := data.(type) { switch value := data.(type) {
case unsafe.Pointer: case unsafe.Pointer:
sh := &reflect.SliceHeader{ // This could be solved in Go 1.17 by unsafe.Slice instead. (https://github.com/golang/go/issues/19367)
// We could opt for removing unsafe.Pointer support in the lib as well.
sh := &reflect.SliceHeader{ //nolint:govet
Data: uintptr(value), Data: uintptr(value),
Len: len(buf), Len: len(buf),
Cap: len(buf), Cap: len(buf),
...@@ -83,6 +96,8 @@ func unmarshalBytes(data interface{}, buf []byte) error { ...@@ -83,6 +96,8 @@ func unmarshalBytes(data interface{}, buf []byte) error {
copy(dst, buf) copy(dst, buf)
runtime.KeepAlive(value) runtime.KeepAlive(value)
return nil return nil
case Map, *Map, Program, *Program:
return fmt.Errorf("can't unmarshal into %T", value)
case encoding.BinaryUnmarshaler: case encoding.BinaryUnmarshaler:
return value.UnmarshalBinary(buf) return value.UnmarshalBinary(buf)
case *string: case *string:
......
eBPF
-------
[![](https://godoc.org/github.com/cilium/ebpf?status.svg)](https://godoc.org/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.
[ebpf/asm](https://godoc.org/github.com/cilium/ebpf/asm) contains a basic assembler.
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
* [Cilium eBPF documentation](https://cilium.readthedocs.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)
...@@ -9,20 +9,24 @@ if [[ "${1:-}" = "--in-vm" ]]; then ...@@ -9,20 +9,24 @@ if [[ "${1:-}" = "--in-vm" ]]; then
shift shift
mount -t bpf bpf /sys/fs/bpf mount -t bpf bpf /sys/fs/bpf
mount -t tracefs tracefs /sys/kernel/debug/tracing
export CGO_ENABLED=0 export CGO_ENABLED=0
export GOFLAGS=-mod=readonly export GOFLAGS=-mod=readonly
export GOPATH=/run/go-path export GOPATH=/run/go-path
export GOPROXY=file:///run/go-root/pkg/mod/cache/download export GOPROXY=file:///run/go-path/pkg/mod/cache/download
export GOSUMDB=off
export GOCACHE=/run/go-cache export GOCACHE=/run/go-cache
elfs=""
if [[ -d "/run/input/bpf" ]]; then if [[ -d "/run/input/bpf" ]]; then
elfs="/run/input/bpf" export KERNEL_SELFTESTS="/run/input/bpf"
fi fi
readonly output="${1}"
shift
echo Running tests... echo Running tests...
/usr/local/bin/go test -coverprofile="$1/coverage.txt" -covermode=atomic -v -elfs "$elfs" ./... go test -v -coverpkg=./... -coverprofile="$output/coverage.txt" -count 1 ./...
touch "$1/success" touch "$output/success"
exit 0 exit 0
fi fi
...@@ -66,11 +70,12 @@ fi ...@@ -66,11 +70,12 @@ fi
echo Testing on "${kernel_version}" echo Testing on "${kernel_version}"
$sudo virtme-run --kimg "${tmp_dir}/${kernel}" --memory 512M --pwd \ $sudo virtme-run --kimg "${tmp_dir}/${kernel}" --memory 512M --pwd \
--rw \
--rwdir=/run/input="${input}" \ --rwdir=/run/input="${input}" \
--rwdir=/run/output="${output}" \ --rwdir=/run/output="${output}" \
--rodir=/run/go-path="$(go env GOPATH)" \ --rodir=/run/go-path="$(go env GOPATH)" \
--rwdir=/run/go-cache="$(go env GOCACHE)" \ --rwdir=/run/go-cache="$(go env GOCACHE)" \
--script-sh "$(realpath "$0") --in-vm /run/output" \ --script-sh "PATH=\"$PATH\" $(realpath "$0") --in-vm /run/output" \
--qemu-opts -smp 2 # need at least two CPUs for some tests --qemu-opts -smp 2 # need at least two CPUs for some tests
if [[ ! -e "${output}/success" ]]; then if [[ ! -e "${output}/success" ]]; then
...@@ -78,10 +83,8 @@ if [[ ! -e "${output}/success" ]]; then ...@@ -78,10 +83,8 @@ if [[ ! -e "${output}/success" ]]; then
exit 1 exit 1
else else
echo "Test successful on ${kernel_version}" echo "Test successful on ${kernel_version}"
if [[ -v CODECOV_TOKEN ]]; then if [[ -v COVERALLS_TOKEN ]]; then
curl --fail -s https://codecov.io/bash > "${tmp_dir}/codecov.sh" goveralls -coverprofile="${output}/coverage.txt" -service=semaphore -repotoken "$COVERALLS_TOKEN"
chmod +x "${tmp_dir}/codecov.sh"
"${tmp_dir}/codecov.sh" -f "${output}/coverage.txt"
fi fi
fi fi
......
package ebpf package ebpf
//go:generate stringer -output types_string.go -type=MapType,ProgramType,AttachType import (
"github.com/cilium/ebpf/internal/unix"
)
//go:generate stringer -output types_string.go -type=MapType,ProgramType,AttachType,PinType
// MapType indicates the type map structure // MapType indicates the type map structure
// that will be initialized in the kernel. // that will be initialized in the kernel.
...@@ -85,10 +89,19 @@ const ( ...@@ -85,10 +89,19 @@ const (
// hasPerCPUValue returns true if the Map stores a value per CPU. // hasPerCPUValue returns true if the Map stores a value per CPU.
func (mt MapType) hasPerCPUValue() bool { func (mt MapType) hasPerCPUValue() bool {
if mt == PerCPUHash || mt == PerCPUArray || mt == LRUCPUHash { return mt == PerCPUHash || mt == PerCPUArray || mt == LRUCPUHash
return true }
}
return false // 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 // ProgramType of the eBPF program
...@@ -96,60 +109,37 @@ type ProgramType uint32 ...@@ -96,60 +109,37 @@ type ProgramType uint32
// eBPF program types // eBPF program types
const ( const (
// Unrecognized program type
UnspecifiedProgram ProgramType = iota UnspecifiedProgram ProgramType = iota
// SocketFilter socket or seccomp filter
SocketFilter SocketFilter
// Kprobe program
Kprobe Kprobe
// SchedCLS traffic control shaper
SchedCLS SchedCLS
// SchedACT routing control shaper
SchedACT SchedACT
// TracePoint program
TracePoint TracePoint
// XDP program
XDP XDP
// PerfEvent program
PerfEvent PerfEvent
// CGroupSKB program
CGroupSKB CGroupSKB
// CGroupSock program
CGroupSock CGroupSock
// LWTIn program
LWTIn LWTIn
// LWTOut program
LWTOut LWTOut
// LWTXmit program
LWTXmit LWTXmit
// SockOps program
SockOps SockOps
// SkSKB program
SkSKB SkSKB
// CGroupDevice program
CGroupDevice CGroupDevice
// SkMsg program
SkMsg SkMsg
// RawTracepoint program
RawTracepoint RawTracepoint
// CGroupSockAddr program
CGroupSockAddr CGroupSockAddr
// LWTSeg6Local program
LWTSeg6Local LWTSeg6Local
// LircMode2 program
LircMode2 LircMode2
// SkReuseport program
SkReuseport SkReuseport
// FlowDissector program
FlowDissector FlowDissector
// CGroupSysctl program
CGroupSysctl CGroupSysctl
// RawTracepointWritable program
RawTracepointWritable RawTracepointWritable
// CGroupSockopt program
CGroupSockopt CGroupSockopt
// Tracing program
Tracing Tracing
StructOps
Extension
LSM
SkLookup
) )
// AttachType of the eBPF program, needed to differentiate allowed context accesses in // AttachType of the eBPF program, needed to differentiate allowed context accesses in
...@@ -157,7 +147,7 @@ const ( ...@@ -157,7 +147,7 @@ const (
// Will cause invalid argument (EINVAL) at program load time if set incorrectly. // Will cause invalid argument (EINVAL) at program load time if set incorrectly.
type AttachType uint32 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 AttachNone AttachType = 0
const ( const (
...@@ -190,7 +180,65 @@ const ( ...@@ -190,7 +180,65 @@ const (
AttachModifyReturn AttachModifyReturn
AttachLSMMac AttachLSMMac
AttachTraceIter AttachTraceIter
AttachCgroupInet4GetPeername
AttachCgroupInet6GetPeername
AttachCgroupInet4GetSockname
AttachCgroupInet6GetSockname
AttachXDPDevMap
AttachCgroupInetSockRelease
AttachXDPCPUMap
AttachSkLookup
AttachXDP
) )
// AttachFlags of the eBPF program used in BPF_PROG_ATTACH command // AttachFlags of the eBPF program used in BPF_PROG_ATTACH command
type AttachFlags uint32 type AttachFlags uint32
// PinType determines whether a map is pinned into a BPFFS.
type PinType int
// Valid pin types.
//
// Mirrors enum libbpf_pin_type.
const (
PinNone PinType = iota
// Pin an object by using its name as the filename.
PinByName
)
// LoadPinOptions control how a pinned object is loaded.
type LoadPinOptions struct {
// Request a read-only or write-only object. The default is a read-write
// object. Only one of the flags may be set.
ReadOnly bool
WriteOnly bool
// Raw flags for the syscall. Other fields of this struct take precedence.
Flags uint32
}
// Marshal returns a value suitable for BPF_OBJ_GET syscall file_flags parameter.
func (lpo *LoadPinOptions) Marshal() uint32 {
if lpo == nil {
return 0
}
flags := lpo.Flags
if lpo.ReadOnly {
flags |= unix.BPF_F_RDONLY
}
if lpo.WriteOnly {
flags |= unix.BPF_F_WRONLY
}
return flags
}
// 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
}
// Code generated by "stringer -output types_string.go -type=MapType,ProgramType,AttachType"; DO NOT EDIT. // Code generated by "stringer -output types_string.go -type=MapType,ProgramType,AttachType,PinType"; DO NOT EDIT.
package ebpf package ebpf
...@@ -77,11 +77,15 @@ func _() { ...@@ -77,11 +77,15 @@ func _() {
_ = x[RawTracepointWritable-24] _ = x[RawTracepointWritable-24]
_ = x[CGroupSockopt-25] _ = x[CGroupSockopt-25]
_ = x[Tracing-26] _ = x[Tracing-26]
_ = x[StructOps-27]
_ = x[Extension-28]
_ = x[LSM-29]
_ = x[SkLookup-30]
} }
const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracing" const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracingStructOpsExtensionLSMSkLookup"
var _ProgramType_index = [...]uint16{0, 18, 30, 36, 44, 52, 62, 65, 74, 83, 93, 98, 104, 111, 118, 123, 135, 140, 153, 167, 179, 188, 199, 212, 224, 245, 258, 265} var _ProgramType_index = [...]uint16{0, 18, 30, 36, 44, 52, 62, 65, 74, 83, 93, 98, 104, 111, 118, 123, 135, 140, 153, 167, 179, 188, 199, 212, 224, 245, 258, 265, 274, 283, 286, 294}
func (i ProgramType) String() string { func (i ProgramType) String() string {
if i >= ProgramType(len(_ProgramType_index)-1) { if i >= ProgramType(len(_ProgramType_index)-1) {
...@@ -123,11 +127,20 @@ func _() { ...@@ -123,11 +127,20 @@ func _() {
_ = x[AttachModifyReturn-26] _ = x[AttachModifyReturn-26]
_ = x[AttachLSMMac-27] _ = x[AttachLSMMac-27]
_ = x[AttachTraceIter-28] _ = x[AttachTraceIter-28]
_ = x[AttachCgroupInet4GetPeername-29]
_ = x[AttachCgroupInet6GetPeername-30]
_ = x[AttachCgroupInet4GetSockname-31]
_ = x[AttachCgroupInet6GetSockname-32]
_ = x[AttachXDPDevMap-33]
_ = x[AttachCgroupInetSockRelease-34]
_ = x[AttachXDPCPUMap-35]
_ = x[AttachSkLookup-36]
_ = x[AttachXDP-37]
} }
const _AttachType_name = "AttachNoneAttachCGroupInetEgressAttachCGroupInetSockCreateAttachCGroupSockOpsAttachSkSKBStreamParserAttachSkSKBStreamVerdictAttachCGroupDeviceAttachSkMsgVerdictAttachCGroupInet4BindAttachCGroupInet6BindAttachCGroupInet4ConnectAttachCGroupInet6ConnectAttachCGroupInet4PostBindAttachCGroupInet6PostBindAttachCGroupUDP4SendmsgAttachCGroupUDP6SendmsgAttachLircMode2AttachFlowDissectorAttachCGroupSysctlAttachCGroupUDP4RecvmsgAttachCGroupUDP6RecvmsgAttachCGroupGetsockoptAttachCGroupSetsockoptAttachTraceRawTpAttachTraceFEntryAttachTraceFExitAttachModifyReturnAttachLSMMacAttachTraceIter" const _AttachType_name = "AttachNoneAttachCGroupInetEgressAttachCGroupInetSockCreateAttachCGroupSockOpsAttachSkSKBStreamParserAttachSkSKBStreamVerdictAttachCGroupDeviceAttachSkMsgVerdictAttachCGroupInet4BindAttachCGroupInet6BindAttachCGroupInet4ConnectAttachCGroupInet6ConnectAttachCGroupInet4PostBindAttachCGroupInet6PostBindAttachCGroupUDP4SendmsgAttachCGroupUDP6SendmsgAttachLircMode2AttachFlowDissectorAttachCGroupSysctlAttachCGroupUDP4RecvmsgAttachCGroupUDP6RecvmsgAttachCGroupGetsockoptAttachCGroupSetsockoptAttachTraceRawTpAttachTraceFEntryAttachTraceFExitAttachModifyReturnAttachLSMMacAttachTraceIterAttachCgroupInet4GetPeernameAttachCgroupInet6GetPeernameAttachCgroupInet4GetSocknameAttachCgroupInet6GetSocknameAttachXDPDevMapAttachCgroupInetSockReleaseAttachXDPCPUMapAttachSkLookupAttachXDP"
var _AttachType_index = [...]uint16{0, 10, 32, 58, 77, 100, 124, 142, 160, 181, 202, 226, 250, 275, 300, 323, 346, 361, 380, 398, 421, 444, 466, 488, 504, 521, 537, 555, 567, 582} var _AttachType_index = [...]uint16{0, 10, 32, 58, 77, 100, 124, 142, 160, 181, 202, 226, 250, 275, 300, 323, 346, 361, 380, 398, 421, 444, 466, 488, 504, 521, 537, 555, 567, 582, 610, 638, 666, 694, 709, 736, 751, 765, 774}
func (i AttachType) String() string { func (i AttachType) String() string {
if i >= AttachType(len(_AttachType_index)-1) { if i >= AttachType(len(_AttachType_index)-1) {
...@@ -135,3 +148,21 @@ func (i AttachType) String() string { ...@@ -135,3 +148,21 @@ func (i AttachType) String() string {
} }
return _AttachType_name[_AttachType_index[i]:_AttachType_index[i+1]] return _AttachType_name[_AttachType_index[i]:_AttachType_index[i+1]]
} }
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[PinNone-0]
_ = x[PinByName-1]
}
const _PinType_name = "PinNonePinByName"
var _PinType_index = [...]uint8{0, 7, 16}
func (i PinType) String() string {
if i < 0 || i >= PinType(len(_PinType_index)-1) {
return "PinType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _PinType_name[_PinType_index[i]:_PinType_index[i+1]]
}
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// +build !windows
// Package activation implements primitives for systemd socket activation. // Package activation implements primitives for systemd socket activation.
package activation package activation
......
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package activation
import "os"
func Files(unsetEnv bool) []*os.File {
return nil
}
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package dbus package dbus
import ( import (
"context"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"os" "os"
...@@ -112,39 +113,63 @@ type Conn struct { ...@@ -112,39 +113,63 @@ type Conn struct {
// New establishes a connection to any available bus and authenticates. // New establishes a connection to any available bus and authenticates.
// Callers should call Close() when done with the connection. // Callers should call Close() when done with the connection.
// Deprecated: use NewWithContext instead
func New() (*Conn, error) { func New() (*Conn, error) {
conn, err := NewSystemConnection() return NewWithContext(context.Background())
}
// NewWithContext same as New with context
func NewWithContext(ctx context.Context) (*Conn, error) {
conn, err := NewSystemConnectionContext(ctx)
if err != nil && os.Geteuid() == 0 { if err != nil && os.Geteuid() == 0 {
return NewSystemdConnection() return NewSystemdConnectionContext(ctx)
} }
return conn, err return conn, err
} }
// NewSystemConnection establishes a connection to the system bus and authenticates. // NewSystemConnection establishes a connection to the system bus and authenticates.
// Callers should call Close() when done with the connection // Callers should call Close() when done with the connection
// Deprecated: use NewSystemConnectionContext instead
func NewSystemConnection() (*Conn, error) { func NewSystemConnection() (*Conn, error) {
return NewSystemConnectionContext(context.Background())
}
// NewSystemConnectionContext same as NewSystemConnection with context
func NewSystemConnectionContext(ctx context.Context) (*Conn, error) {
return NewConnection(func() (*dbus.Conn, error) { return NewConnection(func() (*dbus.Conn, error) {
return dbusAuthHelloConnection(dbus.SystemBusPrivate) return dbusAuthHelloConnection(ctx, dbus.SystemBusPrivate)
}) })
} }
// NewUserConnection establishes a connection to the session bus and // NewUserConnection establishes a connection to the session bus and
// authenticates. This can be used to connect to systemd user instances. // authenticates. This can be used to connect to systemd user instances.
// Callers should call Close() when done with the connection. // Callers should call Close() when done with the connection.
// Deprecated: use NewUserConnectionContext instead
func NewUserConnection() (*Conn, error) { func NewUserConnection() (*Conn, error) {
return NewUserConnectionContext(context.Background())
}
// NewUserConnectionContext same as NewUserConnection with context
func NewUserConnectionContext(ctx context.Context) (*Conn, error) {
return NewConnection(func() (*dbus.Conn, error) { return NewConnection(func() (*dbus.Conn, error) {
return dbusAuthHelloConnection(dbus.SessionBusPrivate) return dbusAuthHelloConnection(ctx, dbus.SessionBusPrivate)
}) })
} }
// NewSystemdConnection establishes a private, direct connection to systemd. // NewSystemdConnection establishes a private, direct connection to systemd.
// This can be used for communicating with systemd without a dbus daemon. // This can be used for communicating with systemd without a dbus daemon.
// Callers should call Close() when done with the connection. // Callers should call Close() when done with the connection.
// Deprecated: use NewSystemdConnectionContext instead
func NewSystemdConnection() (*Conn, error) { func NewSystemdConnection() (*Conn, error) {
return NewSystemdConnectionContext(context.Background())
}
// NewSystemdConnectionContext same as NewSystemdConnection with context
func NewSystemdConnectionContext(ctx context.Context) (*Conn, error) {
return NewConnection(func() (*dbus.Conn, error) { return NewConnection(func() (*dbus.Conn, error) {
// We skip Hello when talking directly to systemd. // We skip Hello when talking directly to systemd.
return dbusAuthConnection(func(opts ...dbus.ConnOption) (*dbus.Conn, error) { return dbusAuthConnection(ctx, func(opts ...dbus.ConnOption) (*dbus.Conn, error) {
return dbus.Dial("unix:path=/run/systemd/private") return dbus.Dial("unix:path=/run/systemd/private", opts...)
}) })
}) })
} }
...@@ -201,8 +226,8 @@ func (c *Conn) GetManagerProperty(prop string) (string, error) { ...@@ -201,8 +226,8 @@ func (c *Conn) GetManagerProperty(prop string) (string, error) {
return variant.String(), nil return variant.String(), nil
} }
func dbusAuthConnection(createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) { func dbusAuthConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
conn, err := createBus() conn, err := createBus(dbus.WithContext(ctx))
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -221,8 +246,8 @@ func dbusAuthConnection(createBus func(opts ...dbus.ConnOption) (*dbus.Conn, err ...@@ -221,8 +246,8 @@ func dbusAuthConnection(createBus func(opts ...dbus.ConnOption) (*dbus.Conn, err
return conn, nil return conn, nil
} }
func dbusAuthHelloConnection(createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) { func dbusAuthHelloConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
conn, err := dbusAuthConnection(createBus) conn, err := dbusAuthConnection(ctx, createBus)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
dist: bionic
language: go
go_import_path: github.com/godbus/dbus
go:
- 1.11.x
- 1.12.x
- 1.13.x
- tip
matrix:
fast_finish: true
allow_failures:
- go: tip
addons:
apt:
packages:
- dbus
- dbus-x11
before_install:
- export GO111MODULE=on
script:
- go test -v -race -mod=readonly ./... # Run all the tests with the race detector enabled
- go vet ./... # go vet is the official Go static analyzer
jobs:
include:
# The build matrix doesn't cover build stages, so manually expand
# the jobs with anchors
- &multiarch
stage: "Multiarch Test"
go: 1.11.x
env: TARGETS="386 arm arm64 ppc64le"
before_install:
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
script:
- |
set -e
for target in $TARGETS; do
printf "\e[1mRunning test suite under ${target}.\e[0m\n"
GOARCH="$target" go test -v ./...
printf "\n\n"
done
- <<: *multiarch
go: 1.12.x
- <<: *multiarch
go: 1.13.x
[![Build Status](https://travis-ci.org/godbus/dbus.svg?branch=master)](https://travis-ci.org/godbus/dbus) ![Build Status](https://github.com/godbus/dbus/workflows/Go/badge.svg)
dbus dbus
---- ----
...@@ -32,6 +32,8 @@ gives a short overview over the basic usage. ...@@ -32,6 +32,8 @@ gives a short overview over the basic usage.
#### Projects using godbus #### Projects using godbus
- [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library. - [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library.
- [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API. - [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API.
- [playerbm](https://github.com/altdesktop/playerbm) a bookmark utility for media players.
- [iwd](https://github.com/shibumi/iwd) go bindings for the internet wireless daemon "iwd".
Please note that the API is considered unstable for now and may change without Please note that the API is considered unstable for now and may change without
further notice. further notice.
......
...@@ -37,7 +37,7 @@ const ( ...@@ -37,7 +37,7 @@ const (
// Auth defines the behaviour of an authentication mechanism. // Auth defines the behaviour of an authentication mechanism.
type Auth interface { type Auth interface {
// Return the name of the mechnism, the argument to the first AUTH command // Return the name of the mechanism, the argument to the first AUTH command
// and the next status. // and the next status.
FirstData() (name, resp []byte, status AuthStatus) FirstData() (name, resp []byte, status AuthStatus)
......
...@@ -24,6 +24,15 @@ type Call struct { ...@@ -24,6 +24,15 @@ type Call struct {
// Holds the response once the call is done. // Holds the response once the call is done.
Body []interface{} Body []interface{}
// ResponseSequence stores the sequence number of the DBus message containing
// the call response (or error). This can be compared to the sequence number
// of other call responses and signals on this connection to determine their
// relative ordering on the underlying DBus connection.
// For errors, ResponseSequence is populated only if the error came from a
// DBusMessage that was received or if there was an error receiving. In case of
// failure to make the call, ResponseSequence will be NoSequence.
ResponseSequence Sequence
// tracks context and canceler // tracks context and canceler
ctx context.Context ctx context.Context
ctxCanceler context.CancelFunc ctxCanceler context.CancelFunc
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment