Update runc to v1.0.0-rc91

https://github.com/opencontainers/runc/releases/tag/v1.0.0-rc91

Signed-off-by: Davanum Srinivas <davanum@gmail.com>
This commit is contained in:
Davanum Srinivas 2020-07-01 22:06:59 -04:00
parent c91c72c867
commit 963625d7bc
No known key found for this signature in database
GPG Key ID: 80D83A796103BF59
275 changed files with 9060 additions and 18508 deletions

View File

@ -17,7 +17,7 @@ github.com/docker/go-units v0.4.0
github.com/godbus/dbus/v5 v5.0.3 github.com/godbus/dbus/v5 v5.0.3
github.com/gogo/googleapis v1.3.2 github.com/gogo/googleapis v1.3.2
github.com/gogo/protobuf v1.3.1 github.com/gogo/protobuf v1.3.1
github.com/golang/protobuf v1.3.3 github.com/golang/protobuf v1.3.5
github.com/google/go-cmp v0.2.0 github.com/google/go-cmp v0.2.0
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
@ -31,13 +31,13 @@ github.com/Microsoft/go-winio v0.4.14
github.com/Microsoft/hcsshim v0.8.9 github.com/Microsoft/hcsshim v0.8.9
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.1 github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/runc v1.0.0-rc10 github.com/opencontainers/runc v1.0.0-rc91
github.com/opencontainers/runtime-spec v1.0.2 github.com/opencontainers/runtime-spec 237cc4f519e2e8f9b235bacccfa8ef5a84df2875 # v1.0.3-0.20200520003142-237cc4f519e2
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.3.0 github.com/prometheus/client_golang v1.6.0
github.com/prometheus/client_model v0.1.0 github.com/prometheus/client_model v0.2.0
github.com/prometheus/common v0.7.0 github.com/prometheus/common v0.9.1
github.com/prometheus/procfs v0.0.8 github.com/prometheus/procfs v0.0.11
github.com/russross/blackfriday/v2 v2.0.1 github.com/russross/blackfriday/v2 v2.0.1
github.com/shurcooL/sanitized_anchor_name v1.0.0 github.com/shurcooL/sanitized_anchor_name v1.0.0
github.com/sirupsen/logrus v1.6.0 github.com/sirupsen/logrus v1.6.0
@ -47,42 +47,41 @@ go.etcd.io/bbolt v1.3.5
go.opencensus.io v0.22.0 go.opencensus.io v0.22.0
golang.org/x/net f3200d17e092c607f615320ecaad13d87ad9a2b3 golang.org/x/net f3200d17e092c607f615320ecaad13d87ad9a2b3
golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e
golang.org/x/sys 5c8b2ff67527cb88b770f693cebf3799036d8bc0 golang.org/x/sys 9dae0f8f577553e0f21298e18926efc9644c281d
golang.org/x/text v0.3.3 golang.org/x/text v0.3.3
google.golang.org/genproto e50cd9704f63023d62cd06a1994b98227fc4d21a google.golang.org/genproto e50cd9704f63023d62cd06a1994b98227fc4d21a
google.golang.org/grpc v1.27.1 google.golang.org/grpc v1.27.1
gotest.tools/v3 v3.0.2 gotest.tools/v3 v3.0.2
# cgroups dependencies # cgroups dependencies
github.com/cilium/ebpf 4032b1d8aae306b7bb94a2a11002932caf88c644 github.com/cilium/ebpf 1c8d4c9ef7759622653a1d319284a44652333b28
# cri dependencies # cri dependencies
github.com/containerd/cri 4f8a580795344b0f4c1146a3abce0409962f3890 # master github.com/containerd/cri aa0f4fd37b82a273156d35a7e13831bd0940ab99 # master
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/docker/docker 4634ce647cf2ce2c6031129ccd109e557244986f github.com/docker/docker 4634ce647cf2ce2c6031129ccd109e557244986f
github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528 github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528
github.com/emicklei/go-restful v2.9.5 github.com/emicklei/go-restful v2.9.5
github.com/go-logr/logr v0.2.0
github.com/google/gofuzz v1.1.0 github.com/google/gofuzz v1.1.0
github.com/json-iterator/go v1.1.8 github.com/json-iterator/go v1.1.9
github.com/modern-go/concurrent 1.0.3 github.com/modern-go/concurrent 1.0.3
github.com/modern-go/reflect2 v1.0.1 github.com/modern-go/reflect2 v1.0.1
github.com/opencontainers/selinux bb88c45a3863dc4c38320d71b890bb30ef9feba4 github.com/opencontainers/selinux v1.5.1
github.com/seccomp/libseccomp-golang v0.9.1 github.com/seccomp/libseccomp-golang v0.9.1
github.com/stretchr/testify v1.4.0
github.com/tchap/go-patricia v2.2.6 github.com/tchap/go-patricia v2.2.6
golang.org/x/crypto bac4c82f69751a6dd76e702d54b3ceb88adab236 golang.org/x/crypto bac4c82f69751a6dd76e702d54b3ceb88adab236
golang.org/x/oauth2 0f29369cfe4552d0e4bcddc57cc75f4d7e672a33 golang.org/x/oauth2 858c2ad4c8b6c5d10852cb89079f6ca1c7309787
golang.org/x/time 9d24e82272b4f38b78bc8cff74fa936d31ccd8ef golang.org/x/time 555d28b269f0569763d25dbe1a237ae74c6bcc82
gopkg.in/inf.v0 v0.9.1 gopkg.in/inf.v0 v0.9.1
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8
k8s.io/api v0.18.2 k8s.io/api v0.19.0-beta.2
k8s.io/apimachinery v0.18.2 k8s.io/apimachinery v0.19.0-beta.2
k8s.io/apiserver v0.18.2 k8s.io/apiserver v0.19.0-beta.2
k8s.io/client-go v0.18.2 k8s.io/client-go v0.19.0-beta.2
k8s.io/cri-api v0.18.2 k8s.io/cri-api v0.19.0-beta.2
k8s.io/klog v1.0.0 k8s.io/klog/v2 v2.2.0
k8s.io/kubernetes v1.18.2 k8s.io/utils 2df71ebbae66f39338aed4cd0bb82d2212ee33cc
k8s.io/utils a9aa75ae1b89e1b992c33383f48e942d97e52dae
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 sigs.k8s.io/structured-merge-diff/v3 v3.0.0
sigs.k8s.io/yaml v1.2.0 sigs.k8s.io/yaml v1.2.0
@ -90,7 +89,7 @@ sigs.k8s.io/yaml v1.2.0
github.com/containerd/go-cni v1.0.0 github.com/containerd/go-cni v1.0.0
github.com/containernetworking/cni v0.7.1 github.com/containernetworking/cni v0.7.1
github.com/containernetworking/plugins v0.7.6 github.com/containernetworking/plugins v0.7.6
github.com/fsnotify/fsnotify v1.4.8 github.com/fsnotify/fsnotify v1.4.9
# image decrypt depedencies # image decrypt depedencies
github.com/containerd/imgcrypt v1.0.1 github.com/containerd/imgcrypt v1.0.1

16
vendor/github.com/cilium/ebpf/abi.go generated vendored
View File

@ -3,14 +3,13 @@ package ebpf
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"syscall" "syscall"
"github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal"
"github.com/pkg/errors"
) )
// MapABI are the attributes of a Map which are available across all supported kernels. // MapABI are the attributes of a Map which are available across all supported kernels.
@ -35,7 +34,7 @@ func newMapABIFromSpec(spec *MapSpec) *MapABI {
func newMapABIFromFd(fd *internal.FD) (string, *MapABI, error) { func newMapABIFromFd(fd *internal.FD) (string, *MapABI, error) {
info, err := bpfGetMapInfoByFD(fd) info, err := bpfGetMapInfoByFD(fd)
if err != nil { if err != nil {
if errors.Cause(err) == syscall.EINVAL { if errors.Is(err, syscall.EINVAL) {
abi, err := newMapABIFromProc(fd) abi, err := newMapABIFromProc(fd)
return "", abi, err return "", abi, err
} }
@ -98,7 +97,7 @@ func newProgramABIFromSpec(spec *ProgramSpec) *ProgramABI {
func newProgramABIFromFd(fd *internal.FD) (string, *ProgramABI, error) { func newProgramABIFromFd(fd *internal.FD) (string, *ProgramABI, error) {
info, err := bpfGetProgInfoByFD(fd) info, err := bpfGetProgInfoByFD(fd)
if err != nil { if err != nil {
if errors.Cause(err) == syscall.EINVAL { if errors.Is(err, syscall.EINVAL) {
return newProgramABIFromProc(fd) return newProgramABIFromProc(fd)
} }
@ -127,7 +126,7 @@ func newProgramABIFromProc(fd *internal.FD) (string, *ProgramABI, error) {
"prog_type": &abi.Type, "prog_type": &abi.Type,
"prog_tag": &name, "prog_tag": &name,
}) })
if errors.Cause(err) == errMissingFields { if errors.Is(err, errMissingFields) {
return "", nil, &internal.UnsupportedFeatureError{ return "", nil, &internal.UnsupportedFeatureError{
Name: "reading ABI from /proc/self/fdinfo", Name: "reading ABI from /proc/self/fdinfo",
MinimumVersion: internal.Version{4, 11, 0}, MinimumVersion: internal.Version{4, 11, 0},
@ -152,7 +151,10 @@ func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error {
} }
defer fh.Close() defer fh.Close()
return errors.Wrap(scanFdInfoReader(fh, fields), fh.Name()) if err := scanFdInfoReader(fh, fields); err != nil {
return fmt.Errorf("%s: %w", fh.Name(), err)
}
return nil
} }
var errMissingFields = errors.New("missing fields") var errMissingFields = errors.New("missing fields")
@ -176,7 +178,7 @@ func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
} }
if n, err := fmt.Fscanln(bytes.NewReader(parts[1]), field); err != nil || n != 1 { if n, err := fmt.Fscanln(bytes.NewReader(parts[1]), field); err != nil || n != 1 {
return errors.Wrapf(err, "can't parse field %s", name) return fmt.Errorf("can't parse field %s: %v", name, err)
} }
scanned++ scanned++

View File

@ -2,12 +2,11 @@ package asm
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"math" "math"
"strings" "strings"
"github.com/pkg/errors"
) )
// InstructionSize is the size of a BPF instruction in bytes // InstructionSize is the size of a BPF instruction in bytes
@ -39,10 +38,12 @@ func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, err
} }
ins.OpCode = bi.OpCode ins.OpCode = bi.OpCode
ins.Dst = bi.Registers.Dst()
ins.Src = bi.Registers.Src()
ins.Offset = bi.Offset ins.Offset = bi.Offset
ins.Constant = int64(bi.Constant) ins.Constant = int64(bi.Constant)
ins.Dst, ins.Src, err = bi.Registers.Unmarshal(bo)
if err != nil {
return 0, fmt.Errorf("can't unmarshal registers: %s", err)
}
if !bi.OpCode.isDWordLoad() { if !bi.OpCode.isDWordLoad() {
return InstructionSize, nil return InstructionSize, nil
@ -75,9 +76,14 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
cons = int32(uint32(ins.Constant)) cons = int32(uint32(ins.Constant))
} }
regs, err := newBPFRegisters(ins.Dst, ins.Src, bo)
if err != nil {
return 0, fmt.Errorf("can't marshal registers: %s", err)
}
bpfi := bpfInstruction{ bpfi := bpfInstruction{
ins.OpCode, ins.OpCode,
newBPFRegisters(ins.Dst, ins.Src), regs,
ins.Offset, ins.Offset,
cons, cons,
} }
@ -103,22 +109,52 @@ func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error)
// RewriteMapPtr changes an instruction to use a new map fd. // RewriteMapPtr changes an instruction to use a new map fd.
// //
// Returns an error if the fd is invalid, or the instruction // Returns an error if the instruction doesn't load a map.
// is incorrect.
func (ins *Instruction) RewriteMapPtr(fd int) error { func (ins *Instruction) RewriteMapPtr(fd int) error {
if !ins.OpCode.isDWordLoad() { if !ins.OpCode.isDWordLoad() {
return errors.Errorf("%s is not a 64 bit load", ins.OpCode) return fmt.Errorf("%s is not a 64 bit load", ins.OpCode)
} }
if fd < 0 { if ins.Src != PseudoMapFD && ins.Src != PseudoMapValue {
return errors.New("invalid fd") return errors.New("not a load from a map")
} }
ins.Src = R1 // Preserve the offset value for direct map loads.
ins.Constant = int64(fd) offset := uint64(ins.Constant) & (math.MaxUint32 << 32)
rawFd := uint64(uint32(fd))
ins.Constant = int64(offset | rawFd)
return nil return nil
} }
func (ins *Instruction) mapPtr() uint32 {
return uint32(uint64(ins.Constant) & math.MaxUint32)
}
// RewriteMapOffset changes the offset of a direct load from a map.
//
// Returns an error if the instruction is not a direct load.
func (ins *Instruction) RewriteMapOffset(offset uint32) error {
if !ins.OpCode.isDWordLoad() {
return fmt.Errorf("%s is not a 64 bit load", ins.OpCode)
}
if ins.Src != PseudoMapValue {
return errors.New("not a direct load from a map")
}
fd := uint64(ins.Constant) & math.MaxUint32
ins.Constant = int64(uint64(offset)<<32 | fd)
return nil
}
func (ins *Instruction) mapOffset() uint32 {
return uint32(uint64(ins.Constant) >> 32)
}
func (ins *Instruction) isLoadFromMap() bool {
return ins.OpCode == LoadImmOp(DWord) && (ins.Src == PseudoMapFD || ins.Src == PseudoMapValue)
}
// 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' {
@ -139,6 +175,19 @@ func (ins Instruction) Format(f fmt.State, c rune) {
return return
} }
if ins.isLoadFromMap() {
fd := int32(ins.mapPtr())
switch ins.Src {
case PseudoMapFD:
fmt.Fprintf(f, "LoadMapPtr dst: %s fd: %d", ins.Dst, fd)
case PseudoMapValue:
fmt.Fprintf(f, "LoadMapValue dst: %s, fd: %d off: %d", ins.Dst, fd, ins.mapOffset())
}
goto ref
}
fmt.Fprintf(f, "%v ", op) fmt.Fprintf(f, "%v ", op)
switch cls := op.Class(); cls { switch cls := op.Class(); cls {
case LdClass, LdXClass, StClass, StXClass: case LdClass, LdXClass, StClass, StXClass:
@ -166,7 +215,7 @@ func (ins Instruction) Format(f fmt.State, c rune) {
case JumpClass: case JumpClass:
switch jop := op.JumpOp(); jop { switch jop := op.JumpOp(); jop {
case Call: case Call:
if ins.Src == R1 { if ins.Src == PseudoCall {
// bpf-to-bpf call // bpf-to-bpf call
fmt.Fprint(f, ins.Constant) fmt.Fprint(f, ins.Constant)
} else { } else {
@ -183,6 +232,7 @@ func (ins Instruction) Format(f fmt.State, c rune) {
} }
} }
ref:
if ins.Reference != "" { if ins.Reference != "" {
fmt.Fprintf(f, " <%s>", ins.Reference) fmt.Fprintf(f, " <%s>", ins.Reference)
} }
@ -235,7 +285,7 @@ func (insns Instructions) SymbolOffsets() (map[string]int, error) {
} }
if _, ok := offsets[ins.Symbol]; ok { if _, ok := offsets[ins.Symbol]; ok {
return nil, errors.Errorf("duplicate symbol %s", ins.Symbol) return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
} }
offsets[ins.Symbol] = i offsets[ins.Symbol] = i
@ -273,7 +323,7 @@ func (insns Instructions) marshalledOffsets() (map[string]int, error) {
} }
if _, ok := symbols[ins.Symbol]; ok { if _, ok := symbols[ins.Symbol]; ok {
return nil, errors.Errorf("duplicate symbol %s", ins.Symbol) return nil, fmt.Errorf("duplicate symbol %s", ins.Symbol)
} }
symbols[ins.Symbol] = currentPos symbols[ins.Symbol] = currentPos
@ -350,11 +400,11 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
num := 0 num := 0
for i, ins := range insns { for i, ins := range insns {
switch { switch {
case ins.OpCode.JumpOp() == Call && ins.Constant == -1: case ins.OpCode.JumpOp() == Call && ins.Src == PseudoCall && ins.Constant == -1:
// Rewrite bpf to bpf call // Rewrite bpf to bpf call
offset, ok := absoluteOffsets[ins.Reference] offset, ok := absoluteOffsets[ins.Reference]
if !ok { if !ok {
return errors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference) return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
} }
ins.Constant = int64(offset - num - 1) ins.Constant = int64(offset - num - 1)
@ -363,7 +413,7 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
// Rewrite jump to label // Rewrite jump to label
offset, ok := absoluteOffsets[ins.Reference] offset, ok := absoluteOffsets[ins.Reference]
if !ok { if !ok {
return errors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference) return fmt.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
} }
ins.Offset = int16(offset - num - 1) ins.Offset = int16(offset - num - 1)
@ -371,7 +421,7 @@ func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
n, err := ins.Marshal(w, bo) n, err := ins.Marshal(w, bo)
if err != nil { if err != nil {
return errors.Wrapf(err, "instruction %d", i) return fmt.Errorf("instruction %d: %w", i, err)
} }
num += int(n / InstructionSize) num += int(n / InstructionSize)
@ -388,16 +438,26 @@ type bpfInstruction struct {
type bpfRegisters uint8 type bpfRegisters uint8
func newBPFRegisters(dst, src Register) bpfRegisters { func newBPFRegisters(dst, src Register, bo binary.ByteOrder) (bpfRegisters, error) {
return bpfRegisters((src << 4) | (dst & 0xF)) switch bo {
case binary.LittleEndian:
return bpfRegisters((src << 4) | (dst & 0xF)), nil
case binary.BigEndian:
return bpfRegisters((dst << 4) | (src & 0xF)), nil
default:
return 0, fmt.Errorf("unrecognized ByteOrder %T", bo)
}
} }
func (r bpfRegisters) Dst() Register { func (r bpfRegisters) Unmarshal(bo binary.ByteOrder) (dst, src Register, err error) {
return Register(r & 0xF) switch bo {
} case binary.LittleEndian:
return Register(r & 0xF), Register(r >> 4), nil
func (r bpfRegisters) Src() Register { case binary.BigEndian:
return Register(r >> 4) return Register(r >> 4), Register(r & 0xf), nil
default:
return 0, 0, fmt.Errorf("unrecognized ByteOrder %T", bo)
}
} }
type unreferencedSymbolError struct { type unreferencedSymbolError struct {

View File

@ -95,7 +95,7 @@ func (op JumpOp) Label(label string) Instruction {
if op == Call { if op == Call {
return Instruction{ return Instruction{
OpCode: OpCode(JumpClass).SetJumpOp(Call), OpCode: OpCode(JumpClass).SetJumpOp(Call),
Src: R1, Src: PseudoCall,
Constant: -1, Constant: -1,
Reference: label, Reference: label,
} }

View File

@ -110,11 +110,26 @@ func LoadMapPtr(dst Register, fd int) Instruction {
return Instruction{ return Instruction{
OpCode: LoadImmOp(DWord), OpCode: LoadImmOp(DWord),
Dst: dst, Dst: dst,
Src: R1, Src: PseudoMapFD,
Constant: int64(fd), Constant: int64(fd),
} }
} }
// LoadMapValue stores a pointer to the value at a certain offset of a map.
func LoadMapValue(dst Register, fd int, offset uint32) Instruction {
if fd < 0 {
return Instruction{OpCode: InvalidOpCode}
}
fdAndOffset := (uint64(offset) << 32) | uint64(uint32(fd))
return Instruction{
OpCode: LoadImmOp(DWord),
Dst: dst,
Src: PseudoMapValue,
Constant: int64(fdAndOffset),
}
}
// LoadIndOp returns the OpCode for loading a value of given size from an sk_buff. // LoadIndOp returns the OpCode for loading a value of given size from an sk_buff.
func LoadIndOp(size Size) OpCode { func LoadIndOp(size Size) OpCode {
return OpCode(LdClass).SetMode(IndMode).SetSize(size) return OpCode(LdClass).SetMode(IndMode).SetSize(size)

View File

@ -225,7 +225,7 @@ func (op OpCode) String() string {
} }
default: default:
fmt.Fprintf(&f, "%#x", op) fmt.Fprintf(&f, "OpCode(%#x)", uint8(op))
} }
return f.String() return f.String()

View File

@ -33,6 +33,13 @@ const (
RFP = R10 RFP = R10
) )
// Pseudo registers used by 64bit loads and jumps
const (
PseudoMapFD = R1 // BPF_PSEUDO_MAP_FD
PseudoMapValue = R2 // BPF_PSEUDO_MAP_VALUE
PseudoCall = R1 // BPF_PSEUDO_CALL
)
func (r Register) String() string { func (r Register) String() string {
v := uint8(r) v := uint8(r)
if v == 10 { if v == 10 {

View File

@ -1,9 +1,13 @@
package ebpf package ebpf
import ( import (
"errors"
"fmt"
"math"
"github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf" "github.com/cilium/ebpf/internal/btf"
"github.com/pkg/errors"
) )
// CollectionOptions control loading a collection into the kernel. // CollectionOptions control loading a collection into the kernel.
@ -39,6 +43,89 @@ func (cs *CollectionSpec) Copy() *CollectionSpec {
return &cpy return &cpy
} }
// RewriteMaps replaces all references to specific maps.
//
// Use this function to use pre-existing maps instead of creating new ones
// when calling NewCollection. Any named maps are removed from CollectionSpec.Maps.
//
// Returns an error if a named map isn't used in at least one program.
func (cs *CollectionSpec) RewriteMaps(maps map[string]*Map) error {
for symbol, m := range maps {
// have we seen a program that uses this symbol / map
seen := false
fd := m.FD()
for progName, progSpec := range cs.Programs {
err := progSpec.Instructions.RewriteMapPtr(symbol, fd)
switch {
case err == nil:
seen = true
case asm.IsUnreferencedSymbol(err):
// Not all programs need to use the map
default:
return fmt.Errorf("program %s: %w", progName, err)
}
}
if !seen {
return fmt.Errorf("map %s not referenced by any programs", symbol)
}
// Prevent NewCollection from creating rewritten maps
delete(cs.Maps, symbol)
}
return nil
}
// RewriteConstants replaces the value of multiple constants.
//
// The constant must be defined like so in the C program:
//
// static volatile const type foobar;
// static volatile const type foobar = default;
//
// Replacement values must be of the same length as the C sizeof(type).
// If necessary, they are marshalled according to the same rules as
// map values.
//
// From Linux 5.5 the verifier will use constants to eliminate dead code.
//
// Returns an error if a constant doesn't exist.
func (cs *CollectionSpec) RewriteConstants(consts map[string]interface{}) error {
rodata := cs.Maps[".rodata"]
if rodata == nil {
return errors.New("missing .rodata section")
}
if rodata.BTF == nil {
return errors.New(".rodata section has no BTF")
}
if n := len(rodata.Contents); n != 1 {
return fmt.Errorf("expected one key in .rodata, found %d", n)
}
kv := rodata.Contents[0]
value, ok := kv.Value.([]byte)
if !ok {
return fmt.Errorf("first value in .rodata is %T not []byte", kv.Value)
}
buf := make([]byte, len(value))
copy(buf, value)
err := patchValue(buf, btf.MapValue(rodata.BTF), consts)
if err != nil {
return err
}
rodata.Contents[0] = MapKV{kv.Key, buf}
return nil
}
// Collection is a collection of Programs and Maps associated // Collection is a collection of Programs and Maps associated
// with their symbols // with their symbols
type Collection struct { type Collection struct {
@ -99,14 +186,14 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (col
var handle *btf.Handle var handle *btf.Handle
if mapSpec.BTF != nil { if mapSpec.BTF != nil {
handle, err = loadBTF(btf.MapSpec(mapSpec.BTF)) handle, err = loadBTF(btf.MapSpec(mapSpec.BTF))
if err != nil && !btf.IsNotSupported(err) { if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, err return nil, err
} }
} }
m, err := newMapWithBTF(mapSpec, handle) m, err := newMapWithBTF(mapSpec, handle)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "map %s", mapName) return nil, fmt.Errorf("map %s: %w", mapName, err)
} }
maps[mapName] = m maps[mapName] = m
} }
@ -116,37 +203,43 @@ func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (col
// Rewrite any reference to a valid map. // Rewrite any reference to a valid map.
for i := range progSpec.Instructions { for i := range progSpec.Instructions {
var ( ins := &progSpec.Instructions[i]
ins = &progSpec.Instructions[i]
m = maps[ins.Reference]
)
if ins.Reference == "" || m == nil { if ins.OpCode != asm.LoadImmOp(asm.DWord) || ins.Reference == "" {
continue continue
} }
if ins.Src == asm.R1 { if uint32(ins.Constant) != math.MaxUint32 {
// Don't overwrite maps already rewritten, users can // Don't overwrite maps already rewritten, users can
// rewrite programs in the spec themselves // rewrite programs in the spec themselves
continue continue
} }
m := maps[ins.Reference]
if m == nil {
return nil, fmt.Errorf("program %s: missing map %s", progName, ins.Reference)
}
fd := m.FD()
if fd < 0 {
return nil, fmt.Errorf("map %s: %w", ins.Reference, internal.ErrClosedFd)
}
if err := ins.RewriteMapPtr(m.FD()); err != nil { if err := ins.RewriteMapPtr(m.FD()); err != nil {
return nil, errors.Wrapf(err, "progam %s: map %s", progName, ins.Reference) return nil, fmt.Errorf("progam %s: map %s: %w", progName, ins.Reference, err)
} }
} }
var handle *btf.Handle var handle *btf.Handle
if progSpec.BTF != nil { if progSpec.BTF != nil {
handle, err = loadBTF(btf.ProgramSpec(progSpec.BTF)) handle, err = loadBTF(btf.ProgramSpec(progSpec.BTF))
if err != nil && !btf.IsNotSupported(err) { if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, err return nil, err
} }
} }
prog, err := newProgramWithBTF(progSpec, handle, opts.Programs) prog, err := newProgramWithBTF(progSpec, handle, opts.Programs)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "program %s", progName) return nil, fmt.Errorf("program %s: %w", progName, err)
} }
progs[progName] = prog progs[progName] = prog
} }

View File

@ -4,21 +4,23 @@ import (
"bytes" "bytes"
"debug/elf" "debug/elf"
"encoding/binary" "encoding/binary"
"errors"
"fmt"
"io" "io"
"math"
"os" "os"
"strings" "strings"
"github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf" "github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
) )
type elfCode struct { type elfCode struct {
*elf.File *elf.File
symbols []elf.Symbol symbols []elf.Symbol
symbolsPerSection map[elf.SectionIndex]map[uint64]string symbolsPerSection map[elf.SectionIndex]map[uint64]elf.Symbol
license string license string
version uint32 version uint32
} }
@ -32,7 +34,10 @@ func LoadCollectionSpec(file string) (*CollectionSpec, error) {
defer f.Close() defer f.Close()
spec, err := LoadCollectionSpecFromReader(f) spec, err := LoadCollectionSpecFromReader(f)
return spec, errors.Wrapf(err, "file %s", file) if err != nil {
return nil, fmt.Errorf("file %s: %w", file, err)
}
return spec, nil
} }
// LoadCollectionSpecFromReader parses an ELF file into a CollectionSpec. // LoadCollectionSpecFromReader parses an ELF file into a CollectionSpec.
@ -45,7 +50,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
symbols, err := f.Symbols() symbols, err := f.Symbols()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "load symbols") return nil, fmt.Errorf("load symbols: %v", err)
} }
ec := &elfCode{f, symbols, symbolsPerSection(symbols), "", 0} ec := &elfCode{f, symbols, symbolsPerSection(symbols), "", 0}
@ -57,6 +62,7 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
progSections = make(map[elf.SectionIndex]*elf.Section) progSections = make(map[elf.SectionIndex]*elf.Section)
relSections = make(map[elf.SectionIndex]*elf.Section) relSections = make(map[elf.SectionIndex]*elf.Section)
mapSections = make(map[elf.SectionIndex]*elf.Section) mapSections = make(map[elf.SectionIndex]*elf.Section)
dataSections = make(map[elf.SectionIndex]*elf.Section)
) )
for i, sec := range ec.Sections { for i, sec := range ec.Sections {
@ -69,15 +75,17 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
mapSections[elf.SectionIndex(i)] = sec mapSections[elf.SectionIndex(i)] = sec
case sec.Name == ".maps": case sec.Name == ".maps":
btfMaps[elf.SectionIndex(i)] = sec btfMaps[elf.SectionIndex(i)] = sec
case sec.Name == ".bss" || sec.Name == ".rodata" || sec.Name == ".data":
dataSections[elf.SectionIndex(i)] = sec
case sec.Type == elf.SHT_REL: case sec.Type == elf.SHT_REL:
if int(sec.Info) >= len(ec.Sections) { if int(sec.Info) >= len(ec.Sections) {
return nil, errors.Errorf("found relocation section %v for missing section %v", i, sec.Info) return nil, fmt.Errorf("found relocation section %v for missing section %v", i, sec.Info)
} }
// Store relocations under the section index of the target // Store relocations under the section index of the target
idx := elf.SectionIndex(sec.Info) idx := elf.SectionIndex(sec.Info)
if relSections[idx] != nil { if relSections[idx] != nil {
return nil, errors.Errorf("section %d has multiple relocation sections", sec.Info) return nil, fmt.Errorf("section %d has multiple relocation sections", sec.Info)
} }
relSections[idx] = sec relSections[idx] = sec
case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0: case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0:
@ -87,34 +95,52 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
ec.license, err = loadLicense(licenseSection) ec.license, err = loadLicense(licenseSection)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "load license") return nil, fmt.Errorf("load license: %w", err)
} }
ec.version, err = loadVersion(versionSection, ec.ByteOrder) ec.version, err = loadVersion(versionSection, ec.ByteOrder)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "load version") return nil, fmt.Errorf("load version: %w", err)
} }
btf, err := btf.LoadSpecFromReader(rd) btfSpec, err := btf.LoadSpecFromReader(rd)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "load BTF") return nil, fmt.Errorf("load BTF: %w", err)
}
relocations, referencedSections, err := ec.loadRelocations(relSections)
if err != nil {
return nil, fmt.Errorf("load relocations: %w", err)
} }
maps := make(map[string]*MapSpec) maps := make(map[string]*MapSpec)
if err := ec.loadMaps(maps, mapSections); err != nil { if err := ec.loadMaps(maps, mapSections); err != nil {
return nil, errors.Wrap(err, "load maps") return nil, fmt.Errorf("load maps: %w", err)
} }
if len(btfMaps) > 0 { if len(btfMaps) > 0 {
if err := ec.loadBTFMaps(maps, btfMaps, btf); err != nil { if err := ec.loadBTFMaps(maps, btfMaps, btfSpec); err != nil {
return nil, errors.Wrap(err, "load BTF maps") return nil, fmt.Errorf("load BTF maps: %w", err)
} }
} }
progs, err := ec.loadPrograms(progSections, relSections, btf) if len(dataSections) > 0 {
for idx := range dataSections {
if !referencedSections[idx] {
// Prune data sections which are not referenced by any
// instructions.
delete(dataSections, idx)
}
}
if err := ec.loadDataSections(maps, dataSections, btfSpec); err != nil {
return nil, fmt.Errorf("load data sections: %w", err)
}
}
progs, err := ec.loadPrograms(progSections, relocations, btfSpec)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "load programs") return nil, fmt.Errorf("load programs: %w", err)
} }
return &CollectionSpec{maps, progs}, nil return &CollectionSpec{maps, progs}, nil
@ -122,11 +148,12 @@ func LoadCollectionSpecFromReader(rd io.ReaderAt) (*CollectionSpec, error) {
func loadLicense(sec *elf.Section) (string, error) { func loadLicense(sec *elf.Section) (string, error) {
if sec == nil { if sec == nil {
return "", errors.Errorf("missing license section") return "", nil
} }
data, err := sec.Data() data, err := sec.Data()
if err != nil { if err != nil {
return "", errors.Wrapf(err, "section %s", sec.Name) return "", fmt.Errorf("section %s: %v", sec.Name, err)
} }
return string(bytes.TrimRight(data, "\000")), nil return string(bytes.TrimRight(data, "\000")), nil
} }
@ -137,52 +164,51 @@ func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) {
} }
var version uint32 var version uint32
err := binary.Read(sec.Open(), bo, &version) if err := binary.Read(sec.Open(), bo, &version); err != nil {
return version, errors.Wrapf(err, "section %s", sec.Name) return 0, fmt.Errorf("section %s: %v", sec.Name, err)
}
return version, nil
} }
func (ec *elfCode) loadPrograms(progSections, relSections map[elf.SectionIndex]*elf.Section, btf *btf.Spec) (map[string]*ProgramSpec, error) { func (ec *elfCode) loadPrograms(progSections map[elf.SectionIndex]*elf.Section, relocations map[elf.SectionIndex]map[uint64]elf.Symbol, btfSpec *btf.Spec) (map[string]*ProgramSpec, error) {
var ( var (
progs []*ProgramSpec progs []*ProgramSpec
libs []*ProgramSpec libs []*ProgramSpec
) )
for idx, prog := range progSections { for idx, sec := range progSections {
syms := ec.symbolsPerSection[idx] syms := ec.symbolsPerSection[idx]
if len(syms) == 0 { if len(syms) == 0 {
return nil, errors.Errorf("section %v: missing symbols", prog.Name) return nil, fmt.Errorf("section %v: missing symbols", sec.Name)
} }
funcSym := syms[0] funcSym, ok := syms[0]
if funcSym == "" { if !ok {
return nil, errors.Errorf("section %v: no label at start", prog.Name) return nil, fmt.Errorf("section %v: no label at start", sec.Name)
} }
rels, err := ec.loadRelocations(relSections[idx]) insns, length, err := ec.loadInstructions(sec, syms, relocations[idx])
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "program %s: can't load relocations", funcSym) return nil, fmt.Errorf("program %s: can't unmarshal instructions: %w", funcSym.Name, err)
} }
insns, length, err := ec.loadInstructions(prog, syms, rels) progType, attachType, attachTo := getProgType(sec.Name)
if err != nil {
return nil, errors.Wrapf(err, "program %s: can't unmarshal instructions", funcSym)
}
progType, attachType := getProgType(prog.Name)
spec := &ProgramSpec{ spec := &ProgramSpec{
Name: funcSym, Name: funcSym.Name,
Type: progType, Type: progType,
AttachType: attachType, AttachType: attachType,
AttachTo: attachTo,
License: ec.license, License: ec.license,
KernelVersion: ec.version, KernelVersion: ec.version,
Instructions: insns, Instructions: insns,
ByteOrder: ec.ByteOrder,
} }
if btf != nil { if btfSpec != nil {
spec.BTF, err = btf.Program(prog.Name, length) spec.BTF, err = btfSpec.Program(sec.Name, length)
if err != nil { if err != nil && !errors.Is(err, btf.ErrNoExtendedInfo) {
return nil, errors.Wrapf(err, "BTF for section %s (program %s)", prog.Name, funcSym) return nil, fmt.Errorf("program %s: %w", funcSym.Name, err)
} }
} }
@ -200,7 +226,7 @@ func (ec *elfCode) loadPrograms(progSections, relSections map[elf.SectionIndex]*
for _, prog := range progs { for _, prog := range progs {
err := link(prog, libs) err := link(prog, libs)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "program %s", prog.Name) return nil, fmt.Errorf("program %s: %w", prog.Name, err)
} }
res[prog.Name] = prog res[prog.Name] = prog
} }
@ -208,39 +234,158 @@ func (ec *elfCode) loadPrograms(progSections, relSections map[elf.SectionIndex]*
return res, nil return res, nil
} }
func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations map[uint64]string) (asm.Instructions, uint64, error) { func (ec *elfCode) loadInstructions(section *elf.Section, symbols, relocations map[uint64]elf.Symbol) (asm.Instructions, uint64, error) {
var ( var (
r = section.Open() r = section.Open()
insns asm.Instructions insns asm.Instructions
ins asm.Instruction
offset uint64 offset uint64
) )
for { for {
var ins asm.Instruction
n, err := ins.Unmarshal(r, ec.ByteOrder) n, err := ins.Unmarshal(r, ec.ByteOrder)
if err == io.EOF { if err == io.EOF {
return insns, offset, nil return insns, offset, nil
} }
if err != nil { if err != nil {
return nil, 0, errors.Wrapf(err, "offset %d", offset) return nil, 0, fmt.Errorf("offset %d: %w", offset, err)
} }
ins.Symbol = symbols[offset] ins.Symbol = symbols[offset].Name
ins.Reference = relocations[offset]
if rel, ok := relocations[offset]; ok {
if err = ec.relocateInstruction(&ins, rel); err != nil {
return nil, 0, fmt.Errorf("offset %d: can't relocate instruction: %w", offset, err)
}
}
insns = append(insns, ins) insns = append(insns, ins)
offset += n offset += n
} }
} }
func (ec *elfCode) relocateInstruction(ins *asm.Instruction, rel elf.Symbol) error {
var (
typ = elf.ST_TYPE(rel.Info)
bind = elf.ST_BIND(rel.Info)
name = rel.Name
)
if typ == elf.STT_SECTION {
// Symbols with section type do not have a name set. Get it
// from the section itself.
idx := int(rel.Section)
if idx > len(ec.Sections) {
return errors.New("out-of-bounds section index")
}
name = ec.Sections[idx].Name
}
outer:
switch {
case ins.OpCode == asm.LoadImmOp(asm.DWord):
// There are two distinct types of a load from a map:
// a direct one, where the value is extracted without
// a call to map_lookup_elem in eBPF, and an indirect one
// that goes via the helper. They are distinguished by
// different relocations.
switch typ {
case elf.STT_SECTION:
// This is a direct load since the referenced symbol is a
// section. Weirdly, the offset of the real symbol in the
// section is encoded in the instruction stream.
if bind != elf.STB_LOCAL {
return fmt.Errorf("direct load: %s: unsupported relocation %s", name, bind)
}
// For some reason, clang encodes the offset of the symbol its
// section in the first basic BPF instruction, while the kernel
// expects it in the second one.
ins.Constant <<= 32
ins.Src = asm.PseudoMapValue
case elf.STT_NOTYPE:
if bind == elf.STB_GLOBAL && rel.Section == elf.SHN_UNDEF {
// This is a relocation generated by inline assembly.
// We can't do more than assigning ins.Reference.
break outer
}
// This is an ELF generated on clang < 8, which doesn't tag
// relocations appropriately.
fallthrough
case elf.STT_OBJECT:
if bind != elf.STB_GLOBAL {
return fmt.Errorf("load: %s: unsupported binding: %s", name, bind)
}
ins.Src = asm.PseudoMapFD
default:
return fmt.Errorf("load: %s: unsupported relocation: %s", name, typ)
}
// Mark the instruction as needing an update when creating the
// collection.
if err := ins.RewriteMapPtr(-1); err != nil {
return err
}
case ins.OpCode.JumpOp() == asm.Call:
if ins.Src != asm.PseudoCall {
return fmt.Errorf("call: %s: incorrect source register", name)
}
switch typ {
case elf.STT_NOTYPE, elf.STT_FUNC:
if bind != elf.STB_GLOBAL {
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
}
case elf.STT_SECTION:
if bind != elf.STB_LOCAL {
return fmt.Errorf("call: %s: unsupported binding: %s", name, bind)
}
// The function we want to call is in the indicated section,
// at the offset encoded in the instruction itself. Reverse
// the calculation to find the real function we're looking for.
// A value of -1 references the first instruction in the section.
offset := int64(int32(ins.Constant)+1) * asm.InstructionSize
if offset < 0 {
return fmt.Errorf("call: %s: invalid offset %d", name, offset)
}
sym, ok := ec.symbolsPerSection[rel.Section][uint64(offset)]
if !ok {
return fmt.Errorf("call: %s: no symbol at offset %d", name, offset)
}
ins.Constant = -1
name = sym.Name
default:
return fmt.Errorf("call: %s: invalid symbol type %s", name, typ)
}
default:
return fmt.Errorf("relocation for unsupported instruction: %s", ins.OpCode)
}
ins.Reference = name
return nil
}
func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section) error { func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section) error {
for idx, sec := range mapSections { for idx, sec := range mapSections {
syms := ec.symbolsPerSection[idx] syms := ec.symbolsPerSection[idx]
if len(syms) == 0 { if len(syms) == 0 {
return errors.Errorf("section %v: no symbols", sec.Name) return fmt.Errorf("section %v: no symbols", sec.Name)
} }
if sec.Size%uint64(len(syms)) != 0 { if sec.Size%uint64(len(syms)) != 0 {
return errors.Errorf("section %v: map descriptors are not of equal size", sec.Name) return fmt.Errorf("section %v: map descriptors are not of equal size", sec.Name)
} }
var ( var (
@ -248,36 +393,38 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.Sectio
size = sec.Size / uint64(len(syms)) size = sec.Size / uint64(len(syms))
) )
for i, offset := 0, uint64(0); i < len(syms); i, offset = i+1, offset+size { for i, offset := 0, uint64(0); i < len(syms); i, offset = i+1, offset+size {
mapSym := syms[offset] mapSym, ok := syms[offset]
if mapSym == "" { if !ok {
return errors.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset) return fmt.Errorf("section %s: missing symbol for map at offset %d", sec.Name, offset)
} }
if maps[mapSym] != nil { if maps[mapSym.Name] != nil {
return errors.Errorf("section %v: map %v already exists", sec.Name, mapSym) return fmt.Errorf("section %v: map %v already exists", sec.Name, mapSym)
} }
lr := io.LimitReader(r, int64(size)) lr := io.LimitReader(r, int64(size))
var spec MapSpec spec := MapSpec{
Name: SanitizeName(mapSym.Name, -1),
}
switch { switch {
case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil: case binary.Read(lr, ec.ByteOrder, &spec.Type) != nil:
return errors.Errorf("map %v: missing type", mapSym) return fmt.Errorf("map %v: missing type", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil: case binary.Read(lr, ec.ByteOrder, &spec.KeySize) != nil:
return errors.Errorf("map %v: missing key size", mapSym) return fmt.Errorf("map %v: missing key size", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil: case binary.Read(lr, ec.ByteOrder, &spec.ValueSize) != nil:
return errors.Errorf("map %v: missing value size", mapSym) return fmt.Errorf("map %v: missing value size", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil: case binary.Read(lr, ec.ByteOrder, &spec.MaxEntries) != nil:
return errors.Errorf("map %v: missing max entries", mapSym) return fmt.Errorf("map %v: missing max entries", mapSym)
case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil: case binary.Read(lr, ec.ByteOrder, &spec.Flags) != nil:
return errors.Errorf("map %v: missing flags", mapSym) return fmt.Errorf("map %v: missing flags", mapSym)
} }
if _, err := io.Copy(internal.DiscardZeroes{}, lr); err != nil { if _, err := io.Copy(internal.DiscardZeroes{}, lr); err != nil {
return errors.Errorf("map %v: unknown and non-zero fields in definition", mapSym) return fmt.Errorf("map %v: unknown and non-zero fields in definition", mapSym)
} }
maps[mapSym] = &spec maps[mapSym.Name] = &spec
} }
} }
@ -285,85 +432,117 @@ func (ec *elfCode) loadMaps(maps map[string]*MapSpec, mapSections map[elf.Sectio
} }
func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error { func (ec *elfCode) loadBTFMaps(maps map[string]*MapSpec, mapSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error {
if spec == nil { if spec == nil {
return errors.Errorf("missing BTF") return fmt.Errorf("missing BTF")
} }
for idx, sec := range mapSections { for idx, sec := range mapSections {
syms := ec.symbolsPerSection[idx] syms := ec.symbolsPerSection[idx]
if len(syms) == 0 { if len(syms) == 0 {
return errors.Errorf("section %v: no symbols", sec.Name) return fmt.Errorf("section %v: no symbols", sec.Name)
} }
for _, sym := range syms { for _, sym := range syms {
if maps[sym] != nil { name := sym.Name
return errors.Errorf("section %v: map %v already exists", sec.Name, sym) if maps[name] != nil {
return fmt.Errorf("section %v: map %v already exists", sec.Name, sym)
} }
btfMap, err := spec.Map(sym) mapSpec, err := mapSpecFromBTF(spec, name)
if err != nil { if err != nil {
return errors.Wrapf(err, "map %v: can't get BTF", sym) return fmt.Errorf("map %v: %w", name, err)
} }
spec, err := mapSpecFromBTF(btfMap) maps[name] = mapSpec
if err != nil {
return errors.Wrapf(err, "map %v", sym)
}
maps[sym] = spec
} }
} }
return nil return nil
} }
func mapSpecFromBTF(btfMap *btf.Map) (*MapSpec, error) { func mapSpecFromBTF(spec *btf.Spec, name string) (*MapSpec, error) {
btfMap, btfMapMembers, err := spec.Map(name)
if err != nil {
return nil, fmt.Errorf("can't get BTF: %w", err)
}
keyType := btf.MapKey(btfMap)
size, err := btf.Sizeof(keyType)
if err != nil {
return nil, fmt.Errorf("can't get size of BTF key: %w", err)
}
keySize := uint32(size)
valueType := btf.MapValue(btfMap)
size, err = btf.Sizeof(valueType)
if err != nil {
return nil, fmt.Errorf("can't get size of BTF value: %w", err)
}
valueSize := uint32(size)
var ( var (
mapType, flags, maxEntries uint32 mapType, flags, maxEntries uint32
err error
) )
for _, member := range btf.MapType(btfMap).Members { for _, member := range btfMapMembers {
switch member.Name { switch member.Name {
case "type": case "type":
mapType, err = uintFromBTF(member.Type) mapType, err = uintFromBTF(member.Type)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't get type") return nil, fmt.Errorf("can't get type: %w", err)
} }
case "map_flags": case "map_flags":
flags, err = uintFromBTF(member.Type) flags, err = uintFromBTF(member.Type)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't get BTF map flags") return nil, fmt.Errorf("can't get BTF map flags: %w", err)
} }
case "max_entries": case "max_entries":
maxEntries, err = uintFromBTF(member.Type) maxEntries, err = uintFromBTF(member.Type)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't get BTF map max entries") return nil, fmt.Errorf("can't get BTF map max entries: %w", err)
} }
case "key": case "key_size":
case "value": if _, isVoid := keyType.(*btf.Void); !isVoid {
return nil, errors.New("both key and key_size given")
}
keySize, err = uintFromBTF(member.Type)
if err != nil {
return nil, fmt.Errorf("can't get BTF key size: %w", err)
}
case "value_size":
if _, isVoid := valueType.(*btf.Void); !isVoid {
return nil, errors.New("both value and value_size given")
}
valueSize, err = uintFromBTF(member.Type)
if err != nil {
return nil, fmt.Errorf("can't get BTF value size: %w", err)
}
case "pinning":
pinning, err := uintFromBTF(member.Type)
if err != nil {
return nil, fmt.Errorf("can't get pinning: %w", err)
}
if pinning != 0 {
return nil, fmt.Errorf("'pinning' attribute not supported: %w", ErrNotSupported)
}
case "key", "value":
default: default:
return nil, errors.Errorf("unrecognized field %s in BTF map definition", member.Name) return nil, fmt.Errorf("unrecognized field %s in BTF map definition", member.Name)
} }
} }
keySize, err := btf.Sizeof(btf.MapKey(btfMap))
if err != nil {
return nil, errors.Wrap(err, "can't get size of BTF key")
}
valueSize, err := btf.Sizeof(btf.MapValue(btfMap))
if err != nil {
return nil, errors.Wrap(err, "can't get size of BTF value")
}
return &MapSpec{ return &MapSpec{
Type: MapType(mapType), Type: MapType(mapType),
KeySize: uint32(keySize), KeySize: keySize,
ValueSize: uint32(valueSize), ValueSize: valueSize,
MaxEntries: maxEntries, MaxEntries: maxEntries,
Flags: flags, Flags: flags,
BTF: btfMap, BTF: btfMap,
@ -375,103 +554,134 @@ func mapSpecFromBTF(btfMap *btf.Map) (*MapSpec, error) {
func uintFromBTF(typ btf.Type) (uint32, error) { func uintFromBTF(typ btf.Type) (uint32, error) {
ptr, ok := typ.(*btf.Pointer) ptr, ok := typ.(*btf.Pointer)
if !ok { if !ok {
return 0, errors.Errorf("not a pointer: %v", typ) return 0, fmt.Errorf("not a pointer: %v", typ)
} }
arr, ok := ptr.Target.(*btf.Array) arr, ok := ptr.Target.(*btf.Array)
if !ok { if !ok {
return 0, errors.Errorf("not a pointer to array: %v", typ) return 0, fmt.Errorf("not a pointer to array: %v", typ)
} }
return arr.Nelems, nil return arr.Nelems, nil
} }
func getProgType(v string) (ProgramType, AttachType) { func (ec *elfCode) loadDataSections(maps map[string]*MapSpec, dataSections map[elf.SectionIndex]*elf.Section, spec *btf.Spec) error {
types := map[string]ProgramType{ if spec == nil {
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c#n3568 return errors.New("data sections require BTF, make sure all consts are marked as static")
"socket": SocketFilter,
"seccomp": SocketFilter,
"kprobe/": Kprobe,
"uprobe/": Kprobe,
"kretprobe/": Kprobe,
"uretprobe/": Kprobe,
"tracepoint/": TracePoint,
"raw_tracepoint/": RawTracepoint,
"xdp": XDP,
"perf_event": PerfEvent,
"lwt_in": LWTIn,
"lwt_out": LWTOut,
"lwt_xmit": LWTXmit,
"lwt_seg6local": LWTSeg6Local,
"sockops": SockOps,
"sk_skb": SkSKB,
"sk_msg": SkMsg,
"lirc_mode2": LircMode2,
"flow_dissector": FlowDissector,
"cgroup_skb/": CGroupSKB,
"cgroup/dev": CGroupDevice,
"cgroup/skb": CGroupSKB,
"cgroup/sock": CGroupSock,
"cgroup/post_bind": CGroupSock,
"cgroup/bind": CGroupSockAddr,
"cgroup/connect": CGroupSockAddr,
"cgroup/sendmsg": CGroupSockAddr,
"cgroup/recvmsg": CGroupSockAddr,
"cgroup/sysctl": CGroupSysctl,
"cgroup/getsockopt": CGroupSockopt,
"cgroup/setsockopt": CGroupSockopt,
"classifier": SchedCLS,
"action": SchedACT,
}
attachTypes := map[string]AttachType{
"cgroup_skb/ingress": AttachCGroupInetIngress,
"cgroup_skb/egress": AttachCGroupInetEgress,
"cgroup/sock": AttachCGroupInetSockCreate,
"cgroup/post_bind4": AttachCGroupInet4PostBind,
"cgroup/post_bind6": AttachCGroupInet6PostBind,
"cgroup/dev": AttachCGroupDevice,
"sockops": AttachCGroupSockOps,
"sk_skb/stream_parser": AttachSkSKBStreamParser,
"sk_skb/stream_verdict": AttachSkSKBStreamVerdict,
"sk_msg": AttachSkSKBStreamVerdict,
"lirc_mode2": AttachLircMode2,
"flow_dissector": AttachFlowDissector,
"cgroup/bind4": AttachCGroupInet4Bind,
"cgroup/bind6": AttachCGroupInet6Bind,
"cgroup/connect4": AttachCGroupInet4Connect,
"cgroup/connect6": AttachCGroupInet6Connect,
"cgroup/sendmsg4": AttachCGroupUDP4Sendmsg,
"cgroup/sendmsg6": AttachCGroupUDP6Sendmsg,
"cgroup/recvmsg4": AttachCGroupUDP4Recvmsg,
"cgroup/recvmsg6": AttachCGroupUDP6Recvmsg,
"cgroup/sysctl": AttachCGroupSysctl,
"cgroup/getsockopt": AttachCGroupGetsockopt,
"cgroup/setsockopt": AttachCGroupSetsockopt,
}
attachType := AttachNone
for k, t := range attachTypes {
if strings.HasPrefix(v, k) {
attachType = t
}
} }
for k, t := range types { for _, sec := range dataSections {
if strings.HasPrefix(v, k) { btfMap, err := spec.Datasec(sec.Name)
return t, attachType if err != nil {
return err
} }
data, err := sec.Data()
if err != nil {
return fmt.Errorf("data section %s: can't get contents: %w", sec.Name, err)
} }
return UnspecifiedProgram, AttachNone
if uint64(len(data)) > math.MaxUint32 {
return fmt.Errorf("data section %s: contents exceed maximum size", sec.Name)
}
mapSpec := &MapSpec{
Name: SanitizeName(sec.Name, -1),
Type: Array,
KeySize: 4,
ValueSize: uint32(len(data)),
MaxEntries: 1,
Contents: []MapKV{{uint32(0), data}},
BTF: btfMap,
}
switch sec.Name {
case ".rodata":
mapSpec.Flags = unix.BPF_F_RDONLY_PROG
mapSpec.Freeze = true
case ".bss":
// The kernel already zero-initializes the map
mapSpec.Contents = nil
}
maps[sec.Name] = mapSpec
}
return nil
} }
func (ec *elfCode) loadRelocations(sec *elf.Section) (map[uint64]string, error) { func getProgType(sectionName string) (ProgramType, AttachType, string) {
rels := make(map[uint64]string) types := map[string]struct {
if sec == nil { progType ProgramType
return rels, nil attachType AttachType
}{
// From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c
"socket": {SocketFilter, AttachNone},
"seccomp": {SocketFilter, AttachNone},
"kprobe/": {Kprobe, AttachNone},
"uprobe/": {Kprobe, AttachNone},
"kretprobe/": {Kprobe, AttachNone},
"uretprobe/": {Kprobe, AttachNone},
"tracepoint/": {TracePoint, AttachNone},
"raw_tracepoint/": {RawTracepoint, AttachNone},
"xdp": {XDP, AttachNone},
"perf_event": {PerfEvent, AttachNone},
"lwt_in": {LWTIn, AttachNone},
"lwt_out": {LWTOut, AttachNone},
"lwt_xmit": {LWTXmit, AttachNone},
"lwt_seg6local": {LWTSeg6Local, AttachNone},
"sockops": {SockOps, AttachCGroupSockOps},
"sk_skb/stream_parser": {SkSKB, AttachSkSKBStreamParser},
"sk_skb/stream_verdict": {SkSKB, AttachSkSKBStreamParser},
"sk_msg": {SkMsg, AttachSkSKBStreamVerdict},
"lirc_mode2": {LircMode2, AttachLircMode2},
"flow_dissector": {FlowDissector, AttachFlowDissector},
"iter/": {Tracing, AttachTraceIter},
"cgroup_skb/ingress": {CGroupSKB, AttachCGroupInetIngress},
"cgroup_skb/egress": {CGroupSKB, AttachCGroupInetEgress},
"cgroup/dev": {CGroupDevice, AttachCGroupDevice},
"cgroup/skb": {CGroupSKB, AttachNone},
"cgroup/sock": {CGroupSock, AttachCGroupInetSockCreate},
"cgroup/post_bind4": {CGroupSock, AttachCGroupInet4PostBind},
"cgroup/post_bind6": {CGroupSock, AttachCGroupInet6PostBind},
"cgroup/bind4": {CGroupSockAddr, AttachCGroupInet4Bind},
"cgroup/bind6": {CGroupSockAddr, AttachCGroupInet6Bind},
"cgroup/connect4": {CGroupSockAddr, AttachCGroupInet4Connect},
"cgroup/connect6": {CGroupSockAddr, AttachCGroupInet6Connect},
"cgroup/sendmsg4": {CGroupSockAddr, AttachCGroupUDP4Sendmsg},
"cgroup/sendmsg6": {CGroupSockAddr, AttachCGroupUDP6Sendmsg},
"cgroup/recvmsg4": {CGroupSockAddr, AttachCGroupUDP4Recvmsg},
"cgroup/recvmsg6": {CGroupSockAddr, AttachCGroupUDP6Recvmsg},
"cgroup/sysctl": {CGroupSysctl, AttachCGroupSysctl},
"cgroup/getsockopt": {CGroupSockopt, AttachCGroupGetsockopt},
"cgroup/setsockopt": {CGroupSockopt, AttachCGroupSetsockopt},
"classifier": {SchedCLS, AttachNone},
"action": {SchedACT, AttachNone},
} }
for prefix, t := range types {
if !strings.HasPrefix(sectionName, prefix) {
continue
}
if !strings.HasSuffix(prefix, "/") {
return t.progType, t.attachType, ""
}
return t.progType, t.attachType, sectionName[len(prefix):]
}
return UnspecifiedProgram, AttachNone, ""
}
func (ec *elfCode) loadRelocations(sections map[elf.SectionIndex]*elf.Section) (map[elf.SectionIndex]map[uint64]elf.Symbol, map[elf.SectionIndex]bool, error) {
result := make(map[elf.SectionIndex]map[uint64]elf.Symbol)
targets := make(map[elf.SectionIndex]bool)
for idx, sec := range sections {
rels := make(map[uint64]elf.Symbol)
if sec.Entsize < 16 { if sec.Entsize < 16 {
return nil, errors.New("rels are less than 16 bytes") return nil, nil, fmt.Errorf("section %s: relocations are less than 16 bytes", sec.Name)
} }
r := sec.Open() r := sec.Open()
@ -480,22 +690,27 @@ func (ec *elfCode) loadRelocations(sec *elf.Section) (map[uint64]string, error)
var rel elf.Rel64 var rel elf.Rel64
if binary.Read(ent, ec.ByteOrder, &rel) != nil { if binary.Read(ent, ec.ByteOrder, &rel) != nil {
return nil, errors.Errorf("can't parse relocation at offset %v", off) return nil, nil, fmt.Errorf("can't parse relocation at offset %v", off)
} }
symNo := int(elf.R_SYM64(rel.Info) - 1) symNo := int(elf.R_SYM64(rel.Info) - 1)
if symNo >= len(ec.symbols) { if symNo >= len(ec.symbols) {
return nil, errors.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo) return nil, nil, fmt.Errorf("relocation at offset %d: symbol %v doesnt exist", off, symNo)
} }
rels[rel.Off] = ec.symbols[symNo].Name symbol := ec.symbols[symNo]
targets[symbol.Section] = true
rels[rel.Off] = ec.symbols[symNo]
} }
return rels, nil
result[idx] = rels
}
return result, targets, nil
} }
func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]string { func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]elf.Symbol {
result := make(map[elf.SectionIndex]map[uint64]string) result := make(map[elf.SectionIndex]map[uint64]elf.Symbol)
for i, sym := range symbols { for _, sym := range symbols {
switch elf.ST_TYPE(sym.Info) { switch elf.ST_TYPE(sym.Info) {
case elf.STT_NOTYPE: case elf.STT_NOTYPE:
// Older versions of LLVM doesn't tag // Older versions of LLVM doesn't tag
@ -509,15 +724,19 @@ func symbolsPerSection(symbols []elf.Symbol) map[elf.SectionIndex]map[uint64]str
continue continue
} }
if sym.Section == elf.SHN_UNDEF || sym.Section >= elf.SHN_LORESERVE {
continue
}
if sym.Name == "" { if sym.Name == "" {
continue continue
} }
idx := sym.Section idx := sym.Section
if _, ok := result[idx]; !ok { if _, ok := result[idx]; !ok {
result[idx] = make(map[uint64]string) result[idx] = make(map[uint64]elf.Symbol)
} }
result[idx][sym.Value] = symbols[i].Name result[idx][sym.Value] = sym
} }
return result return result
} }

View File

@ -1,8 +1,5 @@
module github.com/cilium/ebpf module github.com/cilium/ebpf
go 1.12 go 1.13
require ( require golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9
github.com/pkg/errors v0.8.1
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7
)

View File

@ -4,20 +4,29 @@ import (
"bytes" "bytes"
"debug/elf" "debug/elf"
"encoding/binary" "encoding/binary"
"errors"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math" "math"
"os"
"reflect" "reflect"
"sync"
"unsafe" "unsafe"
"github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/unix" "github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
) )
const btfMagic = 0xeB9F const btfMagic = 0xeB9F
// Errors returned by BTF functions.
var (
ErrNotSupported = internal.ErrNotSupported
ErrNotFound = errors.New("not found")
ErrNoExtendedInfo = errors.New("no extended info")
)
// Spec represents decoded BTF. // Spec represents decoded BTF.
type Spec struct { type Spec struct {
rawTypes []rawType rawTypes []rawType
@ -25,6 +34,7 @@ type Spec struct {
types map[string][]Type types map[string][]Type
funcInfos map[string]extInfo funcInfos map[string]extInfo
lineInfos map[string]extInfo lineInfos map[string]extInfo
byteOrder binary.ByteOrder
} }
type btfHeader struct { type btfHeader struct {
@ -52,6 +62,7 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
var ( var (
btfSection *elf.Section btfSection *elf.Section
btfExtSection *elf.Section btfExtSection *elf.Section
sectionSizes = make(map[string]uint32)
) )
for _, sec := range file.Sections { for _, sec := range file.Sections {
@ -60,6 +71,16 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
btfSection = sec btfSection = sec
case ".BTF.ext": case ".BTF.ext":
btfExtSection = sec btfExtSection = sec
default:
if sec.Type != elf.SHT_PROGBITS && sec.Type != elf.SHT_NOBITS {
break
}
if sec.Size > math.MaxUint32 {
return nil, fmt.Errorf("section %s exceeds maximum size", sec.Name)
}
sectionSizes[sec.Name] = uint32(sec.Size)
} }
} }
@ -67,74 +88,59 @@ func LoadSpecFromReader(rd io.ReaderAt) (*Spec, error) {
return nil, nil return nil, nil
} }
spec, err := parseBTF(btfSection.Open(), file.ByteOrder) symbols, err := file.Symbols()
if err != nil {
return nil, fmt.Errorf("can't read symbols: %v", err)
}
variableOffsets := make(map[variable]uint32)
for _, symbol := range symbols {
if idx := symbol.Section; idx >= elf.SHN_LORESERVE && idx <= elf.SHN_HIRESERVE {
// Ignore things like SHN_ABS
continue
}
secName := file.Sections[symbol.Section].Name
if _, ok := sectionSizes[secName]; !ok {
continue
}
if symbol.Value > math.MaxUint32 {
return nil, fmt.Errorf("section %s: symbol %s: size exceeds maximum", secName, symbol.Name)
}
variableOffsets[variable{secName, symbol.Name}] = uint32(symbol.Value)
}
spec, err := loadNakedSpec(btfSection.Open(), file.ByteOrder, sectionSizes, variableOffsets)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if btfExtSection != nil { if btfExtSection == nil {
return spec, nil
}
spec.funcInfos, spec.lineInfos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings) spec.funcInfos, spec.lineInfos, err = parseExtInfos(btfExtSection.Open(), file.ByteOrder, spec.strings)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't read ext info") return nil, fmt.Errorf("can't read ext info: %w", err)
}
} }
return spec, nil return spec, nil
} }
func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) (*Spec, error) { func loadNakedSpec(btf io.ReadSeeker, bo binary.ByteOrder, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) (*Spec, error) {
rawBTF, err := ioutil.ReadAll(btf) rawTypes, rawStrings, err := parseBTF(btf, bo)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't read BTF") return nil, err
} }
rd := bytes.NewReader(rawBTF) err = fixupDatasec(rawTypes, rawStrings, sectionSizes, variableOffsets)
var header btfHeader
if err := binary.Read(rd, bo, &header); err != nil {
return nil, errors.Wrap(err, "can't read header")
}
if header.Magic != btfMagic {
return nil, errors.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, errors.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, errors.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, errors.New("header is too short")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, rd, remainder); err != nil {
return nil, errors.Wrap(err, "header padding")
}
if _, err := rd.Seek(int64(header.HdrLen+header.StringOff), io.SeekStart); err != nil {
return nil, errors.Wrap(err, "can't seek to start of string section")
}
strings, err := readStringTable(io.LimitReader(rd, int64(header.StringLen)))
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't read type names") return nil, err
} }
if _, err := rd.Seek(int64(header.HdrLen+header.TypeOff), io.SeekStart); err != nil { types, err := inflateRawTypes(rawTypes, rawStrings)
return nil, errors.Wrap(err, "can't seek to start of type section")
}
rawTypes, err := readTypes(io.LimitReader(rd, int64(header.TypeLen)), bo)
if err != nil {
return nil, errors.Wrap(err, "can't read types")
}
types, err := inflateRawTypes(rawTypes, strings)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -142,13 +148,158 @@ func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) (*Spec, error) {
return &Spec{ return &Spec{
rawTypes: rawTypes, rawTypes: rawTypes,
types: types, types: types,
strings: strings, strings: rawStrings,
funcInfos: make(map[string]extInfo), byteOrder: bo,
lineInfos: make(map[string]extInfo),
}, nil }, nil
} }
func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) { var kernelBTF struct {
sync.Mutex
*Spec
}
// LoadKernelSpec returns the current kernel's BTF information.
//
// Requires a >= 5.5 kernel with CONFIG_DEBUG_INFO_BTF enabled. Returns
// ErrNotSupported if BTF is not enabled.
func LoadKernelSpec() (*Spec, error) {
kernelBTF.Lock()
defer kernelBTF.Unlock()
if kernelBTF.Spec != nil {
return kernelBTF.Spec, nil
}
var err error
kernelBTF.Spec, err = loadKernelSpec()
return kernelBTF.Spec, err
}
func loadKernelSpec() (*Spec, error) {
fh, err := os.Open("/sys/kernel/btf/vmlinux")
if os.IsNotExist(err) {
return nil, fmt.Errorf("can't open kernel BTF at /sys/kernel/btf/vmlinux: %w", ErrNotFound)
}
if err != nil {
return nil, fmt.Errorf("can't read kernel BTF: %s", err)
}
defer fh.Close()
return loadNakedSpec(fh, internal.NativeEndian, nil, nil)
}
func parseBTF(btf io.ReadSeeker, bo binary.ByteOrder) ([]rawType, stringTable, error) {
rawBTF, err := ioutil.ReadAll(btf)
if err != nil {
return nil, nil, fmt.Errorf("can't read BTF: %v", err)
}
rd := bytes.NewReader(rawBTF)
var header btfHeader
if err := binary.Read(rd, bo, &header); err != nil {
return nil, nil, fmt.Errorf("can't read header: %v", err)
}
if header.Magic != btfMagic {
return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
}
if header.Version != 1 {
return nil, nil, fmt.Errorf("unexpected version %v", header.Version)
}
if header.Flags != 0 {
return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags)
}
remainder := int64(header.HdrLen) - int64(binary.Size(&header))
if remainder < 0 {
return nil, nil, errors.New("header is too short")
}
if _, err := io.CopyN(internal.DiscardZeroes{}, rd, remainder); err != nil {
return nil, nil, fmt.Errorf("header padding: %v", err)
}
if _, err := rd.Seek(int64(header.HdrLen+header.StringOff), io.SeekStart); err != nil {
return nil, nil, fmt.Errorf("can't seek to start of string section: %v", err)
}
rawStrings, err := readStringTable(io.LimitReader(rd, int64(header.StringLen)))
if err != nil {
return nil, nil, fmt.Errorf("can't read type names: %w", err)
}
if _, err := rd.Seek(int64(header.HdrLen+header.TypeOff), io.SeekStart); err != nil {
return nil, nil, fmt.Errorf("can't seek to start of type section: %v", err)
}
rawTypes, err := readTypes(io.LimitReader(rd, int64(header.TypeLen)), bo)
if err != nil {
return nil, nil, fmt.Errorf("can't read types: %w", err)
}
return rawTypes, rawStrings, nil
}
type variable struct {
section string
name string
}
func fixupDatasec(rawTypes []rawType, rawStrings stringTable, sectionSizes map[string]uint32, variableOffsets map[variable]uint32) error {
for i, rawType := range rawTypes {
if rawType.Kind() != kindDatasec {
continue
}
name, err := rawStrings.Lookup(rawType.NameOff)
if err != nil {
return err
}
if name == ".kconfig" || name == ".ksym" {
return fmt.Errorf("reference to %s: %w", name, ErrNotSupported)
}
size, ok := sectionSizes[name]
if !ok {
return fmt.Errorf("data section %s: missing size", name)
}
rawTypes[i].SizeType = size
secinfos := rawType.data.([]btfVarSecinfo)
for j, secInfo := range secinfos {
id := int(secInfo.Type - 1)
if id >= len(rawTypes) {
return fmt.Errorf("data section %s: invalid type id %d for variable %d", name, id, j)
}
varName, err := rawStrings.Lookup(rawTypes[id].NameOff)
if err != nil {
return fmt.Errorf("data section %s: can't get name for type %d: %w", name, id, err)
}
offset, ok := variableOffsets[variable{name, varName}]
if !ok {
return fmt.Errorf("data section %s: missing offset for variable %s", name, varName)
}
secinfos[j].Offset = offset
}
}
return nil
}
type marshalOpts struct {
ByteOrder binary.ByteOrder
StripFuncLinkage bool
}
func (s *Spec) marshal(opts marshalOpts) ([]byte, error) {
var ( var (
buf bytes.Buffer buf bytes.Buffer
header = new(btfHeader) header = new(btfHeader)
@ -160,17 +311,14 @@ func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) {
_, _ = buf.Write(make([]byte, headerLen)) _, _ = buf.Write(make([]byte, headerLen))
// Write type section, just after the header. // Write type section, just after the header.
for _, typ := range s.rawTypes { for _, raw := range s.rawTypes {
if typ.Kind() == kindDatasec { switch {
// Datasec requires patching with information from the ELF case opts.StripFuncLinkage && raw.Kind() == kindFunc:
// file. We don't support this at the moment, so patch raw.SetLinkage(linkageStatic)
// out any Datasec by turning it into a void*.
typ = rawType{}
typ.SetKind(kindPointer)
} }
if err := typ.Marshal(&buf, bo); err != nil { if err := raw.Marshal(&buf, opts.ByteOrder); err != nil {
return nil, errors.Wrap(err, "can't marshal BTF") return nil, fmt.Errorf("can't marshal BTF: %w", err)
} }
} }
@ -192,9 +340,9 @@ func (s *Spec) marshal(bo binary.ByteOrder) ([]byte, error) {
} }
raw := buf.Bytes() raw := buf.Bytes()
err := binary.Write(sliceWriter(raw[:headerLen]), bo, header) err := binary.Write(sliceWriter(raw[:headerLen]), opts.ByteOrder, header)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't write header") return nil, fmt.Errorf("can't write header: %v", err)
} }
return raw, nil return raw, nil
@ -214,17 +362,22 @@ func (sw sliceWriter) Write(p []byte) (int, error) {
// //
// Length is the number of bytes in the raw BPF instruction stream. // Length is the number of bytes in the raw BPF instruction stream.
// //
// Returns an error if there is no BTF. // Returns an error which may wrap ErrNoExtendedInfo if the Spec doesn't
// contain extended BTF info.
func (s *Spec) Program(name string, length uint64) (*Program, error) { func (s *Spec) Program(name string, length uint64) (*Program, error) {
if length == 0 { if length == 0 {
return nil, errors.New("length musn't be zero") return nil, errors.New("length musn't be zero")
} }
if s.funcInfos == nil && s.lineInfos == nil {
return nil, fmt.Errorf("BTF for section %s: %w", name, ErrNoExtendedInfo)
}
funcInfos, funcOK := s.funcInfos[name] funcInfos, funcOK := s.funcInfos[name]
lineInfos, lineOK := s.lineInfos[name] lineInfos, lineOK := s.lineInfos[name]
if !funcOK && !lineOK { if !funcOK && !lineOK {
return nil, errors.Errorf("no BTF for program %s", name) return nil, fmt.Errorf("no extended BTF info for section %s", name)
} }
return &Program{s, length, funcInfos, lineInfos}, nil return &Program{s, length, funcInfos, lineInfos}, nil
@ -233,15 +386,15 @@ func (s *Spec) Program(name string, length uint64) (*Program, error) {
// Map finds the BTF for a map. // Map finds the BTF for a map.
// //
// Returns an error if there is no BTF for the given name. // Returns an error if there is no BTF for the given name.
func (s *Spec) Map(name string) (*Map, error) { func (s *Spec) Map(name string) (*Map, []Member, error) {
var mapVar Var var mapVar Var
if err := s.FindType(name, &mapVar); err != nil { if err := s.FindType(name, &mapVar); err != nil {
return nil, err return nil, nil, err
} }
mapStruct, ok := mapVar.Type.(*Struct) mapStruct, ok := mapVar.Type.(*Struct)
if !ok { if !ok {
return nil, errors.Errorf("expected struct, have %s", mapVar.Type) return nil, nil, fmt.Errorf("expected struct, have %s", mapVar.Type)
} }
var key, value Type var key, value Type
@ -256,23 +409,32 @@ func (s *Spec) Map(name string) (*Map, error) {
} }
if key == nil { if key == nil {
return nil, errors.Errorf("map %s: missing 'key' in type", name) key = (*Void)(nil)
} }
if value == nil { if value == nil {
return nil, errors.Errorf("map %s: missing 'value' in type", name) value = (*Void)(nil)
} }
return &Map{mapStruct, s, key, value}, nil return &Map{s, key, value}, mapStruct.Members, nil
} }
var errNotFound = errors.New("not found") // Datasec returns the BTF required to create maps which represent data sections.
func (s *Spec) Datasec(name string) (*Map, error) {
var datasec Datasec
if err := s.FindType(name, &datasec); err != nil {
return nil, fmt.Errorf("data section %s: can't get BTF: %w", name, err)
}
return &Map{s, &Void{}, &datasec}, nil
}
// FindType searches for a type with a specific name. // FindType searches for a type with a specific name.
// //
// hint determines the type of the returned Type. // hint determines the type of the returned Type.
// //
// Returns an error if there is no or multiple matches. // Returns an error wrapping ErrNotFound if no matching
// type exists in spec.
func (s *Spec) FindType(name string, typ Type) error { func (s *Spec) FindType(name string, typ Type) error {
var ( var (
wanted = reflect.TypeOf(typ) wanted = reflect.TypeOf(typ)
@ -285,14 +447,14 @@ func (s *Spec) FindType(name string, typ Type) error {
} }
if candidate != nil { if candidate != nil {
return errors.Errorf("type %s: multiple candidates for %T", name, typ) return fmt.Errorf("type %s: multiple candidates for %T", name, typ)
} }
candidate = typ candidate = typ
} }
if candidate == nil { if candidate == nil {
return errors.WithMessagef(errNotFound, "type %s", name) return fmt.Errorf("type %s: %w", name, ErrNotFound)
} }
value := reflect.Indirect(reflect.ValueOf(copyType(candidate))) value := reflect.Indirect(reflect.ValueOf(copyType(candidate)))
@ -307,16 +469,22 @@ type Handle struct {
// NewHandle loads BTF into the kernel. // NewHandle loads BTF into the kernel.
// //
// Returns an error if BTF is not supported, which can // Returns ErrNotSupported if BTF is not supported.
// be checked by IsNotSupported.
func NewHandle(spec *Spec) (*Handle, error) { func NewHandle(spec *Spec) (*Handle, error) {
if err := haveBTF(); err != nil { if err := haveBTF(); err != nil {
return nil, err return nil, err
} }
btf, err := spec.marshal(internal.NativeEndian) if spec.byteOrder != internal.NativeEndian {
return nil, fmt.Errorf("can't load %s BTF on %s", spec.byteOrder, internal.NativeEndian)
}
btf, err := spec.marshal(marshalOpts{
ByteOrder: internal.NativeEndian,
StripFuncLinkage: haveFuncLinkage() != nil,
})
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't marshal BTF") return nil, fmt.Errorf("can't marshal BTF: %w", err)
} }
if uint64(len(btf)) > math.MaxUint32 { if uint64(len(btf)) > math.MaxUint32 {
@ -360,7 +528,6 @@ func (h *Handle) FD() int {
// Map is the BTF for a map. // Map is the BTF for a map.
type Map struct { type Map struct {
definition *Struct
spec *Spec spec *Spec
key, value Type key, value Type
} }
@ -371,12 +538,6 @@ func MapSpec(m *Map) *Spec {
return m.spec return m.spec
} }
// MapType should be a method on Map, but is a free function
// to hide it from users of the ebpf package.
func MapType(m *Map) *Struct {
return m.definition
}
// MapKey should be a method on Map, but is a free function // MapKey should be a method on Map, but is a free function
// to hide it from users of the ebpf package. // to hide it from users of the ebpf package.
func MapKey(m *Map) Type { func MapKey(m *Map) Type {
@ -411,12 +572,12 @@ func ProgramSpec(s *Program) *Spec {
func ProgramAppend(s, other *Program) error { func ProgramAppend(s, other *Program) error {
funcInfos, err := s.funcInfos.append(other.funcInfos, s.length) funcInfos, err := s.funcInfos.append(other.funcInfos, s.length)
if err != nil { if err != nil {
return errors.Wrap(err, "func infos") return fmt.Errorf("func infos: %w", err)
} }
lineInfos, err := s.lineInfos.append(other.lineInfos, s.length) lineInfos, err := s.lineInfos.append(other.lineInfos, s.length)
if err != nil { if err != nil {
return errors.Wrap(err, "line infos") return fmt.Errorf("line infos: %w", err)
} }
s.length += other.length s.length += other.length
@ -451,13 +612,6 @@ func ProgramLineInfos(s *Program) (recordSize uint32, bytes []byte, err error) {
return s.lineInfos.recordSize, bytes, nil return s.lineInfos.recordSize, bytes, nil
} }
// IsNotSupported returns true if the error indicates that the kernel
// doesn't support BTF.
func IsNotSupported(err error) bool {
ufe, ok := errors.Cause(err).(*internal.UnsupportedFeatureError)
return ok && ufe.Name == "BTF"
}
type bpfLoadBTFAttr struct { type bpfLoadBTFAttr struct {
btf internal.Pointer btf internal.Pointer
logBuf internal.Pointer logBuf internal.Pointer
@ -477,26 +631,36 @@ func bpfLoadBTF(attr *bpfLoadBTFAttr) (*internal.FD, error) {
return internal.NewFD(uint32(fd)), nil return internal.NewFD(uint32(fd)), nil
} }
func minimalBTF(bo binary.ByteOrder) []byte { func marshalBTF(types interface{}, strings []byte, bo binary.ByteOrder) []byte {
const minHeaderLength = 24 const minHeaderLength = 24
typesLen := uint32(binary.Size(types))
header := btfHeader{
Magic: btfMagic,
Version: 1,
HdrLen: minHeaderLength,
TypeOff: 0,
TypeLen: typesLen,
StringOff: typesLen,
StringLen: uint32(len(strings)),
}
buf := new(bytes.Buffer)
_ = binary.Write(buf, bo, &header)
_ = binary.Write(buf, bo, types)
buf.Write(strings)
return buf.Bytes()
}
var haveBTF = internal.FeatureTest("BTF", "5.1", func() (bool, error) {
var ( var (
types struct { types struct {
Integer btfType Integer btfType
Var btfType Var btfType
btfVar struct{ Linkage uint32 } btfVar struct{ Linkage uint32 }
} }
typLen = uint32(binary.Size(&types))
strings = []byte{0, 'a', 0} strings = []byte{0, 'a', 0}
header = btfHeader{
Magic: btfMagic,
Version: 1,
HdrLen: minHeaderLength,
TypeOff: 0,
TypeLen: typLen,
StringOff: typLen,
StringLen: uint32(len(strings)),
}
) )
// We use a BTF_KIND_VAR here, to make sure that // We use a BTF_KIND_VAR here, to make sure that
@ -507,16 +671,8 @@ func minimalBTF(bo binary.ByteOrder) []byte {
types.Var.SetKind(kindVar) types.Var.SetKind(kindVar)
types.Var.SizeType = 1 types.Var.SizeType = 1
buf := new(bytes.Buffer) btf := marshalBTF(&types, strings, internal.NativeEndian)
_ = binary.Write(buf, bo, &header)
_ = binary.Write(buf, bo, &types)
buf.Write(strings)
return buf.Bytes()
}
var haveBTF = internal.FeatureTest("BTF", "5.1", func() bool {
btf := minimalBTF(internal.NativeEndian)
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{ fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf), btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)), btfSize: uint32(len(btf)),
@ -526,5 +682,35 @@ var haveBTF = internal.FeatureTest("BTF", "5.1", func() bool {
} }
// Check for EINVAL specifically, rather than err != nil since we // Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions. // otherwise misdetect due to insufficient permissions.
return errors.Cause(err) != unix.EINVAL return !errors.Is(err, unix.EINVAL), nil
})
var haveFuncLinkage = internal.FeatureTest("BTF func linkage", "5.6", func() (bool, error) {
var (
types struct {
FuncProto btfType
Func btfType
}
strings = []byte{0, 'a', 0}
)
types.FuncProto.SetKind(kindFuncProto)
types.Func.SetKind(kindFunc)
types.Func.SizeType = 1 // aka FuncProto
types.Func.NameOff = 1
types.Func.SetLinkage(linkageGlobal)
btf := marshalBTF(&types, strings, internal.NativeEndian)
fd, err := bpfLoadBTF(&bpfLoadBTFAttr{
btf: internal.NewSlicePointer(btf),
btfSize: uint32(len(btf)),
})
if err == nil {
fd.Close()
}
// Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions.
return !errors.Is(err, unix.EINVAL), nil
}) })

View File

@ -2,9 +2,8 @@ package btf
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"io" "io"
"github.com/pkg/errors"
) )
// btfKind describes a Type. // btfKind describes a Type.
@ -32,6 +31,14 @@ const (
kindDatasec kindDatasec
) )
type btfFuncLinkage uint8
const (
linkageStatic btfFuncLinkage = iota
linkageGlobal
linkageExtern
)
const ( const (
btfTypeKindShift = 24 btfTypeKindShift = 24
btfTypeKindLen = 4 btfTypeKindLen = 4
@ -43,7 +50,7 @@ const (
type btfType struct { type btfType struct {
NameOff uint32 NameOff uint32
/* "info" bits arrangement /* "info" bits arrangement
* bits 0-15: vlen (e.g. # of struct's members) * bits 0-15: vlen (e.g. # of struct's members), linkage
* bits 16-23: unused * bits 16-23: unused
* bits 24-27: kind (e.g. int, ptr, array...etc) * bits 24-27: kind (e.g. int, ptr, array...etc)
* bits 28-30: unused * bits 28-30: unused
@ -61,6 +68,45 @@ type btfType struct {
SizeType uint32 SizeType uint32
} }
func (k btfKind) String() string {
switch k {
case kindUnknown:
return "Unknown"
case kindInt:
return "Integer"
case kindPointer:
return "Pointer"
case kindArray:
return "Array"
case kindStruct:
return "Struct"
case kindUnion:
return "Union"
case kindEnum:
return "Enumeration"
case kindForward:
return "Forward"
case kindTypedef:
return "Typedef"
case kindVolatile:
return "Volatile"
case kindConst:
return "Const"
case kindRestrict:
return "Restrict"
case kindFunc:
return "Function"
case kindFuncProto:
return "Function Proto"
case kindVar:
return "Variable"
case kindDatasec:
return "Section"
default:
return fmt.Sprintf("Unknown (%d)", k)
}
}
func mask(len uint32) uint32 { func mask(len uint32) uint32 {
return (1 << len) - 1 return (1 << len) - 1
} }
@ -90,6 +136,14 @@ func (bt *btfType) SetVlen(vlen int) {
bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift) bt.setInfo(uint32(vlen), btfTypeVlenMask, btfTypeVlenShift)
} }
func (bt *btfType) Linkage() btfFuncLinkage {
return btfFuncLinkage(bt.info(btfTypeVlenMask, btfTypeVlenShift))
}
func (bt *btfType) SetLinkage(linkage btfFuncLinkage) {
bt.setInfo(uint32(linkage), btfTypeVlenMask, btfTypeVlenShift)
}
func (bt *btfType) Type() TypeID { func (bt *btfType) Type() TypeID {
// TODO: Panic here if wrong kind? // TODO: Panic here if wrong kind?
return TypeID(bt.SizeType) return TypeID(bt.SizeType)
@ -129,6 +183,26 @@ type btfMember struct {
Offset uint32 Offset uint32
} }
type btfVarSecinfo struct {
Type TypeID
Offset uint32
Size uint32
}
type btfVariable struct {
Linkage uint32
}
type btfEnum struct {
NameOff uint32
Val int32
}
type btfParam struct {
NameOff uint32
Type TypeID
}
func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) { func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
var ( var (
header btfType header btfType
@ -139,14 +213,13 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
if err := binary.Read(r, bo, &header); err == io.EOF { if err := binary.Read(r, bo, &header); err == io.EOF {
return types, nil return types, nil
} else if err != nil { } else if err != nil {
return nil, errors.Wrapf(err, "can't read type info for id %v", id) return nil, fmt.Errorf("can't read type info for id %v: %v", id, err)
} }
var data interface{} var data interface{}
switch header.Kind() { switch header.Kind() {
case kindInt: case kindInt:
// sizeof(uint32) data = new(uint32)
data = make([]byte, 4)
case kindPointer: case kindPointer:
case kindArray: case kindArray:
data = new(btfArray) data = new(btfArray)
@ -155,8 +228,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
case kindUnion: case kindUnion:
data = make([]btfMember, header.Vlen()) data = make([]btfMember, header.Vlen())
case kindEnum: case kindEnum:
// sizeof(struct btf_enum) data = make([]btfEnum, header.Vlen())
data = make([]byte, header.Vlen()*4*2)
case kindForward: case kindForward:
case kindTypedef: case kindTypedef:
case kindVolatile: case kindVolatile:
@ -164,16 +236,13 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
case kindRestrict: case kindRestrict:
case kindFunc: case kindFunc:
case kindFuncProto: case kindFuncProto:
// sizeof(struct btf_param) data = make([]btfParam, header.Vlen())
data = make([]byte, header.Vlen()*4*2)
case kindVar: case kindVar:
// sizeof(struct btf_variable) data = new(btfVariable)
data = make([]byte, 4)
case kindDatasec: case kindDatasec:
// sizeof(struct btf_var_secinfo) data = make([]btfVarSecinfo, header.Vlen())
data = make([]byte, header.Vlen()*4*3)
default: default:
return nil, errors.Errorf("type id %v: unknown kind: %v", id, header.Kind()) return nil, fmt.Errorf("type id %v: unknown kind: %v", id, header.Kind())
} }
if data == nil { if data == nil {
@ -182,7 +251,7 @@ func readTypes(r io.Reader, bo binary.ByteOrder) ([]rawType, error) {
} }
if err := binary.Read(r, bo, data); err != nil { if err := binary.Read(r, bo, data); err != nil {
return nil, errors.Wrapf(err, "type id %d: kind %v: can't read %T", id, header.Kind(), data) return nil, fmt.Errorf("type id %d: kind %v: can't read %T: %v", id, header.Kind(), data, err)
} }
types = append(types, rawType{header, data}) types = append(types, rawType{header, data})

View File

@ -3,13 +3,13 @@ package btf
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal"
"github.com/pkg/errors"
) )
type btfExtHeader struct { type btfExtHeader struct {
@ -25,23 +25,21 @@ type btfExtHeader struct {
} }
func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, err error) { func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, err error) {
const expectedMagic = 0xeB9F
var header btfExtHeader var header btfExtHeader
if err := binary.Read(r, bo, &header); err != nil { if err := binary.Read(r, bo, &header); err != nil {
return nil, nil, errors.Wrap(err, "can't read header") return nil, nil, fmt.Errorf("can't read header: %v", err)
} }
if header.Magic != expectedMagic { if header.Magic != btfMagic {
return nil, nil, errors.Errorf("incorrect magic value %v", header.Magic) return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic)
} }
if header.Version != 1 { if header.Version != 1 {
return nil, nil, errors.Errorf("unexpected version %v", header.Version) return nil, nil, fmt.Errorf("unexpected version %v", header.Version)
} }
if header.Flags != 0 { if header.Flags != 0 {
return nil, nil, errors.Errorf("unsupported flags %v", header.Flags) return 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))
@ -53,25 +51,25 @@ func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (f
// .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, errors.Wrap(err, "header padding") return 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, errors.Wrap(err, "can't seek to function info section") return nil, nil, fmt.Errorf("can't seek to function info section: %v", err)
} }
funcInfo, err = parseExtInfo(io.LimitReader(r, int64(header.FuncInfoLen)), bo, strings) funcInfo, err = parseExtInfo(io.LimitReader(r, int64(header.FuncInfoLen)), bo, strings)
if err != nil { if err != nil {
return nil, nil, errors.Wrap(err, "function info") return 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, errors.Wrap(err, "can't seek to line info section") return nil, nil, fmt.Errorf("can't seek to line info section: %v", err)
} }
lineInfo, err = parseExtInfo(io.LimitReader(r, int64(header.LineInfoLen)), bo, strings) lineInfo, err = parseExtInfo(io.LimitReader(r, int64(header.LineInfoLen)), bo, strings)
if err != nil { if err != nil {
return nil, nil, errors.Wrap(err, "line info") return nil, nil, fmt.Errorf("line info: %w", err)
} }
return funcInfo, lineInfo, nil return funcInfo, lineInfo, nil
@ -94,7 +92,7 @@ type extInfo struct {
func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) { func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) {
if other.recordSize != ei.recordSize { if other.recordSize != ei.recordSize {
return extInfo{}, errors.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize) return extInfo{}, fmt.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize)
} }
records := make([]extInfoRecord, 0, len(ei.records)+len(other.records)) records := make([]extInfoRecord, 0, len(ei.records)+len(other.records))
@ -119,7 +117,7 @@ func (ei extInfo) MarshalBinary() ([]byte, error) {
// while the ELF tracks it in bytes. // while the ELF tracks it in bytes.
insnOff := uint32(info.InsnOff / asm.InstructionSize) insnOff := uint32(info.InsnOff / asm.InstructionSize)
if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil { if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil {
return nil, errors.Wrap(err, "can't write instruction offset") return nil, fmt.Errorf("can't write instruction offset: %v", err)
} }
buf.Write(info.Opaque) buf.Write(info.Opaque)
@ -131,7 +129,7 @@ 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) {
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, errors.Wrap(err, "can't read record size") return nil, fmt.Errorf("can't read record size: %v", err)
} }
if recordSize < 4 { if recordSize < 4 {
@ -145,32 +143,32 @@ func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[st
if err := binary.Read(r, bo, &infoHeader); err == io.EOF { if err := binary.Read(r, bo, &infoHeader); err == io.EOF {
return result, nil return result, nil
} else if err != nil { } else if err != nil {
return nil, errors.Wrap(err, "can't read ext info header") return nil, fmt.Errorf("can't read ext info header: %v", err)
} }
secName, err := strings.Lookup(infoHeader.SecNameOff) secName, err := strings.Lookup(infoHeader.SecNameOff)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't get section name") return nil, fmt.Errorf("can't get section name: %w", err)
} }
if infoHeader.NumInfo == 0 { if infoHeader.NumInfo == 0 {
return nil, errors.Errorf("section %s has invalid number of records", secName) return nil, fmt.Errorf("section %s has invalid number of records", secName)
} }
var records []extInfoRecord var records []extInfoRecord
for i := uint32(0); i < infoHeader.NumInfo; i++ { for i := uint32(0); i < infoHeader.NumInfo; i++ {
var byteOff uint32 var byteOff uint32
if err := binary.Read(r, bo, &byteOff); err != nil { if err := binary.Read(r, bo, &byteOff); err != nil {
return nil, errors.Wrapf(err, "section %v: can't read extended info offset", secName) return nil, fmt.Errorf("section %v: can't read extended info offset: %v", secName, err)
} }
buf := make([]byte, int(recordSize-4)) buf := make([]byte, int(recordSize-4))
if _, err := io.ReadFull(r, buf); err != nil { if _, err := io.ReadFull(r, buf); err != nil {
return nil, errors.Wrapf(err, "section %v: can't read record", secName) return nil, fmt.Errorf("section %v: can't read record: %v", secName, err)
} }
if byteOff%asm.InstructionSize != 0 { if byteOff%asm.InstructionSize != 0 {
return nil, errors.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff) return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff)
} }
records = append(records, extInfoRecord{uint64(byteOff), buf}) records = append(records, extInfoRecord{uint64(byteOff), buf})

View File

@ -2,10 +2,10 @@ package btf
import ( import (
"bytes" "bytes"
"errors"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"github.com/pkg/errors"
) )
type stringTable []byte type stringTable []byte
@ -13,7 +13,7 @@ type stringTable []byte
func readStringTable(r io.Reader) (stringTable, error) { func readStringTable(r io.Reader) (stringTable, error) {
contents, err := ioutil.ReadAll(r) contents, err := ioutil.ReadAll(r)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't read string table") return nil, fmt.Errorf("can't read string table: %v", err)
} }
if len(contents) < 1 { if len(contents) < 1 {
@ -33,22 +33,22 @@ func readStringTable(r io.Reader) (stringTable, error) {
func (st stringTable) Lookup(offset uint32) (string, error) { func (st stringTable) Lookup(offset uint32) (string, error) {
if int64(offset) > int64(^uint(0)>>1) { if int64(offset) > int64(^uint(0)>>1) {
return "", errors.Errorf("offset %d overflows int", offset) return "", fmt.Errorf("offset %d overflows int", offset)
} }
pos := int(offset) pos := int(offset)
if pos >= len(st) { if pos >= len(st) {
return "", errors.Errorf("offset %d is out of bounds", offset) return "", fmt.Errorf("offset %d is out of bounds", offset)
} }
if pos > 0 && st[pos-1] != '\x00' { if pos > 0 && st[pos-1] != '\x00' {
return "", errors.Errorf("offset %d isn't start of a string", offset) return "", fmt.Errorf("offset %d isn't start of a string", offset)
} }
str := st[pos:] str := st[pos:]
end := bytes.IndexByte(str, '\x00') end := bytes.IndexByte(str, '\x00')
if end == -1 { if end == -1 {
return "", errors.Errorf("offset %d isn't null terminated", offset) return "", fmt.Errorf("offset %d isn't null terminated", offset)
} }
return string(str[:end]), nil return string(str[:end]), nil

View File

@ -1,9 +1,9 @@
package btf package btf
import ( import (
"errors"
"fmt"
"math" "math"
"github.com/pkg/errors"
) )
const maxTypeDepth = 32 const maxTypeDepth = 32
@ -38,9 +38,10 @@ func (n Name) name() string {
// Void is the unit type of BTF. // Void is the unit type of BTF.
type Void struct{} type Void struct{}
func (v Void) ID() TypeID { return 0 } func (v *Void) ID() TypeID { return 0 }
func (v Void) copy() Type { return Void{} } func (v *Void) size() uint32 { return 0 }
func (v Void) walk(*copyStack) {} func (v *Void) copy() Type { return (*Void)(nil) }
func (v *Void) walk(*copyStack) {}
// Int is an integer of a given length. // Int is an integer of a given length.
type Int struct { type Int struct {
@ -103,7 +104,8 @@ func (s *Struct) walk(cs *copyStack) {
func (s *Struct) copy() Type { func (s *Struct) copy() Type {
cpy := *s cpy := *s
cpy.Members = copyMembers(cpy.Members) cpy.Members = make([]Member, len(s.Members))
copy(cpy.Members, s.Members)
return &cpy return &cpy
} }
@ -126,7 +128,8 @@ func (u *Union) walk(cs *copyStack) {
func (u *Union) copy() Type { func (u *Union) copy() Type {
cpy := *u cpy := *u
cpy.Members = copyMembers(cpy.Members) cpy.Members = make([]Member, len(u.Members))
copy(cpy.Members, u.Members)
return &cpy return &cpy
} }
@ -139,14 +142,6 @@ type Member struct {
Offset uint32 Offset uint32
} }
func copyMembers(in []Member) []Member {
cpy := make([]Member, 0, len(in))
for _, member := range in {
cpy = append(cpy, member)
}
return cpy
}
// Enum lists possible values. // Enum lists possible values.
type Enum struct { type Enum struct {
TypeID TypeID
@ -265,15 +260,31 @@ type Datasec struct {
TypeID TypeID
Name Name
Size uint32 Size uint32
Vars []VarSecinfo
} }
func (ds *Datasec) size() uint32 { return ds.Size } func (ds *Datasec) size() uint32 { return ds.Size }
func (ds *Datasec) walk(*copyStack) {}
func (ds *Datasec) walk(cs *copyStack) {
for i := range ds.Vars {
cs.push(&ds.Vars[i].Type)
}
}
func (ds *Datasec) copy() Type { func (ds *Datasec) copy() Type {
cpy := *ds cpy := *ds
cpy.Vars = make([]VarSecinfo, len(ds.Vars))
copy(cpy.Vars, ds.Vars)
return &cpy return &cpy
} }
// VarSecinfo describes variable in a Datasec
type VarSecinfo struct {
Type Type
Offset uint32
Size uint32
}
type sizer interface { type sizer interface {
size() uint32 size() uint32
} }
@ -326,7 +337,7 @@ func Sizeof(typ Type) (int, error) {
continue continue
default: default:
return 0, errors.Errorf("unrecognized type %T", typ) return 0, fmt.Errorf("unrecognized type %T", typ)
} }
if n > 0 && elem > math.MaxInt64/n { if n > 0 && elem > math.MaxInt64/n {
@ -405,12 +416,17 @@ var _ namer = Name("")
// compilation units, multiple types may share the same name. A Type may form a // compilation units, multiple types may share the same name. A Type may form a
// cyclic graph by pointing at itself. // cyclic graph by pointing at itself.
func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map[string][]Type, err error) { func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map[string][]Type, err error) {
type fixup struct { type fixupDef struct {
id TypeID id TypeID
expectedKind btfKind
typ *Type typ *Type
} }
var fixups []fixup var fixups []fixupDef
fixup := func(id TypeID, expectedKind btfKind, typ *Type) {
fixups = append(fixups, fixupDef{id, expectedKind, typ})
}
convertMembers := func(raw []btfMember) ([]Member, error) { convertMembers := func(raw []btfMember) ([]Member, error) {
// NB: The fixup below relies on pre-allocating this array to // NB: The fixup below relies on pre-allocating this array to
// work, since otherwise append might re-allocate members. // work, since otherwise append might re-allocate members.
@ -418,7 +434,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
for i, btfMember := range raw { for i, btfMember := range raw {
name, err := rawStrings.LookupName(btfMember.NameOff) name, err := rawStrings.LookupName(btfMember.NameOff)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "can't get name for member %d", i) return nil, fmt.Errorf("can't get name for member %d: %w", i, err)
} }
members = append(members, Member{ members = append(members, Member{
Name: name, Name: name,
@ -426,13 +442,13 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
}) })
} }
for i := range members { for i := range members {
fixups = append(fixups, fixup{raw[i].Type, &members[i].Type}) fixup(raw[i].Type, kindUnknown, &members[i].Type)
} }
return members, nil return members, nil
} }
types := make([]Type, 0, len(rawTypes)) types := make([]Type, 0, len(rawTypes))
types = append(types, Void{}) types = append(types, (*Void)(nil))
namedTypes = make(map[string][]Type) namedTypes = make(map[string][]Type)
for i, raw := range rawTypes { for i, raw := range rawTypes {
@ -445,7 +461,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
name, err := rawStrings.LookupName(raw.NameOff) name, err := rawStrings.LookupName(raw.NameOff)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "can't get name for type id %d", id) return nil, fmt.Errorf("can't get name for type id %d: %w", id, err)
} }
switch raw.Kind() { switch raw.Kind() {
@ -454,7 +470,7 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
case kindPointer: case kindPointer:
ptr := &Pointer{id, nil} ptr := &Pointer{id, nil}
fixups = append(fixups, fixup{raw.Type(), &ptr.Target}) fixup(raw.Type(), kindUnknown, &ptr.Target)
typ = ptr typ = ptr
case kindArray: case kindArray:
@ -463,20 +479,20 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
// IndexType is unused according to btf.rst. // IndexType is unused according to btf.rst.
// Don't make it available right now. // Don't make it available right now.
arr := &Array{id, nil, btfArr.Nelems} arr := &Array{id, nil, btfArr.Nelems}
fixups = append(fixups, fixup{btfArr.Type, &arr.Type}) fixup(btfArr.Type, kindUnknown, &arr.Type)
typ = arr typ = arr
case kindStruct: case kindStruct:
members, err := convertMembers(raw.data.([]btfMember)) members, err := convertMembers(raw.data.([]btfMember))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "struct %s (id %d)", name, id) return nil, fmt.Errorf("struct %s (id %d): %w", name, id, err)
} }
typ = &Struct{id, name, raw.Size(), members} typ = &Struct{id, name, raw.Size(), members}
case kindUnion: case kindUnion:
members, err := convertMembers(raw.data.([]btfMember)) members, err := convertMembers(raw.data.([]btfMember))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "union %s (id %d)", name, id) return nil, fmt.Errorf("union %s (id %d): %w", name, id, err)
} }
typ = &Union{id, name, raw.Size(), members} typ = &Union{id, name, raw.Size(), members}
@ -488,44 +504,55 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
case kindTypedef: case kindTypedef:
typedef := &Typedef{id, name, nil} typedef := &Typedef{id, name, nil}
fixups = append(fixups, fixup{raw.Type(), &typedef.Type}) fixup(raw.Type(), kindUnknown, &typedef.Type)
typ = typedef typ = typedef
case kindVolatile: case kindVolatile:
volatile := &Volatile{id, nil} volatile := &Volatile{id, nil}
fixups = append(fixups, fixup{raw.Type(), &volatile.Type}) fixup(raw.Type(), kindUnknown, &volatile.Type)
typ = volatile typ = volatile
case kindConst: case kindConst:
cnst := &Const{id, nil} cnst := &Const{id, nil}
fixups = append(fixups, fixup{raw.Type(), &cnst.Type}) fixup(raw.Type(), kindUnknown, &cnst.Type)
typ = cnst typ = cnst
case kindRestrict: case kindRestrict:
restrict := &Restrict{id, nil} restrict := &Restrict{id, nil}
fixups = append(fixups, fixup{raw.Type(), &restrict.Type}) fixup(raw.Type(), kindUnknown, &restrict.Type)
typ = restrict typ = restrict
case kindFunc: case kindFunc:
fn := &Func{id, name, nil} fn := &Func{id, name, nil}
fixups = append(fixups, fixup{raw.Type(), &fn.Type}) fixup(raw.Type(), kindFuncProto, &fn.Type)
typ = fn typ = fn
case kindFuncProto: case kindFuncProto:
fp := &FuncProto{id, nil} fp := &FuncProto{id, nil}
fixups = append(fixups, fixup{raw.Type(), &fp.Return}) fixup(raw.Type(), kindUnknown, &fp.Return)
typ = fp typ = fp
case kindVar: case kindVar:
v := &Var{id, name, nil} v := &Var{id, name, nil}
fixups = append(fixups, fixup{raw.Type(), &v.Type}) fixup(raw.Type(), kindUnknown, &v.Type)
typ = v typ = v
case kindDatasec: case kindDatasec:
typ = &Datasec{id, name, raw.SizeType} btfVars := raw.data.([]btfVarSecinfo)
vars := make([]VarSecinfo, 0, len(btfVars))
for _, btfVar := range btfVars {
vars = append(vars, VarSecinfo{
Offset: btfVar.Offset,
Size: btfVar.Size,
})
}
for i := range vars {
fixup(btfVars[i].Type, kindVar, &vars[i].Type)
}
typ = &Datasec{id, name, raw.SizeType, vars}
default: default:
return nil, errors.Errorf("type id %d: unknown kind: %v", id, raw.Kind()) return nil, fmt.Errorf("type id %d: unknown kind: %v", id, raw.Kind())
} }
types = append(types, typ) types = append(types, typ)
@ -540,7 +567,17 @@ func inflateRawTypes(rawTypes []rawType, rawStrings stringTable) (namedTypes map
for _, fixup := range fixups { for _, fixup := range fixups {
i := int(fixup.id) i := int(fixup.id)
if i >= len(types) { if i >= len(types) {
return nil, errors.Errorf("reference to invalid type id: %d", fixup.id) return nil, fmt.Errorf("reference to invalid type id: %d", fixup.id)
}
// Default void (id 0) to unknown
rawKind := kindUnknown
if i > 0 {
rawKind = rawTypes[i-1].Kind()
}
if expected := fixup.expectedKind; expected != kindUnknown && rawKind != expected {
return nil, fmt.Errorf("expected type id %d to have kind %s, found %s", fixup.id, expected, rawKind)
} }
*fixup.typ = types[i] *fixup.typ = types[i]

View File

@ -2,10 +2,9 @@ package internal
import ( import (
"fmt" "fmt"
"os" "io/ioutil"
"strings"
"sync" "sync"
"github.com/pkg/errors"
) )
var sysCPU struct { var sysCPU struct {
@ -18,45 +17,44 @@ var sysCPU struct {
// Logical CPU numbers must be of the form 0-n // Logical CPU numbers must be of the form 0-n
func PossibleCPUs() (int, error) { func PossibleCPUs() (int, error) {
sysCPU.once.Do(func() { sysCPU.once.Do(func() {
sysCPU.num, sysCPU.err = parseCPUs("/sys/devices/system/cpu/possible") sysCPU.num, sysCPU.err = parseCPUsFromFile("/sys/devices/system/cpu/possible")
}) })
return sysCPU.num, sysCPU.err return sysCPU.num, sysCPU.err
} }
var onlineCPU struct { func parseCPUsFromFile(path string) (int, error) {
once sync.Once spec, err := ioutil.ReadFile(path)
err error
num int
}
// OnlineCPUs returns the number of currently online CPUs
// Logical CPU numbers must be of the form 0-n
func OnlineCPUs() (int, error) {
onlineCPU.once.Do(func() {
onlineCPU.num, onlineCPU.err = parseCPUs("/sys/devices/system/cpu/online")
})
return onlineCPU.num, onlineCPU.err
}
// parseCPUs parses the number of cpus from sysfs,
// in the format of "/sys/devices/system/cpu/{possible,online,..}.
// Logical CPU numbers must be of the form 0-n
func parseCPUs(path string) (int, error) {
file, err := os.Open(path)
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer file.Close()
n, err := parseCPUs(string(spec))
if err != nil {
return 0, fmt.Errorf("can't parse %s: %v", path, err)
}
return n, nil
}
// parseCPUs parses the number of cpus from a string produced
// by bitmap_list_string() in the Linux kernel.
// Multiple ranges are rejected, since they can't be unified
// into a single number.
// This is the format of /sys/devices/system/cpu/possible, it
// is not suitable for /sys/devices/system/cpu/online, etc.
func parseCPUs(spec string) (int, error) {
if strings.Trim(spec, "\n") == "0" {
return 1, nil
}
var low, high int var low, high int
n, _ := fmt.Fscanf(file, "%d-%d", &low, &high) n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high)
if n < 1 || low != 0 { if n != 2 || err != nil {
return 0, errors.Wrapf(err, "%s has unknown format", path) return 0, fmt.Errorf("invalid format: %s", spec)
} }
if n == 1 { if low != 0 {
high = low return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec)
} }
// cpus is 0 indexed // cpus is 0 indexed

View File

@ -2,11 +2,11 @@ package internal
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"strings" "strings"
"github.com/cilium/ebpf/internal/unix" "github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
) )
// ErrorWithLog returns an error that includes logs from the // ErrorWithLog returns an error that includes logs from the
@ -16,19 +16,20 @@ import (
// the log. It is used to check for truncation of the output. // the log. It is used to check for truncation of the output.
func ErrorWithLog(err error, log []byte, logErr error) error { func ErrorWithLog(err error, log []byte, logErr error) error {
logStr := strings.Trim(CString(log), "\t\r\n ") logStr := strings.Trim(CString(log), "\t\r\n ")
if errors.Cause(logErr) == unix.ENOSPC { if errors.Is(logErr, unix.ENOSPC) {
logStr += " (truncated...)" logStr += " (truncated...)"
} }
return &loadError{err, logStr} return &VerifierError{err, logStr}
} }
type loadError struct { // VerifierError includes information from the eBPF verifier.
type VerifierError struct {
cause error cause error
log string log string
} }
func (le *loadError) Error() string { func (le *VerifierError) Error() string {
if le.log == "" { if le.log == "" {
return le.cause.Error() return le.cause.Error()
} }
@ -36,10 +37,6 @@ func (le *loadError) Error() string {
return fmt.Sprintf("%s: %s", le.cause, le.log) return fmt.Sprintf("%s: %s", le.cause, le.log)
} }
func (le *loadError) Cause() error {
return le.cause
}
// CString turns a NUL / zero terminated byte buffer into a string. // CString turns a NUL / zero terminated byte buffer into a string.
func CString(in []byte) string { func CString(in []byte) string {
inLen := bytes.IndexByte(in, 0) inLen := bytes.IndexByte(in, 0)

View File

@ -1,12 +1,13 @@
package internal package internal
import ( import (
"errors"
"fmt"
"os"
"runtime" "runtime"
"strconv" "strconv"
"github.com/cilium/ebpf/internal/unix" "github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
) )
var ErrClosedFd = errors.New("use of closed file descriptor") var ErrClosedFd = errors.New("use of closed file descriptor")
@ -56,8 +57,13 @@ func (fd *FD) Dup() (*FD, error) {
dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0) dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't dup fd") return nil, fmt.Errorf("can't dup fd: %v", err)
} }
return NewFD(uint32(dup)), nil return NewFD(uint32(dup)), nil
} }
func (fd *FD) File(name string) *os.File {
fd.Forget()
return os.NewFile(uintptr(fd.raw), name)
}

View File

@ -1,12 +1,14 @@
package internal package internal
import ( import (
"errors"
"fmt" "fmt"
"sync" "sync"
"github.com/pkg/errors"
) )
// ErrNotSupported indicates that a feature is not supported by the current kernel.
var ErrNotSupported = errors.New("not supported")
// UnsupportedFeatureError is returned by FeatureTest() functions. // UnsupportedFeatureError is returned by FeatureTest() functions.
type UnsupportedFeatureError struct { type UnsupportedFeatureError struct {
// The minimum Linux mainline version required for this feature. // The minimum Linux mainline version required for this feature.
@ -21,33 +23,68 @@ func (ufe *UnsupportedFeatureError) Error() string {
return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion) return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion)
} }
// Is indicates that UnsupportedFeatureError is ErrNotSupported.
func (ufe *UnsupportedFeatureError) Is(target error) bool {
return target == ErrNotSupported
}
type featureTest struct {
sync.Mutex
successful bool
result error
}
// FeatureTestFn is used to determine whether the kernel supports
// a certain feature.
//
// The return values have the following semantics:
//
// err != nil: the test couldn't be executed
// err == nil && available: the feature is available
// err == nil && !available: the feature isn't available
type FeatureTestFn func() (available bool, err error)
// FeatureTest wraps a function so that it is run at most once. // FeatureTest wraps a function so that it is run at most once.
// //
// name should identify the tested feature, while version must be in the // name should identify the tested feature, while version must be in the
// form Major.Minor[.Patch]. // form Major.Minor[.Patch].
// //
// Returns a descriptive UnsupportedFeatureError if the feature is not available. // Returns an error wrapping ErrNotSupported if the feature is not supported.
func FeatureTest(name, version string, fn func() bool) func() error { func FeatureTest(name, version string, fn FeatureTestFn) func() error {
v, err := NewVersion(version) v, err := NewVersion(version)
if err != nil { if err != nil {
return func() error { return err } return func() error { return err }
} }
var ( ft := new(featureTest)
once sync.Once
result error
)
return func() error { return func() error {
once.Do(func() { ft.Lock()
if !fn() { defer ft.Unlock()
result = &UnsupportedFeatureError{
if ft.successful {
return ft.result
}
available, err := fn()
if errors.Is(err, ErrNotSupported) {
// The feature test aborted because a dependent feature
// is missing, which we should cache.
available = false
} else if err != nil {
// We couldn't execute the feature test to a point
// where it could make a determination.
// Don't cache the result, just return it.
return fmt.Errorf("can't detect support for %s: %w", name, err)
}
ft.successful = true
if !available {
ft.result = &UnsupportedFeatureError{
MinimumVersion: v, MinimumVersion: v,
Name: name, Name: name,
} }
} }
}) return ft.result
return result
} }
} }
@ -61,7 +98,7 @@ func NewVersion(ver string) (Version, error) {
var major, minor, patch uint16 var major, minor, patch uint16
n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch) n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch)
if n < 2 { if n < 2 {
return Version{}, errors.Errorf("invalid version: %s", ver) return Version{}, fmt.Errorf("invalid version: %s", ver)
} }
return Version{major, minor, patch}, nil return Version{major, minor, patch}, nil
} }

View File

@ -1,6 +1,6 @@
package internal package internal
import "github.com/pkg/errors" import "errors"
// DiscardZeroes makes sure that all written bytes are zero // DiscardZeroes makes sure that all written bytes are zero
// before discarding them. // before discarding them.

View File

@ -22,5 +22,9 @@ func NewStringPointer(str string) Pointer {
return Pointer{} return Pointer{}
} }
return Pointer{ptr: unsafe.Pointer(&[]byte(str)[0])} // The kernel expects strings to be zero terminated
buf := make([]byte, len(str)+1)
copy(buf, str)
return Pointer{ptr: unsafe.Pointer(&buf[0])}
} }

View File

@ -1,16 +1,61 @@
package internal package internal
import ( import (
"fmt"
"path/filepath"
"runtime" "runtime"
"unsafe" "unsafe"
"github.com/cilium/ebpf/internal/unix" "github.com/cilium/ebpf/internal/unix"
) )
//go:generate stringer -output syscall_string.go -type=BPFCmd
// BPFCmd identifies a subcommand of the bpf syscall.
type BPFCmd int
// Well known BPF commands.
const (
BPF_MAP_CREATE BPFCmd = iota
BPF_MAP_LOOKUP_ELEM
BPF_MAP_UPDATE_ELEM
BPF_MAP_DELETE_ELEM
BPF_MAP_GET_NEXT_KEY
BPF_PROG_LOAD
BPF_OBJ_PIN
BPF_OBJ_GET
BPF_PROG_ATTACH
BPF_PROG_DETACH
BPF_PROG_TEST_RUN
BPF_PROG_GET_NEXT_ID
BPF_MAP_GET_NEXT_ID
BPF_PROG_GET_FD_BY_ID
BPF_MAP_GET_FD_BY_ID
BPF_OBJ_GET_INFO_BY_FD
BPF_PROG_QUERY
BPF_RAW_TRACEPOINT_OPEN
BPF_BTF_LOAD
BPF_BTF_GET_FD_BY_ID
BPF_TASK_FD_QUERY
BPF_MAP_LOOKUP_AND_DELETE_ELEM
BPF_MAP_FREEZE
BPF_BTF_GET_NEXT_ID
BPF_MAP_LOOKUP_BATCH
BPF_MAP_LOOKUP_AND_DELETE_BATCH
BPF_MAP_UPDATE_BATCH
BPF_MAP_DELETE_BATCH
BPF_LINK_CREATE
BPF_LINK_UPDATE
BPF_LINK_GET_FD_BY_ID
BPF_LINK_GET_NEXT_ID
BPF_ENABLE_STATS
BPF_ITER_CREATE
)
// BPF wraps SYS_BPF. // BPF wraps SYS_BPF.
// //
// Any pointers contained in attr must use the Pointer type from this package. // Any pointers contained in attr must use the Pointer type from this package.
func BPF(cmd int, attr unsafe.Pointer, size uintptr) (uintptr, error) { func BPF(cmd BPFCmd, attr unsafe.Pointer, size uintptr) (uintptr, error) {
r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size) r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
runtime.KeepAlive(attr) runtime.KeepAlive(attr)
@ -21,3 +66,74 @@ func BPF(cmd int, attr unsafe.Pointer, size uintptr) (uintptr, error) {
return r1, err return r1, err
} }
type BPFProgAttachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
AttachFlags uint32
ReplaceBpfFd uint32
}
func BPFProgAttach(attr *BPFProgAttachAttr) error {
_, err := BPF(BPF_PROG_ATTACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type BPFProgDetachAttr struct {
TargetFd uint32
AttachBpfFd uint32
AttachType uint32
}
func BPFProgDetach(attr *BPFProgDetachAttr) error {
_, err := BPF(BPF_PROG_DETACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
type bpfObjAttr struct {
fileName Pointer
fd uint32
fileFlags uint32
}
const bpfFSType = 0xcafe4a11
// BPFObjPin wraps BPF_OBJ_PIN.
func BPFObjPin(fileName string, fd *FD) error {
dirName := filepath.Dir(fileName)
var statfs unix.Statfs_t
if err := unix.Statfs(dirName, &statfs); err != nil {
return err
}
if uint64(statfs.Type) != bpfFSType {
return fmt.Errorf("%s is not on a bpf filesystem", fileName)
}
value, err := fd.Value()
if err != nil {
return err
}
attr := bpfObjAttr{
fileName: NewStringPointer(fileName),
fd: value,
}
_, err = BPF(BPF_OBJ_PIN, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return fmt.Errorf("pin object %s: %w", fileName, err)
}
return nil
}
// BPFObjGet wraps BPF_OBJ_GET.
func BPFObjGet(fileName string) (*FD, error) {
attr := bpfObjAttr{
fileName: NewStringPointer(fileName),
}
ptr, err := BPF(BPF_OBJ_GET, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return nil, fmt.Errorf("get object %s: %w", fileName, err)
}
return NewFD(uint32(ptr)), nil
}

View File

@ -0,0 +1,56 @@
// Code generated by "stringer -output syscall_string.go -type=BPFCmd"; DO NOT EDIT.
package internal
import "strconv"
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[BPF_MAP_CREATE-0]
_ = x[BPF_MAP_LOOKUP_ELEM-1]
_ = x[BPF_MAP_UPDATE_ELEM-2]
_ = x[BPF_MAP_DELETE_ELEM-3]
_ = x[BPF_MAP_GET_NEXT_KEY-4]
_ = x[BPF_PROG_LOAD-5]
_ = x[BPF_OBJ_PIN-6]
_ = x[BPF_OBJ_GET-7]
_ = x[BPF_PROG_ATTACH-8]
_ = x[BPF_PROG_DETACH-9]
_ = x[BPF_PROG_TEST_RUN-10]
_ = x[BPF_PROG_GET_NEXT_ID-11]
_ = x[BPF_MAP_GET_NEXT_ID-12]
_ = x[BPF_PROG_GET_FD_BY_ID-13]
_ = x[BPF_MAP_GET_FD_BY_ID-14]
_ = x[BPF_OBJ_GET_INFO_BY_FD-15]
_ = x[BPF_PROG_QUERY-16]
_ = x[BPF_RAW_TRACEPOINT_OPEN-17]
_ = x[BPF_BTF_LOAD-18]
_ = x[BPF_BTF_GET_FD_BY_ID-19]
_ = x[BPF_TASK_FD_QUERY-20]
_ = x[BPF_MAP_LOOKUP_AND_DELETE_ELEM-21]
_ = x[BPF_MAP_FREEZE-22]
_ = x[BPF_BTF_GET_NEXT_ID-23]
_ = x[BPF_MAP_LOOKUP_BATCH-24]
_ = x[BPF_MAP_LOOKUP_AND_DELETE_BATCH-25]
_ = x[BPF_MAP_UPDATE_BATCH-26]
_ = x[BPF_MAP_DELETE_BATCH-27]
_ = x[BPF_LINK_CREATE-28]
_ = x[BPF_LINK_UPDATE-29]
_ = x[BPF_LINK_GET_FD_BY_ID-30]
_ = x[BPF_LINK_GET_NEXT_ID-31]
_ = x[BPF_ENABLE_STATS-32]
_ = x[BPF_ITER_CREATE-33]
}
const _BPFCmd_name = "BPF_MAP_CREATEBPF_MAP_LOOKUP_ELEMBPF_MAP_UPDATE_ELEMBPF_MAP_DELETE_ELEMBPF_MAP_GET_NEXT_KEYBPF_PROG_LOADBPF_OBJ_PINBPF_OBJ_GETBPF_PROG_ATTACHBPF_PROG_DETACHBPF_PROG_TEST_RUNBPF_PROG_GET_NEXT_IDBPF_MAP_GET_NEXT_IDBPF_PROG_GET_FD_BY_IDBPF_MAP_GET_FD_BY_IDBPF_OBJ_GET_INFO_BY_FDBPF_PROG_QUERYBPF_RAW_TRACEPOINT_OPENBPF_BTF_LOADBPF_BTF_GET_FD_BY_IDBPF_TASK_FD_QUERYBPF_MAP_LOOKUP_AND_DELETE_ELEMBPF_MAP_FREEZEBPF_BTF_GET_NEXT_IDBPF_MAP_LOOKUP_BATCHBPF_MAP_LOOKUP_AND_DELETE_BATCHBPF_MAP_UPDATE_BATCHBPF_MAP_DELETE_BATCHBPF_LINK_CREATEBPF_LINK_UPDATEBPF_LINK_GET_FD_BY_IDBPF_LINK_GET_NEXT_IDBPF_ENABLE_STATSBPF_ITER_CREATE"
var _BPFCmd_index = [...]uint16{0, 14, 33, 52, 71, 91, 104, 115, 126, 141, 156, 173, 193, 212, 233, 253, 275, 289, 312, 324, 344, 361, 391, 405, 424, 444, 475, 495, 515, 530, 545, 566, 586, 602, 617}
func (i BPFCmd) String() string {
if i < 0 || i >= BPFCmd(len(_BPFCmd_index)-1) {
return "BPFCmd(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _BPFCmd_name[_BPFCmd_index[i]:_BPFCmd_index[i+1]]
}

View File

@ -10,10 +10,17 @@ import (
const ( const (
ENOENT = linux.ENOENT ENOENT = linux.ENOENT
EEXIST = linux.EEXIST
EAGAIN = linux.EAGAIN EAGAIN = linux.EAGAIN
ENOSPC = linux.ENOSPC ENOSPC = linux.ENOSPC
EINVAL = linux.EINVAL EINVAL = linux.EINVAL
EPOLLIN = linux.EPOLLIN EPOLLIN = linux.EPOLLIN
EINTR = linux.EINTR
EPERM = linux.EPERM
ESRCH = linux.ESRCH
ENODEV = linux.ENODEV
BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG
BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG
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
@ -31,6 +38,7 @@ const (
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
) )
// Statfs_t is a wrapper // Statfs_t is a wrapper
@ -125,3 +133,18 @@ type Utsname = linux.Utsname
func Uname(buf *Utsname) (err error) { func Uname(buf *Utsname) (err error) {
return linux.Uname(buf) return linux.Uname(buf)
} }
// Getpid is a wrapper
func Getpid() int {
return linux.Getpid()
}
// Gettid is a wrapper
func Gettid() int {
return linux.Gettid()
}
// Tgkill is a wrapper
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
return linux.Tgkill(tgid, tid, sig)
}

View File

@ -12,9 +12,16 @@ var errNonLinux = fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime
const ( const (
ENOENT = syscall.ENOENT ENOENT = syscall.ENOENT
EEXIST = syscall.EEXIST
EAGAIN = syscall.EAGAIN EAGAIN = syscall.EAGAIN
ENOSPC = syscall.ENOSPC ENOSPC = syscall.ENOSPC
EINVAL = syscall.EINVAL EINVAL = syscall.EINVAL
EINTR = syscall.EINTR
EPERM = syscall.EPERM
ESRCH = syscall.ESRCH
ENODEV = syscall.ENODEV
BPF_F_RDONLY_PROG = 0
BPF_F_WRONLY_PROG = 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
@ -32,6 +39,8 @@ const (
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
RLIMIT_MEMLOCK = 8
) )
// Statfs_t is a wrapper // Statfs_t is a wrapper
@ -191,3 +200,18 @@ type Utsname struct {
func Uname(buf *Utsname) (err error) { func Uname(buf *Utsname) (err error) {
return errNonLinux return errNonLinux
} }
// Getpid is a wrapper
func Getpid() int {
return -1
}
// Gettid is a wrapper
func Gettid() int {
return -1
}
// Tgkill is a wrapper
func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) {
return errNonLinux
}

View File

@ -1,43 +1,60 @@
package ebpf package ebpf
import ( import (
"fmt"
"github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal/btf" "github.com/cilium/ebpf/internal/btf"
"github.com/pkg/errors"
) )
// link resolves bpf-to-bpf calls. // link resolves bpf-to-bpf calls.
// //
// Each library may contain multiple functions / labels, and is only linked // Each library may contain multiple functions / labels, and is only linked
// if the program being edited references one of these functions. // if prog references one of these functions.
// //
// Libraries must not require linking themselves. // Libraries also linked.
func link(prog *ProgramSpec, libs []*ProgramSpec) error { func link(prog *ProgramSpec, libs []*ProgramSpec) error {
var (
linked = make(map[*ProgramSpec]bool)
pending = []asm.Instructions{prog.Instructions}
insns asm.Instructions
)
for len(pending) > 0 {
insns, pending = pending[0], pending[1:]
for _, lib := range libs { for _, lib := range libs {
insns, err := linkSection(prog.Instructions, lib.Instructions) if linked[lib] {
if err != nil {
return errors.Wrapf(err, "linking %s", lib.Name)
}
if len(insns) == len(prog.Instructions) {
continue continue
} }
prog.Instructions = insns needed, err := needSection(insns, lib.Instructions)
if err != nil {
return fmt.Errorf("linking %s: %w", lib.Name, err)
}
if !needed {
continue
}
linked[lib] = true
prog.Instructions = append(prog.Instructions, lib.Instructions...)
pending = append(pending, lib.Instructions)
if prog.BTF != nil && lib.BTF != nil { if prog.BTF != nil && lib.BTF != nil {
if err := btf.ProgramAppend(prog.BTF, lib.BTF); err != nil { if err := btf.ProgramAppend(prog.BTF, lib.BTF); err != nil {
return errors.Wrapf(err, "linking BTF of %s", lib.Name) return fmt.Errorf("linking BTF of %s: %w", lib.Name, err)
} }
} }
} }
}
return nil return nil
} }
func linkSection(insns, section asm.Instructions) (asm.Instructions, error) { func needSection(insns, section asm.Instructions) (bool, error) {
// A map of symbols to the libraries which contain them. // A map of symbols to the libraries which contain them.
symbols, err := section.SymbolOffsets() symbols, err := section.SymbolOffsets()
if err != nil { if err != nil {
return nil, err return false, err
} }
for _, ins := range insns { for _, ins := range insns {
@ -45,7 +62,7 @@ func linkSection(insns, section asm.Instructions) (asm.Instructions, error) {
continue continue
} }
if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.R1 { if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.PseudoCall {
continue continue
} }
@ -60,11 +77,10 @@ func linkSection(insns, section asm.Instructions) (asm.Instructions, error) {
} }
// At this point we know that at least one function in the // At this point we know that at least one function in the
// library is called from insns. Merge the two sections. // library is called from insns, so we have to link it.
// The rewrite of ins.Constant happens in asm.Instruction.Marshal. return true, nil
return append(insns, section...), nil
} }
// None of the functions in the section are called. Do nothing. // None of the functions in the section are called.
return insns, nil return false, nil
} }

323
vendor/github.com/cilium/ebpf/map.go generated vendored
View File

@ -1,15 +1,25 @@
package ebpf package ebpf
import ( import (
"errors"
"fmt" "fmt"
"strings"
"github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf" "github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/unix" "github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
) )
// Errors returned by Map and MapIterator methods.
var (
ErrKeyNotExist = errors.New("key does not exist")
ErrKeyExist = errors.New("key already exists")
ErrIterationAborted = errors.New("iteration aborted")
)
// MapID represents the unique ID of an eBPF map
type MapID uint32
// MapSpec defines a Map. // MapSpec defines a Map.
type MapSpec struct { type MapSpec struct {
// Name is passed to the kernel as a debug aid. Must only contain // Name is passed to the kernel as a debug aid. Must only contain
@ -21,6 +31,12 @@ type MapSpec struct {
MaxEntries uint32 MaxEntries uint32
Flags uint32 Flags uint32
// The initial contents of the map. May be nil.
Contents []MapKV
// Whether to freeze a map after setting its initial contents.
Freeze bool
// InnerMap is used as a template for ArrayOfMaps and HashOfMaps // InnerMap is used as a template for ArrayOfMaps and HashOfMaps
InnerMap *MapSpec InnerMap *MapSpec
@ -33,16 +49,26 @@ func (ms *MapSpec) String() string {
} }
// Copy returns a copy of the spec. // Copy returns a copy of the spec.
//
// MapSpec.Contents is a shallow copy.
func (ms *MapSpec) Copy() *MapSpec { func (ms *MapSpec) Copy() *MapSpec {
if ms == nil { if ms == nil {
return nil return nil
} }
cpy := *ms cpy := *ms
cpy.Contents = make([]MapKV, len(ms.Contents))
copy(cpy.Contents, ms.Contents)
cpy.InnerMap = ms.InnerMap.Copy() cpy.InnerMap = ms.InnerMap.Copy()
return &cpy return &cpy
} }
// MapKV is used to initialize the contents of a Map.
type MapKV struct {
Key interface{}
Value interface{}
}
// Map represents a Map file descriptor. // Map represents a Map file descriptor.
// //
// It is not safe to close a map which is used by other goroutines. // It is not safe to close a map which is used by other goroutines.
@ -81,14 +107,18 @@ func NewMapFromFD(fd int) (*Map, error) {
// //
// Creating a map for the first time will perform feature detection // Creating a map for the first time will perform feature detection
// by creating small, temporary maps. // by creating small, temporary maps.
//
// The caller is responsible for ensuring the process' rlimit is set
// sufficiently high for locking memory during map creation. This can be done
// by calling unix.Setrlimit with unix.RLIMIT_MEMLOCK prior to calling NewMap.
func NewMap(spec *MapSpec) (*Map, error) { func NewMap(spec *MapSpec) (*Map, error) {
if spec.BTF == nil { if spec.BTF == nil {
return newMapWithBTF(spec, nil) return newMapWithBTF(spec, nil)
} }
handle, err := btf.NewHandle(btf.MapSpec(spec.BTF)) handle, err := btf.NewHandle(btf.MapSpec(spec.BTF))
if err != nil && !btf.IsNotSupported(err) { if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, errors.Wrap(err, "can't load BTF") return nil, fmt.Errorf("can't load BTF: %w", err)
} }
return newMapWithBTF(spec, handle) return newMapWithBTF(spec, handle)
@ -100,7 +130,7 @@ func newMapWithBTF(spec *MapSpec, handle *btf.Handle) (*Map, error) {
} }
if spec.InnerMap == nil { if spec.InnerMap == nil {
return nil, errors.Errorf("%s requires InnerMap", spec.Type) return nil, fmt.Errorf("%s requires InnerMap", spec.Type)
} }
template, err := createMap(spec.InnerMap, nil, handle) template, err := createMap(spec.InnerMap, nil, handle)
@ -113,7 +143,7 @@ func newMapWithBTF(spec *MapSpec, handle *btf.Handle) (*Map, error) {
} }
func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, error) { func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, error) {
spec = spec.Copy() abi := newMapABIFromSpec(spec)
switch spec.Type { switch spec.Type {
case ArrayOfMaps: case ArrayOfMaps:
@ -123,43 +153,50 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
return nil, err return nil, err
} }
if spec.ValueSize != 0 && spec.ValueSize != 4 { if abi.ValueSize != 0 && abi.ValueSize != 4 {
return nil, errors.Errorf("ValueSize must be zero or four for map of map") return nil, errors.New("ValueSize must be zero or four for map of map")
} }
spec.ValueSize = 4 abi.ValueSize = 4
case PerfEventArray: case PerfEventArray:
if spec.KeySize != 0 { if abi.KeySize != 0 && abi.KeySize != 4 {
return nil, errors.Errorf("KeySize must be zero for perf event array") return nil, errors.New("KeySize must be zero or four for perf event array")
} }
if spec.ValueSize != 0 { abi.KeySize = 4
return nil, errors.Errorf("ValueSize must be zero for perf event array")
if abi.ValueSize != 0 && abi.ValueSize != 4 {
return nil, errors.New("ValueSize must be zero or four for perf event array")
} }
if spec.MaxEntries == 0 { abi.ValueSize = 4
n, err := internal.OnlineCPUs()
if abi.MaxEntries == 0 {
n, err := internal.PossibleCPUs()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "perf event array") return nil, fmt.Errorf("perf event array: %w", err)
}
abi.MaxEntries = uint32(n)
} }
spec.MaxEntries = uint32(n)
} }
spec.KeySize = 4 if abi.Flags&(unix.BPF_F_RDONLY_PROG|unix.BPF_F_WRONLY_PROG) > 0 || spec.Freeze {
spec.ValueSize = 4 if err := haveMapMutabilityModifiers(); err != nil {
return nil, fmt.Errorf("map create: %w", err)
}
} }
attr := bpfMapCreateAttr{ attr := bpfMapCreateAttr{
mapType: spec.Type, mapType: abi.Type,
keySize: spec.KeySize, keySize: abi.KeySize,
valueSize: spec.ValueSize, valueSize: abi.ValueSize,
maxEntries: spec.MaxEntries, maxEntries: abi.MaxEntries,
flags: spec.Flags, flags: abi.Flags,
} }
if inner != nil { if inner != nil {
var err error var err error
attr.innerMapFd, err = inner.Value() attr.innerMapFd, err = inner.Value()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "map create") return nil, fmt.Errorf("map create: %w", err)
} }
} }
@ -169,21 +206,33 @@ func createMap(spec *MapSpec, inner *internal.FD, handle *btf.Handle) (*Map, err
attr.btfValueTypeID = btf.MapValue(spec.BTF).ID() attr.btfValueTypeID = btf.MapValue(spec.BTF).ID()
} }
name, err := newBPFObjName(spec.Name)
if err != nil {
return nil, errors.Wrap(err, "map create")
}
if haveObjName() == nil { if haveObjName() == nil {
attr.mapName = name attr.mapName = newBPFObjName(spec.Name)
} }
fd, err := bpfMapCreate(&attr) fd, err := bpfMapCreate(&attr)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "map create") return nil, fmt.Errorf("map create: %w", err)
} }
return newMap(fd, spec.Name, newMapABIFromSpec(spec)) m, err := newMap(fd, spec.Name, abi)
if err != nil {
return nil, err
}
if err := m.populate(spec.Contents); err != nil {
m.Close()
return nil, fmt.Errorf("map create: can't set initial contents: %w", err)
}
if spec.Freeze {
if err := m.Freeze(); err != nil {
m.Close()
return nil, fmt.Errorf("can't freeze map: %w", err)
}
}
return m, nil
} }
func newMap(fd *internal.FD, name string, abi *MapABI) (*Map, error) { func newMap(fd *internal.FD, name string, abi *MapABI) (*Map, error) {
@ -251,9 +300,9 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
*value = m *value = m
return nil return nil
case *Map: case *Map:
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil)) return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
case Map: case Map:
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil)) return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Map)(nil))
case **Program: case **Program:
p, err := unmarshalProgram(valueBytes) p, err := unmarshalProgram(valueBytes)
@ -265,9 +314,9 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
*value = p *value = p
return nil return nil
case *Program: case *Program:
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil)) return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
case Program: case Program:
return errors.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil)) return fmt.Errorf("can't unmarshal into %T, need %T", value, (**Program)(nil))
default: default:
return unmarshalBytes(valueOut, valueBytes) return unmarshalBytes(valueOut, valueBytes)
@ -275,16 +324,18 @@ func (m *Map) Lookup(key, valueOut interface{}) error {
} }
// LookupAndDelete retrieves and deletes a value from a Map. // LookupAndDelete retrieves and deletes a value from a Map.
//
// Returns ErrKeyNotExist if the key doesn't exist.
func (m *Map) LookupAndDelete(key, valueOut interface{}) error { func (m *Map) LookupAndDelete(key, valueOut interface{}) error {
valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize) valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize)
keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil { if err != nil {
return errors.WithMessage(err, "can't marshal key") return fmt.Errorf("can't marshal key: %w", err)
} }
if err := bpfMapLookupAndDelete(m.fd, keyPtr, valuePtr); err != nil { if err := bpfMapLookupAndDelete(m.fd, keyPtr, valuePtr); err != nil {
return errors.WithMessage(err, "lookup and delete and delete failed") return fmt.Errorf("lookup and delete failed: %w", err)
} }
return unmarshalBytes(valueOut, valueBytes) return unmarshalBytes(valueOut, valueBytes)
@ -298,7 +349,7 @@ func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
valuePtr := internal.NewSlicePointer(valueBytes) valuePtr := internal.NewSlicePointer(valueBytes)
err := m.lookup(key, valuePtr) err := m.lookup(key, valuePtr)
if IsNotExist(err) { if errors.Is(err, ErrKeyNotExist) {
return nil, nil return nil, nil
} }
@ -308,11 +359,13 @@ func (m *Map) LookupBytes(key interface{}) ([]byte, error) {
func (m *Map) lookup(key interface{}, valueOut internal.Pointer) error { func (m *Map) lookup(key interface{}, valueOut internal.Pointer) error {
keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil { if err != nil {
return errors.WithMessage(err, "can't marshal key") return fmt.Errorf("can't marshal key: %w", err)
} }
err = bpfMapLookupElem(m.fd, keyPtr, valueOut) if err = bpfMapLookupElem(m.fd, keyPtr, valueOut); err != nil {
return errors.WithMessage(err, "lookup failed") return fmt.Errorf("lookup failed: %w", err)
}
return nil
} }
// MapUpdateFlags controls the behaviour of the Map.Update call. // MapUpdateFlags controls the behaviour of the Map.Update call.
@ -340,7 +393,7 @@ func (m *Map) Put(key, value interface{}) error {
func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error { func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil { if err != nil {
return errors.WithMessage(err, "can't marshal key") return fmt.Errorf("can't marshal key: %w", err)
} }
var valuePtr internal.Pointer var valuePtr internal.Pointer
@ -350,28 +403,36 @@ func (m *Map) Update(key, value interface{}, flags MapUpdateFlags) error {
valuePtr, err = marshalPtr(value, int(m.abi.ValueSize)) valuePtr, err = marshalPtr(value, int(m.abi.ValueSize))
} }
if err != nil { if err != nil {
return errors.WithMessage(err, "can't marshal value") return fmt.Errorf("can't marshal value: %w", err)
} }
return bpfMapUpdateElem(m.fd, keyPtr, valuePtr, uint64(flags)) if err = bpfMapUpdateElem(m.fd, keyPtr, valuePtr, uint64(flags)); err != nil {
return fmt.Errorf("update failed: %w", err)
}
return nil
} }
// Delete removes a value. // Delete removes a value.
// //
// Returns an error if the key does not exist, see IsNotExist. // Returns ErrKeyNotExist if the key does not exist.
func (m *Map) Delete(key interface{}) error { func (m *Map) Delete(key interface{}) error {
keyPtr, err := marshalPtr(key, int(m.abi.KeySize)) keyPtr, err := marshalPtr(key, int(m.abi.KeySize))
if err != nil { if err != nil {
return errors.WithMessage(err, "can't marshal key") return fmt.Errorf("can't marshal key: %w", err)
} }
err = bpfMapDeleteElem(m.fd, keyPtr) if err = bpfMapDeleteElem(m.fd, keyPtr); err != nil {
return errors.WithMessage(err, "can't delete key") return fmt.Errorf("delete failed: %w", err)
}
return nil
} }
// NextKey finds the key following an initial key. // NextKey finds the key following an initial key.
// //
// See NextKeyBytes for details. // See NextKeyBytes for details.
//
// Returns ErrKeyNotExist if there is no next key.
func (m *Map) NextKey(key, nextKeyOut interface{}) error { func (m *Map) NextKey(key, nextKeyOut interface{}) error {
nextKeyPtr, nextKeyBytes := makeBuffer(nextKeyOut, int(m.abi.KeySize)) nextKeyPtr, nextKeyBytes := makeBuffer(nextKeyOut, int(m.abi.KeySize))
@ -383,8 +444,10 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error {
return nil return nil
} }
err := unmarshalBytes(nextKeyOut, nextKeyBytes) if err := unmarshalBytes(nextKeyOut, nextKeyBytes); err != nil {
return errors.WithMessage(err, "can't unmarshal next key") return fmt.Errorf("can't unmarshal next key: %w", err)
}
return nil
} }
// NextKeyBytes returns the key following an initial key as a byte slice. // NextKeyBytes returns the key following an initial key as a byte slice.
@ -392,12 +455,14 @@ func (m *Map) NextKey(key, nextKeyOut interface{}) error {
// Passing nil will return the first key. // Passing nil will return the first key.
// //
// Use Iterate if you want to traverse all entries in the map. // Use Iterate if you want to traverse all entries in the map.
//
// Returns nil if there are no more keys.
func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) { func (m *Map) NextKeyBytes(key interface{}) ([]byte, error) {
nextKey := make([]byte, m.abi.KeySize) nextKey := make([]byte, m.abi.KeySize)
nextKeyPtr := internal.NewSlicePointer(nextKey) nextKeyPtr := internal.NewSlicePointer(nextKey)
err := m.nextKey(key, nextKeyPtr) err := m.nextKey(key, nextKeyPtr)
if IsNotExist(err) { if errors.Is(err, ErrKeyNotExist) {
return nil, nil return nil, nil
} }
@ -413,12 +478,14 @@ func (m *Map) nextKey(key interface{}, nextKeyOut internal.Pointer) error {
if key != nil { if key != nil {
keyPtr, err = marshalPtr(key, int(m.abi.KeySize)) keyPtr, err = marshalPtr(key, int(m.abi.KeySize))
if err != nil { if err != nil {
return errors.WithMessage(err, "can't marshal key") return fmt.Errorf("can't marshal key: %w", err)
} }
} }
err = bpfMapGetNextKey(m.fd, keyPtr, nextKeyOut) if err = bpfMapGetNextKey(m.fd, keyPtr, nextKeyOut); err != nil {
return errors.WithMessage(err, "can't get next key") return fmt.Errorf("next key failed: %w", err)
}
return nil
} }
// Iterate traverses a map. // Iterate traverses a map.
@ -469,7 +536,7 @@ func (m *Map) Clone() (*Map, error) {
dup, err := m.fd.Dup() dup, err := m.fd.Dup()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't clone map") return nil, fmt.Errorf("can't clone map: %w", err)
} }
return newMap(dup, m.name, &m.abi) return newMap(dup, m.name, &m.abi)
@ -479,7 +546,30 @@ func (m *Map) Clone() (*Map, error) {
// //
// This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional // This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional
func (m *Map) Pin(fileName string) error { func (m *Map) Pin(fileName string) error {
return bpfPinObject(fileName, m.fd) return internal.BPFObjPin(fileName, m.fd)
}
// Freeze prevents a map to be modified from user space.
//
// It makes no changes to kernel-side restrictions.
func (m *Map) Freeze() error {
if err := haveMapMutabilityModifiers(); err != nil {
return fmt.Errorf("can't freeze map: %w", err)
}
if err := bpfMapFreeze(m.fd); err != nil {
return fmt.Errorf("can't freeze map: %w", err)
}
return nil
}
func (m *Map) populate(contents []MapKV) error {
for _, kv := range contents {
if err := m.Put(kv.Key, kv.Value); err != nil {
return fmt.Errorf("key %v: %w", kv.Key, err)
}
}
return nil
} }
// LoadPinnedMap load a Map from a BPF file. // LoadPinnedMap load a Map from a BPF file.
@ -487,7 +577,7 @@ func (m *Map) Pin(fileName string) error {
// The function is not compatible with nested maps. // The function is not compatible with nested maps.
// Use LoadPinnedMapExplicit in these situations. // Use LoadPinnedMapExplicit in these situations.
func LoadPinnedMap(fileName string) (*Map, error) { func LoadPinnedMap(fileName string) (*Map, error) {
fd, err := bpfGetObject(fileName) fd, err := internal.BPFObjGet(fileName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -501,7 +591,7 @@ func LoadPinnedMap(fileName string) (*Map, error) {
// LoadPinnedMapExplicit loads a map with explicit parameters. // LoadPinnedMapExplicit loads a map with explicit parameters.
func LoadPinnedMapExplicit(fileName string, abi *MapABI) (*Map, error) { func LoadPinnedMapExplicit(fileName string, abi *MapABI) (*Map, error) {
fd, err := bpfGetObject(fileName) fd, err := internal.BPFObjGet(fileName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -516,18 +606,7 @@ func unmarshalMap(buf []byte) (*Map, error) {
// Looking up an entry in a nested map or prog array returns an id, // Looking up an entry in a nested map or prog array returns an id,
// not an fd. // not an fd.
id := internal.NativeEndian.Uint32(buf) id := internal.NativeEndian.Uint32(buf)
fd, err := bpfGetMapFDByID(id) return NewMapFromID(MapID(id))
if err != nil {
return nil, err
}
name, abi, err := newMapABIFromFd(fd)
if err != nil {
_ = fd.Close()
return nil, err
}
return newMap(fd, name, abi)
} }
// MarshalBinary implements BinaryMarshaler. // MarshalBinary implements BinaryMarshaler.
@ -542,6 +621,60 @@ func (m *Map) MarshalBinary() ([]byte, error) {
return buf, nil return buf, nil
} }
func patchValue(value []byte, typ btf.Type, replacements map[string]interface{}) error {
replaced := make(map[string]bool)
replace := func(name string, offset, size int, replacement interface{}) error {
if offset+size > len(value) {
return fmt.Errorf("%s: offset %d(+%d) is out of bounds", name, offset, size)
}
buf, err := marshalBytes(replacement, size)
if err != nil {
return fmt.Errorf("marshal %s: %w", name, err)
}
copy(value[offset:offset+size], buf)
replaced[name] = true
return nil
}
switch parent := typ.(type) {
case *btf.Datasec:
for _, secinfo := range parent.Vars {
name := string(secinfo.Type.(*btf.Var).Name)
replacement, ok := replacements[name]
if !ok {
continue
}
err := replace(name, int(secinfo.Offset), int(secinfo.Size), replacement)
if err != nil {
return err
}
}
default:
return fmt.Errorf("patching %T is not supported", typ)
}
if len(replaced) == len(replacements) {
return nil
}
var missing []string
for name := range replacements {
if !replaced[name] {
missing = append(missing, name)
}
}
if len(missing) == 1 {
return fmt.Errorf("unknown field: %s", missing[0])
}
return fmt.Errorf("unknown fields: %s", strings.Join(missing, ","))
}
// MapIterator iterates a Map. // MapIterator iterates a Map.
// //
// See Map.Iterate. // See Map.Iterate.
@ -562,8 +695,6 @@ func newMapIterator(target *Map) *MapIterator {
} }
} }
var errIterationAborted = errors.New("iteration aborted")
// Next decodes the next key and value. // Next decodes the next key and value.
// //
// Iterating a hash map from which keys are being deleted is not // Iterating a hash map from which keys are being deleted is not
@ -599,7 +730,7 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool {
mi.prevKey = mi.prevBytes mi.prevKey = mi.prevBytes
mi.err = mi.target.Lookup(nextBytes, valueOut) mi.err = mi.target.Lookup(nextBytes, valueOut)
if IsNotExist(mi.err) { if errors.Is(mi.err, ErrKeyNotExist) {
// Even though the key should be valid, we couldn't look up // Even though the key should be valid, we couldn't look up
// its value. If we're iterating a hash map this is probably // its value. If we're iterating a hash map this is probably
// because a concurrent delete removed the value before we // because a concurrent delete removed the value before we
@ -618,26 +749,50 @@ func (mi *MapIterator) Next(keyOut, valueOut interface{}) bool {
return mi.err == nil return mi.err == nil
} }
mi.err = errIterationAborted mi.err = fmt.Errorf("%w", ErrIterationAborted)
return false return false
} }
// Err returns any encountered error. // Err returns any encountered error.
// //
// The method must be called after Next returns nil. // The method must be called after Next returns nil.
//
// Returns ErrIterationAborted if it wasn't possible to do a full iteration.
func (mi *MapIterator) Err() error { func (mi *MapIterator) Err() error {
return mi.err return mi.err
} }
// IsNotExist returns true if the error indicates that a // MapGetNextID returns the ID of the next eBPF map.
// key doesn't exist. //
func IsNotExist(err error) bool { // Returns ErrNotExist, if there is no next eBPF map.
return errors.Cause(err) == unix.ENOENT func MapGetNextID(startID MapID) (MapID, error) {
id, err := objGetNextID(internal.BPF_MAP_GET_NEXT_ID, uint32(startID))
return MapID(id), err
} }
// IsIterationAborted returns true if the iteration was aborted. // NewMapFromID returns the map for a given id.
// //
// This occurs when keys are deleted from a hash map during iteration. // Returns ErrNotExist, if there is no eBPF map with the given id.
func IsIterationAborted(err error) bool { func NewMapFromID(id MapID) (*Map, error) {
return errors.Cause(err) == errIterationAborted fd, err := bpfObjGetFDByID(internal.BPF_MAP_GET_FD_BY_ID, uint32(id))
if err != nil {
return nil, err
}
name, abi, err := newMapABIFromFd(fd)
if err != nil {
_ = fd.Close()
return nil, err
}
return newMap(fd, name, abi)
}
// ID returns the systemwide unique ID of the map.
func (m *Map) ID() (MapID, error) {
info, err := bpfGetMapInfoByFD(m.fd)
if err != nil {
return MapID(0), err
}
return MapID(info.id), nil
} }

View File

@ -4,13 +4,13 @@ import (
"bytes" "bytes"
"encoding" "encoding"
"encoding/binary" "encoding/binary"
"errors"
"fmt"
"reflect" "reflect"
"runtime" "runtime"
"unsafe" "unsafe"
"github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal"
"github.com/pkg/errors"
) )
func marshalPtr(data interface{}, length int) (internal.Pointer, error) { func marshalPtr(data interface{}, length int) (internal.Pointer, error) {
@ -46,7 +46,9 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) {
default: default:
var wr bytes.Buffer var wr bytes.Buffer
err = binary.Write(&wr, internal.NativeEndian, value) err = binary.Write(&wr, internal.NativeEndian, value)
err = errors.Wrapf(err, "encoding %T", value) if err != nil {
err = fmt.Errorf("encoding %T: %v", value, err)
}
buf = wr.Bytes() buf = wr.Bytes()
} }
if err != nil { if err != nil {
@ -54,7 +56,7 @@ func marshalBytes(data interface{}, length int) (buf []byte, err error) {
} }
if len(buf) != length { if len(buf) != length {
return nil, errors.Errorf("%T doesn't marshal to %d bytes", data, length) return nil, fmt.Errorf("%T doesn't marshal to %d bytes", data, length)
} }
return buf, nil return buf, nil
} }
@ -95,8 +97,10 @@ func unmarshalBytes(data interface{}, buf []byte) error {
return errors.New("require pointer to []byte") return errors.New("require pointer to []byte")
default: default:
rd := bytes.NewReader(buf) rd := bytes.NewReader(buf)
err := binary.Read(rd, internal.NativeEndian, value) if err := binary.Read(rd, internal.NativeEndian, value); err != nil {
return errors.Wrapf(err, "decoding %T", value) return fmt.Errorf("decoding %T: %v", value, err)
}
return nil
} }
} }
@ -120,7 +124,7 @@ func marshalPerCPUValue(slice interface{}, elemLength int) (internal.Pointer, er
sliceValue := reflect.ValueOf(slice) sliceValue := reflect.ValueOf(slice)
sliceLen := sliceValue.Len() sliceLen := sliceValue.Len()
if sliceLen > possibleCPUs { if sliceLen > possibleCPUs {
return internal.Pointer{}, errors.Errorf("per-CPU value exceeds number of CPUs") return internal.Pointer{}, fmt.Errorf("per-CPU value exceeds number of CPUs")
} }
alignedElemLength := align(elemLength, 8) alignedElemLength := align(elemLength, 8)
@ -147,7 +151,7 @@ func marshalPerCPUValue(slice interface{}, elemLength int) (internal.Pointer, er
func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) error { func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) error {
slicePtrType := reflect.TypeOf(slicePtr) slicePtrType := reflect.TypeOf(slicePtr)
if slicePtrType.Kind() != reflect.Ptr || slicePtrType.Elem().Kind() != reflect.Slice { if slicePtrType.Kind() != reflect.Ptr || slicePtrType.Elem().Kind() != reflect.Slice {
return errors.Errorf("per-cpu value requires pointer to slice") return fmt.Errorf("per-cpu value requires pointer to slice")
} }
possibleCPUs, err := internal.PossibleCPUs() possibleCPUs, err := internal.PossibleCPUs()
@ -166,7 +170,7 @@ func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) erro
step := len(buf) / possibleCPUs step := len(buf) / possibleCPUs
if step < elemLength { if step < elemLength {
return errors.Errorf("per-cpu element length is larger than available data") return fmt.Errorf("per-cpu element length is larger than available data")
} }
for i := 0; i < possibleCPUs; i++ { for i := 0; i < possibleCPUs; i++ {
var elem interface{} var elem interface{}
@ -184,7 +188,7 @@ func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) erro
err := unmarshalBytes(elem, elemBytes) err := unmarshalBytes(elem, elemBytes)
if err != nil { if err != nil {
return errors.Wrapf(err, "cpu %d", i) return fmt.Errorf("cpu %d: %w", i, err)
} }
buf = buf[step:] buf = buf[step:]

246
vendor/github.com/cilium/ebpf/prog.go generated vendored
View File

@ -2,20 +2,25 @@ package ebpf
import ( import (
"bytes" "bytes"
"encoding/binary"
"errors"
"fmt" "fmt"
"math" "math"
"strings" "strings"
"time" "time"
"unsafe"
"github.com/cilium/ebpf/asm" "github.com/cilium/ebpf/asm"
"github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf" "github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/unix" "github.com/cilium/ebpf/internal/unix"
"github.com/pkg/errors"
) )
// ErrNotSupported is returned whenever the kernel doesn't support a feature.
var ErrNotSupported = internal.ErrNotSupported
// ProgramID represents the unique ID of an eBPF program
type ProgramID uint32
const ( const (
// Number of bytes to pad the output buffer for BPF_PROG_TEST_RUN. // Number of bytes to pad the output buffer for BPF_PROG_TEST_RUN.
// This is currently the maximum of spare space allocated for SKB // This is currently the maximum of spare space allocated for SKB
@ -42,16 +47,32 @@ type ProgramSpec struct {
// Name is passed to the kernel as a debug aid. Must only contain // Name is passed to the kernel as a debug aid. Must only contain
// alpha numeric and '_' characters. // alpha numeric and '_' characters.
Name string Name string
// Type determines at which hook in the kernel a program will run.
Type ProgramType Type ProgramType
AttachType AttachType AttachType AttachType
// Name of a kernel data structure to attach to. It's interpretation
// depends on Type and AttachType.
AttachTo string
Instructions asm.Instructions Instructions asm.Instructions
// License of the program. Some helpers are only available if
// the license is deemed compatible with the GPL.
//
// See https://www.kernel.org/doc/html/latest/process/license-rules.html#id1
License string License string
// Version used by tracing programs.
//
// Deprecated: superseded by BTF.
KernelVersion uint32 KernelVersion uint32
// The BTF associated with this program. Changing Instructions // The BTF associated with this program. Changing Instructions
// will most likely invalidate the contained data, and may // will most likely invalidate the contained data, and may
// result in errors when attempting to load it into the kernel. // result in errors when attempting to load it into the kernel.
BTF *btf.Program BTF *btf.Program
// The byte order this program was compiled for, may be nil.
ByteOrder binary.ByteOrder
} }
// Copy returns a copy of the spec. // Copy returns a copy of the spec.
@ -77,6 +98,7 @@ type Program struct {
fd *internal.FD fd *internal.FD
name string name string
abi ProgramABI abi ProgramABI
attachType AttachType
} }
// NewProgram creates a new Program. // NewProgram creates a new Program.
@ -97,8 +119,8 @@ func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, er
} }
handle, err := btf.NewHandle(btf.ProgramSpec(spec.BTF)) handle, err := btf.NewHandle(btf.ProgramSpec(spec.BTF))
if err != nil && !btf.IsNotSupported(err) { if err != nil && !errors.Is(err, btf.ErrNotSupported) {
return nil, errors.Wrap(err, "can't load BTF") return nil, fmt.Errorf("can't load BTF: %w", err)
} }
return newProgramWithBTF(spec, handle, opts) return newProgramWithBTF(spec, handle, opts)
@ -130,6 +152,7 @@ func newProgramWithBTF(spec *ProgramSpec, btf *btf.Handle, opts ProgramOptions)
return prog, nil return prog, nil
} }
logErr := err
if opts.LogLevel == 0 { if opts.LogLevel == 0 {
// Re-run with the verifier enabled to get better error messages. // Re-run with the verifier enabled to get better error messages.
logBuf = make([]byte, logSize) logBuf = make([]byte, logSize)
@ -137,11 +160,11 @@ func newProgramWithBTF(spec *ProgramSpec, btf *btf.Handle, opts ProgramOptions)
attr.logSize = uint32(len(logBuf)) attr.logSize = uint32(len(logBuf))
attr.logBuf = internal.NewSlicePointer(logBuf) attr.logBuf = internal.NewSlicePointer(logBuf)
_, logErr := bpfProgLoad(attr) _, logErr = bpfProgLoad(attr)
err = internal.ErrorWithLog(err, logBuf, logErr)
} }
return nil, errors.Wrap(err, "can't load program") err = internal.ErrorWithLog(err, logBuf, logErr)
return nil, fmt.Errorf("can't load program: %w", err)
} }
// NewProgramFromFD creates a program from a raw fd. // NewProgramFromFD creates a program from a raw fd.
@ -181,6 +204,10 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
return nil, errors.New("License cannot be empty") return nil, errors.New("License cannot be empty")
} }
if spec.ByteOrder != nil && spec.ByteOrder != internal.NativeEndian {
return nil, fmt.Errorf("can't load %s program on %s", spec.ByteOrder, internal.NativeEndian)
}
buf := bytes.NewBuffer(make([]byte, 0, len(spec.Instructions)*asm.InstructionSize)) buf := bytes.NewBuffer(make([]byte, 0, len(spec.Instructions)*asm.InstructionSize))
err := spec.Instructions.Marshal(buf, internal.NativeEndian) err := spec.Instructions.Marshal(buf, internal.NativeEndian)
if err != nil { if err != nil {
@ -195,15 +222,11 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
insCount: insCount, insCount: insCount,
instructions: internal.NewSlicePointer(bytecode), instructions: internal.NewSlicePointer(bytecode),
license: internal.NewStringPointer(spec.License), license: internal.NewStringPointer(spec.License),
} kernelVersion: spec.KernelVersion,
name, err := newBPFObjName(spec.Name)
if err != nil {
return nil, err
} }
if haveObjName() == nil { if haveObjName() == nil {
attr.progName = name attr.progName = newBPFObjName(spec.Name)
} }
if handle != nil && spec.BTF != nil { if handle != nil && spec.BTF != nil {
@ -211,7 +234,7 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
recSize, bytes, err := btf.ProgramLineInfos(spec.BTF) recSize, bytes, err := btf.ProgramLineInfos(spec.BTF)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't get BTF line infos") return nil, fmt.Errorf("can't get BTF line infos: %w", err)
} }
attr.lineInfoRecSize = recSize attr.lineInfoRecSize = recSize
attr.lineInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize)) attr.lineInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
@ -219,13 +242,23 @@ func convertProgramSpec(spec *ProgramSpec, handle *btf.Handle) (*bpfProgLoadAttr
recSize, bytes, err = btf.ProgramFuncInfos(spec.BTF) recSize, bytes, err = btf.ProgramFuncInfos(spec.BTF)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't get BTF function infos") return nil, fmt.Errorf("can't get BTF function infos: %w", err)
} }
attr.funcInfoRecSize = recSize attr.funcInfoRecSize = recSize
attr.funcInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize)) attr.funcInfoCnt = uint32(uint64(len(bytes)) / uint64(recSize))
attr.funcInfo = internal.NewSlicePointer(bytes) attr.funcInfo = internal.NewSlicePointer(bytes)
} }
if spec.AttachTo != "" {
target, err := resolveBTFType(spec.AttachTo, spec.Type, spec.AttachType)
if err != nil {
return nil, err
}
if target != nil {
attr.attachBTFID = target.ID()
}
}
return attr, nil return attr, nil
} }
@ -267,7 +300,7 @@ func (p *Program) Clone() (*Program, error) {
dup, err := p.fd.Dup() dup, err := p.fd.Dup()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't clone program") return nil, fmt.Errorf("can't clone program: %w", err)
} }
return newProgram(dup, p.name, &p.abi), nil return newProgram(dup, p.name, &p.abi), nil
@ -277,7 +310,10 @@ func (p *Program) Clone() (*Program, error) {
// //
// This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional // This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional
func (p *Program) Pin(fileName string) error { func (p *Program) Pin(fileName string) error {
return errors.Wrap(bpfPinObject(fileName, p.fd), "can't pin program") if err := internal.BPFObjPin(fileName, p.fd); err != nil {
return fmt.Errorf("can't pin program: %w", err)
}
return nil
} }
// Close unloads the program from the kernel. // Close unloads the program from the kernel.
@ -297,23 +333,33 @@ func (p *Program) Close() error {
// //
// This function requires at least Linux 4.12. // This function requires at least Linux 4.12.
func (p *Program) Test(in []byte) (uint32, []byte, error) { func (p *Program) Test(in []byte) (uint32, []byte, error) {
ret, out, _, err := p.testRun(in, 1) ret, out, _, err := p.testRun(in, 1, nil)
return ret, out, errors.Wrap(err, "can't test program") if err != nil {
return ret, nil, fmt.Errorf("can't test program: %w", err)
}
return ret, out, nil
} }
// Benchmark runs the Program with the given input for a number of times // Benchmark runs the Program with the given input for a number of times
// and returns the time taken per iteration. // and returns the time taken per iteration.
// //
// The returned value is the return value of the last execution of // Returns the result of the last execution of the program and the time per
// the program. // run or an error. reset is called whenever the benchmark syscall is
// interrupted, and should be set to testing.B.ResetTimer or similar.
//
// Note: profiling a call to this function will skew it's results, see
// https://github.com/cilium/ebpf/issues/24
// //
// This function requires at least Linux 4.12. // This function requires at least Linux 4.12.
func (p *Program) Benchmark(in []byte, repeat int) (uint32, time.Duration, error) { func (p *Program) Benchmark(in []byte, repeat int, reset func()) (uint32, time.Duration, error) {
ret, _, total, err := p.testRun(in, repeat) ret, _, total, err := p.testRun(in, repeat, reset)
return ret, total, errors.Wrap(err, "can't benchmark program") if err != nil {
return ret, total, fmt.Errorf("can't benchmark program: %w", err)
}
return ret, total, nil
} }
var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() bool { var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() (bool, error) {
prog, err := NewProgram(&ProgramSpec{ prog, err := NewProgram(&ProgramSpec{
Type: SocketFilter, Type: SocketFilter,
Instructions: asm.Instructions{ Instructions: asm.Instructions{
@ -324,31 +370,26 @@ var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() b
}) })
if err != nil { if err != nil {
// This may be because we lack sufficient permissions, etc. // This may be because we lack sufficient permissions, etc.
return false return false, err
} }
defer prog.Close() defer prog.Close()
fd, err := prog.fd.Value()
if err != nil {
return false
}
// Programs require at least 14 bytes input // Programs require at least 14 bytes input
in := make([]byte, 14) in := make([]byte, 14)
attr := bpfProgTestRunAttr{ attr := bpfProgTestRunAttr{
fd: fd, fd: uint32(prog.FD()),
dataSizeIn: uint32(len(in)), dataSizeIn: uint32(len(in)),
dataIn: internal.NewSlicePointer(in), dataIn: internal.NewSlicePointer(in),
} }
_, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) err = bpfProgTestRun(&attr)
// Check for EINVAL specifically, rather than err != nil since we // Check for EINVAL specifically, rather than err != nil since we
// otherwise misdetect due to insufficient permissions. // otherwise misdetect due to insufficient permissions.
return errors.Cause(err) != unix.EINVAL return !errors.Is(err, unix.EINVAL), nil
}) })
func (p *Program) testRun(in []byte, repeat int) (uint32, []byte, time.Duration, error) { func (p *Program) testRun(in []byte, repeat int, reset func()) (uint32, []byte, time.Duration, error) {
if uint(repeat) > math.MaxUint32 { if uint(repeat) > math.MaxUint32 {
return 0, nil, 0, fmt.Errorf("repeat is too high") return 0, nil, 0, fmt.Errorf("repeat is too high")
} }
@ -386,9 +427,20 @@ func (p *Program) testRun(in []byte, repeat int) (uint32, []byte, time.Duration,
repeat: uint32(repeat), repeat: uint32(repeat),
} }
_, err = internal.BPF(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) for {
if err != nil { err = bpfProgTestRun(&attr)
return 0, nil, 0, errors.Wrap(err, "can't run test") if err == nil {
break
}
if errors.Is(err, unix.EINTR) {
if reset != nil {
reset()
}
continue
}
return 0, nil, 0, fmt.Errorf("can't run test: %w", err)
} }
if int(attr.dataSizeOut) > cap(out) { if int(attr.dataSizeOut) > cap(out) {
@ -410,18 +462,7 @@ func unmarshalProgram(buf []byte) (*Program, error) {
// Looking up an entry in a nested map or prog array returns an id, // Looking up an entry in a nested map or prog array returns an id,
// not an fd. // not an fd.
id := internal.NativeEndian.Uint32(buf) id := internal.NativeEndian.Uint32(buf)
fd, err := bpfGetProgramFDByID(id) return NewProgramFromID(ProgramID(id))
if err != nil {
return nil, err
}
name, abi, err := newProgramABIFromFd(fd)
if err != nil {
_ = fd.Close()
return nil, err
}
return newProgram(fd, name, abi), nil
} }
// MarshalBinary implements BinaryMarshaler. // MarshalBinary implements BinaryMarshaler.
@ -436,7 +477,9 @@ func (p *Program) MarshalBinary() ([]byte, error) {
return buf, nil return buf, nil
} }
// Attach a Program to a container object fd // Attach a Program.
//
// Deprecated: use link.RawAttachProgram instead.
func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error { func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
if fd < 0 { if fd < 0 {
return errors.New("invalid fd") return errors.New("invalid fd")
@ -447,42 +490,47 @@ func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error {
return err return err
} }
attr := bpfProgAlterAttr{ attr := internal.BPFProgAttachAttr{
targetFd: uint32(fd), TargetFd: uint32(fd),
attachBpfFd: pfd, AttachBpfFd: pfd,
attachType: uint32(typ), AttachType: uint32(typ),
attachFlags: uint32(flags), AttachFlags: uint32(flags),
} }
return bpfProgAlter(_ProgAttach, &attr) return internal.BPFProgAttach(&attr)
} }
// Detach a Program from a container object fd // Detach a Program.
//
// Deprecated: use link.RawDetachProgram instead.
func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error { func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error {
if fd < 0 { if fd < 0 {
return errors.New("invalid fd") return errors.New("invalid fd")
} }
if flags != 0 {
return errors.New("flags must be zero")
}
pfd, err := p.fd.Value() pfd, err := p.fd.Value()
if err != nil { if err != nil {
return err return err
} }
attr := bpfProgAlterAttr{ attr := internal.BPFProgDetachAttr{
targetFd: uint32(fd), TargetFd: uint32(fd),
attachBpfFd: pfd, AttachBpfFd: pfd,
attachType: uint32(typ), AttachType: uint32(typ),
attachFlags: uint32(flags),
} }
return bpfProgAlter(_ProgDetach, &attr) return internal.BPFProgDetach(&attr)
} }
// LoadPinnedProgram loads a Program from a BPF file. // LoadPinnedProgram loads a Program from a BPF file.
// //
// Requires at least Linux 4.11. // Requires at least Linux 4.11.
func LoadPinnedProgram(fileName string) (*Program, error) { func LoadPinnedProgram(fileName string) (*Program, error) {
fd, err := bpfGetObject(fileName) fd, err := internal.BPFObjGet(fileName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -490,7 +538,7 @@ func LoadPinnedProgram(fileName string) (*Program, error) {
name, abi, err := newProgramABIFromFd(fd) name, abi, err := newProgramABIFromFd(fd)
if err != nil { if err != nil {
_ = fd.Close() _ = fd.Close()
return nil, errors.Wrapf(err, "can't get ABI for %s", fileName) return nil, fmt.Errorf("can't get ABI for %s: %w", fileName, err)
} }
return newProgram(fd, name, abi), nil return newProgram(fd, name, abi), nil
@ -512,9 +560,63 @@ func SanitizeName(name string, replacement rune) string {
}, name) }, name)
} }
// IsNotSupported returns true if an error occurred because // ProgramGetNextID returns the ID of the next eBPF program.
// the kernel does not have support for a specific feature. //
func IsNotSupported(err error) bool { // Returns ErrNotExist, if there is no next eBPF program.
_, notSupported := errors.Cause(err).(*internal.UnsupportedFeatureError) func ProgramGetNextID(startID ProgramID) (ProgramID, error) {
return notSupported id, err := objGetNextID(internal.BPF_PROG_GET_NEXT_ID, uint32(startID))
return ProgramID(id), err
}
// NewProgramFromID returns the program for a given id.
//
// Returns ErrNotExist, if there is no eBPF program with the given id.
func NewProgramFromID(id ProgramID) (*Program, error) {
fd, err := bpfObjGetFDByID(internal.BPF_PROG_GET_FD_BY_ID, uint32(id))
if err != nil {
return nil, err
}
name, abi, err := newProgramABIFromFd(fd)
if err != nil {
_ = fd.Close()
return nil, err
}
return newProgram(fd, name, abi), nil
}
// ID returns the systemwide unique ID of the program.
func (p *Program) ID() (ProgramID, error) {
info, err := bpfGetProgInfoByFD(p.fd)
if err != nil {
return ProgramID(0), err
}
return ProgramID(info.id), nil
}
func resolveBTFType(name string, progType ProgramType, attachType AttachType) (btf.Type, error) {
kernel, err := btf.LoadKernelSpec()
if err != nil {
return nil, fmt.Errorf("can't resolve BTF type %s: %w", name, err)
}
type match struct {
p ProgramType
a AttachType
}
target := match{progType, attachType}
switch target {
case match{Tracing, AttachTraceIter}:
var target btf.Func
if err := kernel.FindType("bpf_iter_"+name, &target); err != nil {
return nil, fmt.Errorf("can't resolve BTF for iterator %s: %w", name, err)
}
return &target, nil
default:
return nil, nil
}
} }

View File

@ -13,6 +13,11 @@ The library is maintained by [Cloudflare](https://www.cloudflare.com) and [Ciliu
The package is production ready, but **the API is explicitly unstable The package is production ready, but **the API is explicitly unstable
right now**. Expect to update your code if you want to follow along. 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 ## Useful resources
* [Cilium eBPF documentation](https://cilium.readthedocs.io/en/latest/bpf/#bpf-guide) (recommended) * [Cilium eBPF documentation](https://cilium.readthedocs.io/en/latest/bpf/#bpf-guide) (recommended)

View File

@ -1,15 +1,19 @@
package ebpf package ebpf
import ( import (
"path/filepath" "errors"
"strings" "fmt"
"os"
"unsafe" "unsafe"
"github.com/cilium/ebpf/internal" "github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/btf" "github.com/cilium/ebpf/internal/btf"
"github.com/cilium/ebpf/internal/unix" "github.com/cilium/ebpf/internal/unix"
)
"github.com/pkg/errors" // Generic errors returned by BPF syscalls.
var (
ErrNotExist = errors.New("requested object does not exist")
) )
// bpfObjName is a null-terminated string made up of // bpfObjName is a null-terminated string made up of
@ -17,18 +21,15 @@ import (
type bpfObjName [unix.BPF_OBJ_NAME_LEN]byte type bpfObjName [unix.BPF_OBJ_NAME_LEN]byte
// newBPFObjName truncates the result if it is too long. // newBPFObjName truncates the result if it is too long.
func newBPFObjName(name string) (bpfObjName, error) { func newBPFObjName(name string) bpfObjName {
idx := strings.IndexFunc(name, invalidBPFObjNameChar)
if idx != -1 {
return bpfObjName{}, errors.Errorf("invalid character '%c' in name '%s'", name[idx], name)
}
var result bpfObjName var result bpfObjName
copy(result[:unix.BPF_OBJ_NAME_LEN-1], name) copy(result[:unix.BPF_OBJ_NAME_LEN-1], name)
return result, nil return result
} }
func invalidBPFObjNameChar(char rune) bool { func invalidBPFObjNameChar(char rune) bool {
dotAllowed := objNameAllowsDot() == nil
switch { switch {
case char >= 'A' && char <= 'Z': case char >= 'A' && char <= 'Z':
fallthrough fallthrough
@ -36,6 +37,8 @@ func invalidBPFObjNameChar(char rune) bool {
fallthrough fallthrough
case char >= '0' && char <= '9': case char >= '0' && char <= '9':
fallthrough fallthrough
case dotAllowed && char == '.':
fallthrough
case char == '_': case char == '_':
return false return false
default: default:
@ -76,12 +79,6 @@ type bpfMapInfo struct {
mapName bpfObjName // since 4.15 ad5b177bd73f mapName bpfObjName // since 4.15 ad5b177bd73f
} }
type bpfPinObjAttr struct {
fileName internal.Pointer
fd uint32
padding uint32
}
type bpfProgLoadAttr struct { type bpfProgLoadAttr struct {
progType ProgramType progType ProgramType
insCount uint32 insCount uint32
@ -102,6 +99,8 @@ type bpfProgLoadAttr struct {
lineInfoRecSize uint32 lineInfoRecSize uint32
lineInfo internal.Pointer lineInfo internal.Pointer
lineInfoCnt uint32 lineInfoCnt uint32
attachBTFID btf.TypeID
attachProgFd uint32
} }
type bpfProgInfo struct { type bpfProgInfo struct {
@ -130,13 +129,6 @@ type bpfProgTestRunAttr struct {
duration uint32 duration uint32
} }
type bpfProgAlterAttr struct {
targetFd uint32
attachBpfFd uint32
attachType uint32
attachFlags uint32
}
type bpfObjGetInfoByFDAttr struct { type bpfObjGetInfoByFDAttr struct {
fd uint32 fd uint32
infoLen uint32 infoLen uint32
@ -148,9 +140,19 @@ type bpfGetFDByIDAttr struct {
next uint32 next uint32
} }
type bpfMapFreezeAttr struct {
mapFd uint32
}
type bpfObjGetNextIDAttr struct {
startID uint32
nextID uint32
openFlags uint32
}
func bpfProgLoad(attr *bpfProgLoadAttr) (*internal.FD, error) { func bpfProgLoad(attr *bpfProgLoadAttr) (*internal.FD, error) {
for { for {
fd, err := internal.BPF(_ProgLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) fd, err := internal.BPF(internal.BPF_PROG_LOAD, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
// As of ~4.20 the verifier can be interrupted by a signal, // As of ~4.20 the verifier can be interrupted by a signal,
// and returns EAGAIN in that case. // and returns EAGAIN in that case.
if err == unix.EAGAIN { if err == unix.EAGAIN {
@ -165,13 +167,17 @@ func bpfProgLoad(attr *bpfProgLoadAttr) (*internal.FD, error) {
} }
} }
func bpfProgAlter(cmd int, attr *bpfProgAlterAttr) error { func bpfProgTestRun(attr *bpfProgTestRunAttr) error {
_, err := internal.BPF(cmd, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) _, err := internal.BPF(internal.BPF_PROG_TEST_RUN, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err return err
} }
func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) { func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) {
fd, err := internal.BPF(_MapCreate, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) fd, err := internal.BPF(internal.BPF_MAP_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
if errors.Is(err, os.ErrPermission) {
return nil, errors.New("permission denied or insufficient rlimit to lock memory for map")
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -179,7 +185,7 @@ func bpfMapCreate(attr *bpfMapCreateAttr) (*internal.FD, error) {
return internal.NewFD(uint32(fd)), nil return internal.NewFD(uint32(fd)), nil
} }
var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() bool { var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() (bool, error) {
inner, err := bpfMapCreate(&bpfMapCreateAttr{ inner, err := bpfMapCreate(&bpfMapCreateAttr{
mapType: Array, mapType: Array,
keySize: 4, keySize: 4,
@ -187,7 +193,7 @@ var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() bool {
maxEntries: 1, maxEntries: 1,
}) })
if err != nil { if err != nil {
return false return false, err
} }
defer inner.Close() defer inner.Close()
@ -200,11 +206,28 @@ var haveNestedMaps = internal.FeatureTest("nested maps", "4.12", func() bool {
innerMapFd: innerFd, innerMapFd: innerFd,
}) })
if err != nil { if err != nil {
return false return false, nil
} }
_ = nested.Close() _ = nested.Close()
return true return true, nil
})
var haveMapMutabilityModifiers = internal.FeatureTest("read- and write-only maps", "5.2", func() (bool, error) {
// This checks BPF_F_RDONLY_PROG and BPF_F_WRONLY_PROG. Since
// BPF_MAP_FREEZE appeared in 5.2 as well we don't do a separate check.
m, err := bpfMapCreate(&bpfMapCreateAttr{
mapType: Array,
keySize: 4,
valueSize: 4,
maxEntries: 1,
flags: unix.BPF_F_RDONLY_PROG,
})
if err != nil {
return false, nil
}
_ = m.Close()
return true, nil
}) })
func bpfMapLookupElem(m *internal.FD, key, valueOut internal.Pointer) error { func bpfMapLookupElem(m *internal.FD, key, valueOut internal.Pointer) error {
@ -218,8 +241,8 @@ func bpfMapLookupElem(m *internal.FD, key, valueOut internal.Pointer) error {
key: key, key: key,
value: valueOut, value: valueOut,
} }
_, err = internal.BPF(_MapLookupElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) _, err = internal.BPF(internal.BPF_MAP_LOOKUP_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err return wrapMapError(err)
} }
func bpfMapLookupAndDelete(m *internal.FD, key, valueOut internal.Pointer) error { func bpfMapLookupAndDelete(m *internal.FD, key, valueOut internal.Pointer) error {
@ -233,8 +256,8 @@ func bpfMapLookupAndDelete(m *internal.FD, key, valueOut internal.Pointer) error
key: key, key: key,
value: valueOut, value: valueOut,
} }
_, err = internal.BPF(_MapLookupAndDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) _, err = internal.BPF(internal.BPF_MAP_LOOKUP_AND_DELETE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err return wrapMapError(err)
} }
func bpfMapUpdateElem(m *internal.FD, key, valueOut internal.Pointer, flags uint64) error { func bpfMapUpdateElem(m *internal.FD, key, valueOut internal.Pointer, flags uint64) error {
@ -249,8 +272,8 @@ func bpfMapUpdateElem(m *internal.FD, key, valueOut internal.Pointer, flags uint
value: valueOut, value: valueOut,
flags: flags, flags: flags,
} }
_, err = internal.BPF(_MapUpdateElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) _, err = internal.BPF(internal.BPF_MAP_UPDATE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err return wrapMapError(err)
} }
func bpfMapDeleteElem(m *internal.FD, key internal.Pointer) error { func bpfMapDeleteElem(m *internal.FD, key internal.Pointer) error {
@ -263,8 +286,8 @@ func bpfMapDeleteElem(m *internal.FD, key internal.Pointer) error {
mapFd: fd, mapFd: fd,
key: key, key: key,
} }
_, err = internal.BPF(_MapDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) _, err = internal.BPF(internal.BPF_MAP_DELETE_ELEM, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err return wrapMapError(err)
} }
func bpfMapGetNextKey(m *internal.FD, key, nextKeyOut internal.Pointer) error { func bpfMapGetNextKey(m *internal.FD, key, nextKeyOut internal.Pointer) error {
@ -278,42 +301,56 @@ func bpfMapGetNextKey(m *internal.FD, key, nextKeyOut internal.Pointer) error {
key: key, key: key,
value: nextKeyOut, value: nextKeyOut,
} }
_, err = internal.BPF(_MapGetNextKey, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) _, err = internal.BPF(internal.BPF_MAP_GET_NEXT_KEY, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err return wrapMapError(err)
} }
const bpfFSType = 0xcafe4a11 func objGetNextID(cmd internal.BPFCmd, start uint32) (uint32, error) {
attr := bpfObjGetNextIDAttr{
func bpfPinObject(fileName string, fd *internal.FD) error { startID: start,
dirName := filepath.Dir(fileName)
var statfs unix.Statfs_t
if err := unix.Statfs(dirName, &statfs); err != nil {
return err
} }
if uint64(statfs.Type) != bpfFSType { _, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return errors.Errorf("%s is not on a bpf filesystem", fileName) return attr.nextID, wrapObjError(err)
}
func wrapObjError(err error) error {
if err == nil {
return nil
}
if errors.Is(err, unix.ENOENT) {
return fmt.Errorf("%w", ErrNotExist)
} }
value, err := fd.Value() return errors.New(err.Error())
}
func wrapMapError(err error) error {
if err == nil {
return nil
}
if errors.Is(err, unix.ENOENT) {
return ErrKeyNotExist
}
if errors.Is(err, unix.EEXIST) {
return ErrKeyExist
}
return errors.New(err.Error())
}
func bpfMapFreeze(m *internal.FD) error {
fd, err := m.Value()
if err != nil { if err != nil {
return err return err
} }
_, err = internal.BPF(_ObjPin, unsafe.Pointer(&bpfPinObjAttr{ attr := bpfMapFreezeAttr{
fileName: internal.NewStringPointer(fileName), mapFd: fd,
fd: value,
}), 16)
return errors.Wrapf(err, "pin object %s", fileName)
}
func bpfGetObject(fileName string) (*internal.FD, error) {
ptr, err := internal.BPF(_ObjGet, unsafe.Pointer(&bpfPinObjAttr{
fileName: internal.NewStringPointer(fileName),
}), 16)
if err != nil {
return nil, errors.Wrapf(err, "get object %s", fileName)
} }
return internal.NewFD(uint32(ptr)), nil _, err = internal.BPF(internal.BPF_MAP_FREEZE, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return err
} }
func bpfGetObjectInfoByFD(fd *internal.FD, info unsafe.Pointer, size uintptr) error { func bpfGetObjectInfoByFD(fd *internal.FD, info unsafe.Pointer, size uintptr) error {
@ -328,28 +365,51 @@ func bpfGetObjectInfoByFD(fd *internal.FD, info unsafe.Pointer, size uintptr) er
infoLen: uint32(size), infoLen: uint32(size),
info: internal.NewPointer(info), info: internal.NewPointer(info),
} }
_, err = internal.BPF(_ObjGetInfoByFD, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) _, err = internal.BPF(internal.BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
return errors.Wrapf(err, "fd %d", fd) if err != nil {
return fmt.Errorf("fd %d: %w", fd, err)
}
return nil
} }
func bpfGetProgInfoByFD(fd *internal.FD) (*bpfProgInfo, error) { func bpfGetProgInfoByFD(fd *internal.FD) (*bpfProgInfo, error) {
var info bpfProgInfo var info bpfProgInfo
err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)) if err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil {
return &info, errors.Wrap(err, "can't get program info") return nil, fmt.Errorf("can't get program info: %w", err)
}
return &info, nil
} }
func bpfGetMapInfoByFD(fd *internal.FD) (*bpfMapInfo, error) { func bpfGetMapInfoByFD(fd *internal.FD) (*bpfMapInfo, error) {
var info bpfMapInfo var info bpfMapInfo
err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)) err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
return &info, errors.Wrap(err, "can't get map info") if err != nil {
return nil, fmt.Errorf("can't get map info: %w", err)
}
return &info, nil
} }
var haveObjName = internal.FeatureTest("object names", "4.15", func() bool { var haveObjName = internal.FeatureTest("object names", "4.15", func() (bool, error) {
name, err := newBPFObjName("feature_test") attr := bpfMapCreateAttr{
mapType: Array,
keySize: 4,
valueSize: 4,
maxEntries: 1,
mapName: newBPFObjName("feature_test"),
}
fd, err := bpfMapCreate(&attr)
if err != nil { if err != nil {
// This really is a fatal error, but it should be caught return false, nil
// by the unit tests not working. }
return false
_ = fd.Close()
return true, nil
})
var objNameAllowsDot = internal.FeatureTest("dot in object names", "5.2", func() (bool, error) {
if err := haveObjName(); err != nil {
return false, err
} }
attr := bpfMapCreateAttr{ attr := bpfMapCreateAttr{
@ -357,38 +417,22 @@ var haveObjName = internal.FeatureTest("object names", "4.15", func() bool {
keySize: 4, keySize: 4,
valueSize: 4, valueSize: 4,
maxEntries: 1, maxEntries: 1,
mapName: name, mapName: newBPFObjName(".test"),
} }
fd, err := bpfMapCreate(&attr) fd, err := bpfMapCreate(&attr)
if err != nil { if err != nil {
return false return false, nil
} }
_ = fd.Close() _ = fd.Close()
return true return true, nil
}) })
func bpfGetMapFDByID(id uint32) (*internal.FD, error) { func bpfObjGetFDByID(cmd internal.BPFCmd, id uint32) (*internal.FD, error) {
// available from 4.13
attr := bpfGetFDByIDAttr{ attr := bpfGetFDByIDAttr{
id: id, id: id,
} }
ptr, err := internal.BPF(_MapGetFDByID, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) ptr, err := internal.BPF(cmd, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil { return internal.NewFD(uint32(ptr)), wrapObjError(err)
return nil, errors.Wrapf(err, "can't get fd for map id %d", id)
}
return internal.NewFD(uint32(ptr)), nil
}
func bpfGetProgramFDByID(id uint32) (*internal.FD, error) {
// available from 4.13
attr := bpfGetFDByIDAttr{
id: id,
}
ptr, err := internal.BPF(_ProgGetFDByID, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
if err != nil {
return nil, errors.Wrapf(err, "can't get fd for program id %d", id)
}
return internal.NewFD(uint32(ptr)), nil
} }

View File

@ -1,6 +1,6 @@
package ebpf package ebpf
//go:generate stringer -output types_string.go -type=MapType,ProgramType //go:generate stringer -output types_string.go -type=MapType,ProgramType,AttachType
// 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,44 +85,12 @@ 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 { if mt == PerCPUHash || mt == PerCPUArray || mt == LRUCPUHash {
return true return true
} }
return false return false
} }
const (
_MapCreate = iota
_MapLookupElem
_MapUpdateElem
_MapDeleteElem
_MapGetNextKey
_ProgLoad
_ObjPin
_ObjGet
_ProgAttach
_ProgDetach
_ProgTestRun
_ProgGetNextID
_MapGetNextID
_ProgGetFDByID
_MapGetFDByID
_ObjGetInfoByFD
_ProgQuery
_RawTracepointOpen
_BTFLoad
_BTFGetFDByID
_TaskFDQuery
_MapLookupAndDeleteElem
_MapFreeze
)
const (
_Any = iota
_NoExist
_Exist
)
// ProgramType of the eBPF program // ProgramType of the eBPF program
type ProgramType uint32 type ProgramType uint32
@ -219,6 +187,9 @@ const (
AttachTraceRawTp AttachTraceRawTp
AttachTraceFEntry AttachTraceFEntry
AttachTraceFExit AttachTraceFExit
AttachModifyReturn
AttachLSMMac
AttachTraceIter
) )
// AttachFlags of the eBPF program used in BPF_PROG_ATTACH command // AttachFlags of the eBPF program used in BPF_PROG_ATTACH command

View File

@ -1,4 +1,4 @@
// Code generated by "stringer -output types_string.go -type=MapType,ProgramType"; DO NOT EDIT. // Code generated by "stringer -output types_string.go -type=MapType,ProgramType,AttachType"; DO NOT EDIT.
package ebpf package ebpf
@ -89,3 +89,49 @@ func (i ProgramType) String() string {
} }
return _ProgramType_name[_ProgramType_index[i]:_ProgramType_index[i+1]] return _ProgramType_name[_ProgramType_index[i]:_ProgramType_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[AttachNone-0]
_ = x[AttachCGroupInetIngress-0]
_ = x[AttachCGroupInetEgress-1]
_ = x[AttachCGroupInetSockCreate-2]
_ = x[AttachCGroupSockOps-3]
_ = x[AttachSkSKBStreamParser-4]
_ = x[AttachSkSKBStreamVerdict-5]
_ = x[AttachCGroupDevice-6]
_ = x[AttachSkMsgVerdict-7]
_ = x[AttachCGroupInet4Bind-8]
_ = x[AttachCGroupInet6Bind-9]
_ = x[AttachCGroupInet4Connect-10]
_ = x[AttachCGroupInet6Connect-11]
_ = x[AttachCGroupInet4PostBind-12]
_ = x[AttachCGroupInet6PostBind-13]
_ = x[AttachCGroupUDP4Sendmsg-14]
_ = x[AttachCGroupUDP6Sendmsg-15]
_ = x[AttachLircMode2-16]
_ = x[AttachFlowDissector-17]
_ = x[AttachCGroupSysctl-18]
_ = x[AttachCGroupUDP4Recvmsg-19]
_ = x[AttachCGroupUDP6Recvmsg-20]
_ = x[AttachCGroupGetsockopt-21]
_ = x[AttachCGroupSetsockopt-22]
_ = x[AttachTraceRawTp-23]
_ = x[AttachTraceFEntry-24]
_ = x[AttachTraceFExit-25]
_ = x[AttachModifyReturn-26]
_ = x[AttachLSMMac-27]
_ = x[AttachTraceIter-28]
}
const _AttachType_name = "AttachNoneAttachCGroupInetEgressAttachCGroupInetSockCreateAttachCGroupSockOpsAttachSkSKBStreamParserAttachSkSKBStreamVerdictAttachCGroupDeviceAttachSkMsgVerdictAttachCGroupInet4BindAttachCGroupInet6BindAttachCGroupInet4ConnectAttachCGroupInet6ConnectAttachCGroupInet4PostBindAttachCGroupInet6PostBindAttachCGroupUDP4SendmsgAttachCGroupUDP6SendmsgAttachLircMode2AttachFlowDissectorAttachCGroupSysctlAttachCGroupUDP4RecvmsgAttachCGroupUDP6RecvmsgAttachCGroupGetsockoptAttachCGroupSetsockoptAttachTraceRawTpAttachTraceFEntryAttachTraceFExitAttachModifyReturnAttachLSMMacAttachTraceIter"
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}
func (i AttachType) String() string {
if i >= AttachType(len(_AttachType_index)-1) {
return "AttachType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _AttachType_name[_AttachType_index[i]:_AttachType_index[i+1]]
}

View File

@ -78,7 +78,7 @@ specifications as appropriate.
backport version of `libseccomp-dev` is required. See [travis.yml](.travis.yml) for an example on trusty. backport version of `libseccomp-dev` is required. See [travis.yml](.travis.yml) for an example on trusty.
* **btrfs development library.** Required by containerd btrfs support. `btrfs-tools`(Ubuntu, Debian) / `btrfs-progs-devel`(Fedora, CentOS, RHEL) * **btrfs development library.** Required by containerd btrfs support. `btrfs-tools`(Ubuntu, Debian) / `btrfs-progs-devel`(Fedora, CentOS, RHEL)
2. Install **`pkg-config`** (required for linking with `libseccomp`). 2. Install **`pkg-config`** (required for linking with `libseccomp`).
3. Install and setup a Go 1.13.11 development environment. 3. Install and setup a Go 1.13.12 development environment.
4. Make a local clone of this repository. 4. Make a local clone of this repository.
5. Install binary dependencies by running the following command from your cloned `cri/` project directory: 5. Install binary dependencies by running the following command from your cloned `cri/` project directory:
```bash ```bash

View File

@ -37,7 +37,7 @@ import (
imagespec "github.com/opencontainers/image-spec/specs-go/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/klog" "k8s.io/klog/v2"
criconfig "github.com/containerd/cri/pkg/config" criconfig "github.com/containerd/cri/pkg/config"
"github.com/containerd/cri/pkg/constants" "github.com/containerd/cri/pkg/constants"

View File

@ -232,10 +232,10 @@ type PluginConfig struct {
// UnsetSeccompProfile is the profile containerd/cri will use If the provided seccomp profile is // UnsetSeccompProfile is the profile containerd/cri will use If the provided seccomp profile is
// unset (`""`) for a container (default is `unconfined`) // unset (`""`) for a container (default is `unconfined`)
UnsetSeccompProfile string `toml:"unset_seccomp_profile" json:"unsetSeccompProfile"` UnsetSeccompProfile string `toml:"unset_seccomp_profile" json:"unsetSeccompProfile"`
// TolerateMissingHugePagesCgroupController if set to false will error out on create/update // TolerateMissingHugetlbController if set to false will error out on create/update
// container requests with huge page limits if the cgroup controller for hugepages is not present. // container requests with huge page limits if the cgroup controller for hugepages is not present.
// This helps with supporting Kubernetes <=1.18 out of the box. (default is `true`) // This helps with supporting Kubernetes <=1.18 out of the box. (default is `true`)
TolerateMissingHugePagesCgroupController bool `toml:"tolerate_missing_hugepages_controller" json:"tolerateMissingHugePagesCgroupController"` TolerateMissingHugetlbController bool `toml:"tolerate_missing_hugetlb_controller" json:"tolerateMissingHugetlbController"`
// IgnoreImageDefinedVolumes ignores volumes defined by the image. Useful for better resource // IgnoreImageDefinedVolumes ignores volumes defined by the image. Useful for better resource
// isolation, security and early detection of issues in the mount configuration when using // isolation, security and early detection of issues in the mount configuration when using
// ReadOnlyRootFilesystem since containers won't silently mount a temporary volume. // ReadOnlyRootFilesystem since containers won't silently mount a temporary volume.

View File

@ -20,7 +20,7 @@ package config
import ( import (
"github.com/containerd/containerd" "github.com/containerd/containerd"
"k8s.io/kubernetes/pkg/kubelet/server/streaming" "github.com/containerd/cri/pkg/streaming"
) )
// DefaultConfig returns default configurations of cri plugin. // DefaultConfig returns default configurations of cri plugin.
@ -65,7 +65,7 @@ func DefaultConfig() PluginConfig {
}, },
MaxConcurrentDownloads: 3, MaxConcurrentDownloads: 3,
DisableProcMount: false, DisableProcMount: false,
TolerateMissingHugePagesCgroupController: true, TolerateMissingHugetlbController: true,
IgnoreImageDefinedVolumes: false, IgnoreImageDefinedVolumes: false,
} }
} }

View File

@ -23,7 +23,7 @@ import (
"path/filepath" "path/filepath"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"k8s.io/kubernetes/pkg/kubelet/server/streaming" "github.com/containerd/cri/pkg/streaming"
) )
// DefaultConfig returns default configurations of cri plugin. // DefaultConfig returns default configurations of cri plugin.

View File

@ -324,7 +324,7 @@ func WithDevices(osi osinterface.OS, config *runtime.ContainerConfig) oci.SpecOp
Type: string(dev.Type), Type: string(dev.Type),
Major: &dev.Major, Major: &dev.Major,
Minor: &dev.Minor, Minor: &dev.Minor,
Access: dev.Permissions, Access: string(dev.Permissions),
}) })
} }
return nil return nil
@ -408,7 +408,7 @@ func WithSelinuxLabels(process, mount string) oci.SpecOpts {
} }
// WithResources sets the provided resource restrictions // WithResources sets the provided resource restrictions
func WithResources(resources *runtime.LinuxContainerResources, tolerateMissingHugePagesCgroupController bool) oci.SpecOpts { func WithResources(resources *runtime.LinuxContainerResources, tolerateMissingHugetlbController bool) oci.SpecOpts {
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) { return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) {
if resources == nil { if resources == nil {
return nil return nil
@ -451,7 +451,7 @@ func WithResources(resources *runtime.LinuxContainerResources, tolerateMissingHu
if limit != 0 { if limit != 0 {
s.Linux.Resources.Memory.Limit = &limit s.Linux.Resources.Memory.Limit = &limit
} }
if isHugePagesControllerPresent() { if isHugetlbControllerPresent() {
for _, limit := range hugepages { for _, limit := range hugepages {
s.Linux.Resources.HugepageLimits = append(s.Linux.Resources.HugepageLimits, runtimespec.LinuxHugepageLimit{ s.Linux.Resources.HugepageLimits = append(s.Linux.Resources.HugepageLimits, runtimespec.LinuxHugepageLimit{
Pagesize: limit.PageSize, Pagesize: limit.PageSize,
@ -459,9 +459,9 @@ func WithResources(resources *runtime.LinuxContainerResources, tolerateMissingHu
}) })
} }
} else { } else {
if !tolerateMissingHugePagesCgroupController { if !tolerateMissingHugetlbController {
return errors.Errorf("huge pages limits are specified but hugetlb cgroup controller is missing. " + return errors.Errorf("huge pages limits are specified but hugetlb cgroup controller is missing. " +
"Please set tolerate_missing_hugepages_controller to `true` to ignore this error") "Please set tolerate_missing_hugetlb_controller to `true` to ignore this error")
} }
logrus.Warn("hugetlb cgroup controller is absent. skipping huge pages limits") logrus.Warn("hugetlb cgroup controller is absent. skipping huge pages limits")
} }
@ -474,7 +474,7 @@ var (
supportsHugetlb bool supportsHugetlb bool
) )
func isHugePagesControllerPresent() bool { func isHugetlbControllerPresent() bool {
supportsHugetlbOnce.Do(func() { supportsHugetlbOnce.Do(func() {
supportsHugetlb = false supportsHugetlb = false
if IsCgroup2UnifiedMode() { if IsCgroup2UnifiedMode() {

View File

@ -22,6 +22,7 @@ import (
"context" "context"
"path/filepath" "path/filepath"
"sort" "sort"
"strings"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/oci" "github.com/containerd/containerd/oci"
@ -47,6 +48,20 @@ func WithWindowsNetworkNamespace(path string) oci.SpecOpts {
} }
} }
// namedPipePath returns true if the given path is to a named pipe.
func namedPipePath(p string) bool {
return strings.HasPrefix(p, `\\.\pipe\`)
}
// cleanMount returns a cleaned version of the mount path. The input is returned
// as-is if it is a named pipe path.
func cleanMount(p string) string {
if namedPipePath(p) {
return p
}
return filepath.Clean(p)
}
// WithWindowsMounts sorts and adds runtime and CRI mounts to the spec for // WithWindowsMounts sorts and adds runtime and CRI mounts to the spec for
// windows container. // windows container.
func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount) oci.SpecOpts { func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount) oci.SpecOpts {
@ -62,7 +77,7 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
for _, e := range extra { for _, e := range extra {
found := false found := false
for _, c := range criMounts { for _, c := range criMounts {
if filepath.Clean(e.ContainerPath) == filepath.Clean(c.ContainerPath) { if cleanMount(e.ContainerPath) == cleanMount(c.ContainerPath) {
found = true found = true
break break
} }
@ -80,14 +95,14 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
// mounts overridden by supplied mount; // mounts overridden by supplied mount;
mountSet := make(map[string]struct{}) mountSet := make(map[string]struct{})
for _, m := range mounts { for _, m := range mounts {
mountSet[filepath.Clean(m.ContainerPath)] = struct{}{} mountSet[cleanMount(m.ContainerPath)] = struct{}{}
} }
defaultMounts := s.Mounts defaultMounts := s.Mounts
s.Mounts = nil s.Mounts = nil
for _, m := range defaultMounts { for _, m := range defaultMounts {
dst := filepath.Clean(m.Destination) dst := cleanMount(m.Destination)
if _, ok := mountSet[dst]; ok { if _, ok := mountSet[dst]; ok {
// filter out mount overridden by a supplied mount // filter out mount overridden by a supplied mount
continue continue
@ -100,18 +115,26 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
dst = mount.GetContainerPath() dst = mount.GetContainerPath()
src = mount.GetHostPath() src = mount.GetHostPath()
) )
// TODO(windows): Support special mount sources, e.g. named pipe. // In the case of a named pipe mount on Windows, don't stat the file
// Create the host path if it doesn't exist. // or do other operations that open it, as that could interfere with
// the listening process. filepath.Clean also breaks named pipe
// paths, so don't use it.
if !namedPipePath(src) {
if _, err := osi.Stat(src); err != nil { if _, err := osi.Stat(src); err != nil {
// If the source doesn't exist, return an error instead // If the source doesn't exist, return an error instead
// of creating the source. This aligns with Docker's // of creating the source. This aligns with Docker's
// behavior on windows. // behavior on windows.
return errors.Wrapf(err, "failed to stat %q", src) return errors.Wrapf(err, "failed to stat %q", src)
} }
src, err := osi.ResolveSymbolicLink(src) var err error
src, err = osi.ResolveSymbolicLink(src)
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to resolve symlink %q", src) return errors.Wrapf(err, "failed to resolve symlink %q", src)
} }
// hcsshim requires clean path, especially '/' -> '\'.
src = filepath.Clean(src)
dst = filepath.Clean(dst)
}
var options []string var options []string
// NOTE(random-liu): we don't change all mounts to `ro` when root filesystem // NOTE(random-liu): we don't change all mounts to `ro` when root filesystem
@ -122,9 +145,8 @@ func WithWindowsMounts(osi osinterface.OS, config *runtime.ContainerConfig, extr
options = append(options, "rw") options = append(options, "rw")
} }
s.Mounts = append(s.Mounts, runtimespec.Mount{ s.Mounts = append(s.Mounts, runtimespec.Mount{
// hcsshim requires clean path, especially '/' -> '\'. Source: src,
Source: filepath.Clean(src), Destination: dst,
Destination: filepath.Clean(dst),
Options: options, Options: options,
}) })
} }

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2015 The Kubernetes Authors. Copyright 2015 The Kubernetes Authors.
@ -15,4 +31,4 @@ limitations under the License.
*/ */
// Package bandwidth provides utilities for bandwidth shaping // Package bandwidth provides utilities for bandwidth shaping
package bandwidth // import "k8s.io/kubernetes/pkg/util/bandwidth" package bandwidth

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2015 The Kubernetes Authors. Copyright 2015 The Kubernetes Authors.

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2015 The Kubernetes Authors. Copyright 2015 The Kubernetes Authors.

View File

@ -1,5 +1,21 @@
// +build linux // +build linux
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2015 The Kubernetes Authors. Copyright 2015 The Kubernetes Authors.
@ -31,7 +47,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/exec" "k8s.io/utils/exec"
"k8s.io/klog" "k8s.io/klog/v2"
) )
var ( var (

View File

@ -1,5 +1,21 @@
// +build !linux // +build !linux
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2015 The Kubernetes Authors. Copyright 2015 The Kubernetes Authors.

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2015 The Kubernetes Authors. Copyright 2015 The Kubernetes Authors.

View File

@ -225,7 +225,7 @@ func (c *criService) containerSpec(id string, sandboxID string, sandboxPid uint3
if c.config.DisableCgroup { if c.config.DisableCgroup {
specOpts = append(specOpts, customopts.WithDisabledCgroups) specOpts = append(specOpts, customopts.WithDisabledCgroups)
} else { } else {
specOpts = append(specOpts, customopts.WithResources(config.GetLinux().GetResources(), c.config.TolerateMissingHugePagesCgroupController)) specOpts = append(specOpts, customopts.WithResources(config.GetLinux().GetResources(), c.config.TolerateMissingHugetlbController))
if sandboxConfig.GetLinux().GetCgroupParent() != "" { if sandboxConfig.GetLinux().GetCgroupParent() != "" {
cgroupsPath := getCgroupsPath(sandboxConfig.GetLinux().GetCgroupParent(), id) cgroupsPath := getCgroupsPath(sandboxConfig.GetLinux().GetCgroupParent(), id)
specOpts = append(specOpts, oci.WithCgroup(cgroupsPath)) specOpts = append(specOpts, oci.WithCgroup(cgroupsPath))

View File

@ -73,7 +73,7 @@ func (c *criService) updateContainerResources(ctx context.Context,
return errors.Wrap(err, "failed to get container spec") return errors.Wrap(err, "failed to get container spec")
} }
newSpec, err := updateOCILinuxResource(ctx, oldSpec, resources, newSpec, err := updateOCILinuxResource(ctx, oldSpec, resources,
c.config.TolerateMissingHugePagesCgroupController) c.config.TolerateMissingHugetlbController)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to update resource in spec") return errors.Wrap(err, "failed to update resource in spec")
} }
@ -134,7 +134,7 @@ func updateContainerSpec(ctx context.Context, cntr containerd.Container, spec *r
// updateOCILinuxResource updates container resource limit. // updateOCILinuxResource updates container resource limit.
func updateOCILinuxResource(ctx context.Context, spec *runtimespec.Spec, new *runtime.LinuxContainerResources, func updateOCILinuxResource(ctx context.Context, spec *runtimespec.Spec, new *runtime.LinuxContainerResources,
tolerateMissingHugePagesCgroupController bool) (*runtimespec.Spec, error) { tolerateMissingHugetlbController bool) (*runtimespec.Spec, error) {
// Copy to make sure old spec is not changed. // Copy to make sure old spec is not changed.
var cloned runtimespec.Spec var cloned runtimespec.Spec
if err := util.DeepCopy(&cloned, spec); err != nil { if err := util.DeepCopy(&cloned, spec); err != nil {
@ -143,7 +143,7 @@ func updateOCILinuxResource(ctx context.Context, spec *runtimespec.Spec, new *ru
if cloned.Linux == nil { if cloned.Linux == nil {
cloned.Linux = &runtimespec.Linux{} cloned.Linux = &runtimespec.Linux{}
} }
if err := opts.WithResources(new, tolerateMissingHugePagesCgroupController)(ctx, nil, nil, &cloned); err != nil { if err := opts.WithResources(new, tolerateMissingHugetlbController)(ctx, nil, nil, &cloned); err != nil {
return nil, errors.Wrap(err, "unable to set linux container resources") return nil, errors.Wrap(err, "unable to set linux container resources")
} }
return &cloned, nil return &cloned, nil

View File

@ -284,7 +284,7 @@ func (c *criService) getTLSConfig(registryTLSConfig criconfig.TLSConfig) (*tls.C
if len(cert.Certificate) != 0 { if len(cert.Certificate) != 0 {
tlsConfig.Certificates = []tls.Certificate{cert} tlsConfig.Certificates = []tls.Certificate{cert}
} }
tlsConfig.BuildNameToCertificate() tlsConfig.BuildNameToCertificate() // nolint:staticcheck
} }
if registryTLSConfig.CAFile != "" { if registryTLSConfig.CAFile != "" {

View File

@ -33,13 +33,13 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/net/context" "golang.org/x/net/context"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/kubernetes/pkg/util/bandwidth"
"github.com/containerd/cri/pkg/annotations" "github.com/containerd/cri/pkg/annotations"
criconfig "github.com/containerd/cri/pkg/config" criconfig "github.com/containerd/cri/pkg/config"
customopts "github.com/containerd/cri/pkg/containerd/opts" customopts "github.com/containerd/cri/pkg/containerd/opts"
ctrdutil "github.com/containerd/cri/pkg/containerd/util" ctrdutil "github.com/containerd/cri/pkg/containerd/util"
"github.com/containerd/cri/pkg/netns" "github.com/containerd/cri/pkg/netns"
"github.com/containerd/cri/pkg/server/bandwidth"
sandboxstore "github.com/containerd/cri/pkg/store/sandbox" sandboxstore "github.com/containerd/cri/pkg/store/sandbox"
"github.com/containerd/cri/pkg/util" "github.com/containerd/cri/pkg/util"
selinux "github.com/opencontainers/selinux/go-selinux" selinux "github.com/opencontainers/selinux/go-selinux"

View File

@ -28,12 +28,12 @@ import (
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/oci" "github.com/containerd/containerd/oci"
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/cri/pkg/streaming"
cni "github.com/containerd/go-cni" cni "github.com/containerd/go-cni"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"google.golang.org/grpc" "google.golang.org/grpc"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/kubernetes/pkg/kubelet/server/streaming"
"github.com/containerd/cri/pkg/store/label" "github.com/containerd/cri/pkg/store/label"

View File

@ -30,10 +30,10 @@ import (
"k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/tools/remotecommand"
k8scert "k8s.io/client-go/util/cert" k8scert "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/pkg/kubelet/server/streaming"
"k8s.io/utils/exec" "k8s.io/utils/exec"
ctrdutil "github.com/containerd/cri/pkg/containerd/util" ctrdutil "github.com/containerd/cri/pkg/containerd/util"
"github.com/containerd/cri/pkg/streaming"
) )
type streamListenerMode int type streamListenerMode int

View File

@ -17,8 +17,11 @@
package sandbox package sandbox
import ( import (
"strconv"
"sync" "sync"
"time" "time"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
) )
// The sandbox state machine in the CRI plugin: // The sandbox state machine in the CRI plugin:
@ -63,7 +66,7 @@ type State uint32
const ( const (
// StateReady is ready state, it means sandbox container // StateReady is ready state, it means sandbox container
// is running. // is running.
StateReady = iota StateReady State = iota
// StateNotReady is notready state, it ONLY means sandbox // StateNotReady is notready state, it ONLY means sandbox
// container is not running. // container is not running.
// StopPodSandbox should still be called for NOTREADY sandbox to // StopPodSandbox should still be called for NOTREADY sandbox to
@ -75,6 +78,21 @@ const (
StateUnknown StateUnknown
) )
// String returns the string representation of the state
func (s State) String() string {
switch s {
case StateReady:
return runtime.PodSandboxState_SANDBOX_READY.String()
case StateNotReady:
return runtime.PodSandboxState_SANDBOX_NOTREADY.String()
case StateUnknown:
// PodSandboxState doesn't have an unknown state, but State does, so return a string using the same convention
return "SANDBOX_UNKNOWN"
default:
return "invalid sandbox state value: " + strconv.Itoa(int(s))
}
}
// Status is the status of a sandbox. // Status is the status of a sandbox.
type Status struct { type Status struct {
// Pid is the init process id of the sandbox container. // Pid is the init process id of the sandbox container.

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2016 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2015 The Kubernetes Authors. Copyright 2015 The Kubernetes Authors.

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2016 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.
@ -24,13 +40,13 @@ import (
"sync" "sync"
"time" "time"
api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/httpstream"
"k8s.io/apimachinery/pkg/util/httpstream/spdy" "k8s.io/apimachinery/pkg/util/httpstream/spdy"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/klog" "k8s.io/klog/v2"
) )
func handleHTTPStreams(req *http.Request, w http.ResponseWriter, portForwarder PortForwarder, podName string, uid types.UID, supportedPortForwardProtocols []string, idleTimeout, streamCreationTimeout time.Duration) error { func handleHTTPStreams(req *http.Request, w http.ResponseWriter, portForwarder PortForwarder, podName string, uid types.UID, supportedPortForwardProtocols []string, idleTimeout, streamCreationTimeout time.Duration) error {
@ -148,16 +164,6 @@ func (h *httpStreamHandler) monitorStreamPair(p *httpStreamPair, timeout <-chan
h.removeStreamPair(p.requestID) h.removeStreamPair(p.requestID)
} }
// hasStreamPair returns a bool indicating if a stream pair for requestID
// exists.
func (h *httpStreamHandler) hasStreamPair(requestID string) bool {
h.streamPairsLock.RLock()
defer h.streamPairsLock.RUnlock()
_, ok := h.streamPairs[requestID]
return ok
}
// removeStreamPair removes the stream pair identified by requestID from streamPairs. // removeStreamPair removes the stream pair identified by requestID from streamPairs.
func (h *httpStreamHandler) removeStreamPair(requestID string) { func (h *httpStreamHandler) removeStreamPair(requestID string) {
h.streamPairsLock.Lock() h.streamPairsLock.Lock()

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2016 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2016 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.
@ -26,13 +42,13 @@ import (
"sync" "sync"
"time" "time"
"k8s.io/klog" "k8s.io/klog/v2"
api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/server/httplog" "k8s.io/apiserver/pkg/server/httplog"
"k8s.io/apiserver/pkg/util/wsstream" "k8s.io/apiserver/pkg/util/wsstream"
api "k8s.io/kubernetes/pkg/apis/core"
) )
const ( const (

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2016 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2016 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.
@ -15,4 +31,4 @@ limitations under the License.
*/ */
// Package remotecommand contains functions related to executing commands in and attaching to pods. // Package remotecommand contains functions related to executing commands in and attaching to pods.
package remotecommand // import "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" package remotecommand

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2016 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2016 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.
@ -24,6 +40,7 @@ import (
"net/http" "net/http"
"time" "time"
api "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/httpstream"
@ -32,9 +49,8 @@ import (
"k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/util/wsstream" "k8s.io/apiserver/pkg/util/wsstream"
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/tools/remotecommand"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/klog" "k8s.io/klog/v2"
) )
// Options contains details about which streams are required for // Options contains details about which streams are required for
@ -411,6 +427,7 @@ func (*v1ProtocolHandler) supportsTerminalResizing() bool { return false }
func handleResizeEvents(stream io.Reader, channel chan<- remotecommand.TerminalSize) { func handleResizeEvents(stream io.Reader, channel chan<- remotecommand.TerminalSize) {
defer runtime.HandleCrash() defer runtime.HandleCrash()
defer close(channel)
decoder := json.NewDecoder(stream) decoder := json.NewDecoder(stream)
for { for {

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2016 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2016 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.

View File

@ -1,3 +1,19 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* /*
Copyright 2016 The Kubernetes Authors. Copyright 2016 The Kubernetes Authors.
@ -35,8 +51,9 @@ import (
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand" remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/tools/remotecommand"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/kubernetes/pkg/kubelet/server/portforward"
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" "github.com/containerd/cri/pkg/streaming/portforward"
remotecommandserver "github.com/containerd/cri/pkg/streaming/remotecommand"
) )
// Server is the library interface to serve the stream requests. // Server is the library interface to serve the stream requests.

View File

@ -1,13 +1,13 @@
# cri dependencies # cri dependencies
github.com/docker/docker 4634ce647cf2ce2c6031129ccd109e557244986f github.com/docker/docker 4634ce647cf2ce2c6031129ccd109e557244986f
github.com/opencontainers/selinux bb88c45a3863dc4c38320d71b890bb30ef9feba4 github.com/opencontainers/selinux v1.5.1
github.com/tchap/go-patricia v2.2.6 github.com/tchap/go-patricia v2.2.6
# containerd dependencies # containerd dependencies
github.com/beorn7/perks v1.0.1 github.com/beorn7/perks v1.0.1
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.3.1
github.com/cespare/xxhash/v2 v2.1.1 github.com/cespare/xxhash/v2 v2.1.1
github.com/containerd/cgroups b4448137398923af7f4918b8b2ad8249172ca7a6 github.com/containerd/cgroups e9676da73eddf8ed2433f77aaf3b9cf8f0f75b8c
github.com/containerd/console v1.0.0 github.com/containerd/console v1.0.0
github.com/containerd/containerd v1.4.0-beta.0 github.com/containerd/containerd v1.4.0-beta.0
github.com/containerd/continuity d3ef23f19fbb106bb73ffde425d07a9187e30745 github.com/containerd/continuity d3ef23f19fbb106bb73ffde425d07a9187e30745
@ -16,14 +16,14 @@ github.com/containerd/go-runc 7016d3ce2328dd2cb1192b2076eb
github.com/containerd/ttrpc v1.0.1 github.com/containerd/ttrpc v1.0.1
github.com/containerd/typeurl v1.0.1 github.com/containerd/typeurl v1.0.1
github.com/coreos/go-systemd/v22 v22.0.0 github.com/coreos/go-systemd/v22 v22.0.0
github.com/cpuguy83/go-md2man v1.0.10 github.com/cpuguy83/go-md2man/v2 v2.0.0
github.com/docker/go-events e31b211e4f1cd09aa76fe4ac244571fab96ae47f github.com/docker/go-events e31b211e4f1cd09aa76fe4ac244571fab96ae47f
github.com/docker/go-metrics v0.0.1 github.com/docker/go-metrics v0.0.1
github.com/docker/go-units v0.4.0 github.com/docker/go-units v0.4.0
github.com/godbus/dbus/v5 v5.0.3 github.com/godbus/dbus/v5 v5.0.3
github.com/gogo/googleapis v1.3.2 github.com/gogo/googleapis v1.3.2
github.com/gogo/protobuf v1.3.1 github.com/gogo/protobuf v1.3.1
github.com/golang/protobuf v1.3.3 github.com/golang/protobuf v1.3.5
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/hashicorp/errwrap v1.0.0 github.com/hashicorp/errwrap v1.0.0
@ -36,53 +36,55 @@ github.com/Microsoft/go-winio v0.4.14
github.com/Microsoft/hcsshim v0.8.9 github.com/Microsoft/hcsshim v0.8.9
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.1 github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/runc v1.0.0-rc10 github.com/opencontainers/runc v1.0.0-rc91
github.com/opencontainers/runtime-spec v1.0.2 github.com/opencontainers/runtime-spec 237cc4f519e2e8f9b235bacccfa8ef5a84df2875 # v1.0.2-14-g8e2f17c
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.3.0 github.com/prometheus/client_golang v1.6.0
github.com/prometheus/client_model v0.1.0 github.com/prometheus/client_model v0.2.0
github.com/prometheus/common v0.7.0 github.com/prometheus/common v0.9.1
github.com/prometheus/procfs v0.0.8 github.com/prometheus/procfs v0.0.11
github.com/russross/blackfriday v1.5.2 github.com/russross/blackfriday/v2 v2.0.1
github.com/shurcooL/sanitized_anchor_name v1.0.0
github.com/sirupsen/logrus v1.6.0 github.com/sirupsen/logrus v1.6.0
github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2 github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2
github.com/urfave/cli v1.22.0 github.com/urfave/cli v1.22.1 # NOTE: urfave/cli must be <= v1.22.1 due to a regression: https://github.com/urfave/cli/issues/1092
go.etcd.io/bbolt v1.3.3 go.etcd.io/bbolt v1.3.3
go.opencensus.io v0.22.0 go.opencensus.io v0.22.0
golang.org/x/net f3200d17e092c607f615320ecaad13d87ad9a2b3 golang.org/x/net f3200d17e092c607f615320ecaad13d87ad9a2b3
golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e
golang.org/x/sys 5c8b2ff67527cb88b770f693cebf3799036d8bc0 golang.org/x/sys 9dae0f8f577553e0f21298e18926efc9644c281d
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4 golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
google.golang.org/genproto e50cd9704f63023d62cd06a1994b98227fc4d21a google.golang.org/genproto e50cd9704f63023d62cd06a1994b98227fc4d21a
google.golang.org/grpc v1.27.1 google.golang.org/grpc v1.27.1
# cgroups dependencies # cgroups dependencies
github.com/cilium/ebpf 4032b1d8aae306b7bb94a2a11002932caf88c644 github.com/cilium/ebpf 1c8d4c9ef7759622653a1d319284a44652333b28
# kubernetes dependencies # kubernetes dependencies
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528 github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528
github.com/emicklei/go-restful v2.9.5 github.com/emicklei/go-restful v2.9.5
github.com/go-logr/logr v0.2.0
github.com/google/gofuzz v1.1.0 github.com/google/gofuzz v1.1.0
github.com/json-iterator/go v1.1.8 github.com/json-iterator/go v1.1.9
github.com/modern-go/concurrent 1.0.3 github.com/modern-go/concurrent 1.0.3
github.com/modern-go/reflect2 v1.0.1 github.com/modern-go/reflect2 v1.0.1
github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib v1.0.0
github.com/seccomp/libseccomp-golang v0.9.1 github.com/seccomp/libseccomp-golang v0.9.1
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
golang.org/x/crypto bac4c82f69751a6dd76e702d54b3ceb88adab236 golang.org/x/crypto bac4c82f69751a6dd76e702d54b3ceb88adab236
golang.org/x/oauth2 0f29369cfe4552d0e4bcddc57cc75f4d7e672a33 golang.org/x/oauth2 858c2ad4c8b6c5d10852cb89079f6ca1c7309787
golang.org/x/time 9d24e82272b4f38b78bc8cff74fa936d31ccd8ef golang.org/x/time 555d28b269f0569763d25dbe1a237ae74c6bcc82
gopkg.in/inf.v0 v0.9.1 gopkg.in/inf.v0 v0.9.1
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8
k8s.io/api v0.18.2 k8s.io/api v0.19.0-beta.2
k8s.io/apimachinery v0.18.2 k8s.io/apiserver v0.19.0-beta.2
k8s.io/apiserver v0.18.2 k8s.io/apimachinery v0.19.0-beta.2
k8s.io/client-go v0.18.2 k8s.io/client-go v0.19.0-beta.2
k8s.io/cri-api v0.18.2 k8s.io/component-base v0.19.0-beta.2
k8s.io/klog v1.0.0 k8s.io/cri-api v0.19.0-beta.2
k8s.io/kubernetes v1.18.2 k8s.io/klog/v2 v2.2.0
k8s.io/utils a9aa75ae1b89e1b992c33383f48e942d97e52dae k8s.io/utils 2df71ebbae66f39338aed4cd0bb82d2212ee33cc
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 sigs.k8s.io/structured-merge-diff/v3 v3.0.0
sigs.k8s.io/yaml v1.2.0 sigs.k8s.io/yaml v1.2.0
@ -90,7 +92,7 @@ sigs.k8s.io/yaml v1.2.0
github.com/containerd/go-cni v1.0.0 github.com/containerd/go-cni v1.0.0
github.com/containernetworking/cni v0.7.1 github.com/containernetworking/cni v0.7.1
github.com/containernetworking/plugins v0.7.6 github.com/containernetworking/plugins v0.7.6
github.com/fsnotify/fsnotify v1.4.8 github.com/fsnotify/fsnotify v1.4.9
# image decrypt depedencies # image decrypt depedencies
github.com/containerd/imgcrypt v1.0.1 github.com/containerd/imgcrypt v1.0.1

View File

@ -33,6 +33,53 @@ All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based o
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`. Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
## Usage
```go
package main
import (
"log"
"github.com/fsnotify/fsnotify"
)
func main() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
log.Println("event:", event)
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("modified file:", event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
err = watcher.Add("/tmp/foo")
if err != nil {
log.Fatal(err)
}
<-done
}
```
## Contributing ## Contributing
Please refer to [CONTRIBUTING][] before opening an issue or pull request. Please refer to [CONTRIBUTING][] before opening an issue or pull request.

View File

@ -1,4 +1,3 @@
Apache License Apache License
Version 2.0, January 2004 Version 2.0, January 2004
http://www.apache.org/licenses/ http://www.apache.org/licenses/
@ -179,7 +178,7 @@
APPENDIX: How to apply the Apache License to your work. APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]" boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a comment syntax for the file format. We also recommend that a
@ -187,7 +186,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright [yyyy] [name of copyright owner] Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

181
vendor/github.com/go-logr/logr/README.md generated vendored Normal file
View File

@ -0,0 +1,181 @@
# A more minimal logging API for Go
Before you consider this package, please read [this blog post by the
inimitable Dave Cheney][warning-makes-no-sense]. I really appreciate what
he has to say, and it largely aligns with my own experiences. Too many
choices of levels means inconsistent logs.
This package offers a purely abstract interface, based on these ideas but with
a few twists. Code can depend on just this interface and have the actual
logging implementation be injected from callers. Ideally only `main()` knows
what logging implementation is being used.
# Differences from Dave's ideas
The main differences are:
1) Dave basically proposes doing away with the notion of a logging API in favor
of `fmt.Printf()`. I disagree, especially when you consider things like output
locations, timestamps, file and line decorations, and structured logging. I
restrict the API to just 2 types of logs: info and error.
Info logs are things you want to tell the user which are not errors. Error
logs are, well, errors. If your code receives an `error` from a subordinate
function call and is logging that `error` *and not returning it*, use error
logs.
2) Verbosity-levels on info logs. This gives developers a chance to indicate
arbitrary grades of importance for info logs, without assigning names with
semantic meaning such as "warning", "trace", and "debug". Superficially this
may feel very similar, but the primary difference is the lack of semantics.
Because verbosity is a numerical value, it's safe to assume that an app running
with higher verbosity means more (and less important) logs will be generated.
This is a BETA grade API.
There are implementations for the following logging libraries:
- **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr)
- **k8s.io/klog**: [klogr](https://git.k8s.io/klog/klogr)
- **go.uber.org/zap**: [zapr](https://github.com/go-logr/zapr)
- **log** (the Go standard library logger):
[stdr](https://github.com/go-logr/stdr)
- **github.com/sirupsen/logrus**: [logrusr](https://github.com/bombsimon/logrusr)
# FAQ
## Conceptual
## Why structured logging?
- **Structured logs are more easily queriable**: Since you've got
key-value pairs, it's much easier to query your structured logs for
particular values by filtering on the contents of a particular key --
think searching request logs for error codes, Kubernetes reconcilers for
the name and namespace of the reconciled object, etc
- **Structured logging makes it easier to have cross-referencable logs**:
Similarly to searchability, if you maintain conventions around your
keys, it becomes easy to gather all log lines related to a particular
concept.
- **Structured logs allow better dimensions of filtering**: if you have
structure to your logs, you've got more precise control over how much
information is logged -- you might choose in a particular configuration
to log certain keys but not others, only log lines where a certain key
matches a certain value, etc, instead of just having v-levels and names
to key off of.
- **Structured logs better represent structured data**: sometimes, the
data that you want to log is inherently structured (think tuple-link
objects). Structured logs allow you to preserve that structure when
outputting.
## Why V-levels?
**V-levels give operators an easy way to control the chattiness of log
operations**. V-levels provide a way for a given package to distinguish
the relative importance or verbosity of a given log message. Then, if
a particular logger or package is logging too many messages, the user
of the package can simply change the v-levels for that library.
## Why not more named levels, like Warning?
Read [Dave Cheney's post][warning-makes-no-sense]. Then read [Differences
from Dave's ideas](#differences-from-daves-ideas).
## Why not allow format strings, too?
**Format strings negate many of the benefits of structured logs**:
- They're not easily searchable without resorting to fuzzy searching,
regular expressions, etc
- They don't store structured data well, since contents are flattened into
a string
- They're not cross-referencable
- They don't compress easily, since the message is not constant
(unless you turn positional parameters into key-value pairs with numerical
keys, at which point you've gotten key-value logging with meaningless
keys)
## Practical
## Why key-value pairs, and not a map?
Key-value pairs are *much* easier to optimize, especially around
allocations. Zap (a structured logger that inspired logr's interface) has
[performance measurements](https://github.com/uber-go/zap#performance)
that show this quite nicely.
While the interface ends up being a little less obvious, you get
potentially better performance, plus avoid making users type
`map[string]string{}` every time they want to log.
## What if my V-levels differ between libraries?
That's fine. Control your V-levels on a per-logger basis, and use the
`WithName` function to pass different loggers to different libraries.
Generally, you should take care to ensure that you have relatively
consistent V-levels within a given logger, however, as this makes deciding
on what verbosity of logs to request easier.
## But I *really* want to use a format string!
That's not actually a question. Assuming your question is "how do
I convert my mental model of logging with format strings to logging with
constant messages":
1. figure out what the error actually is, as you'd write in a TL;DR style,
and use that as a message
2. For every place you'd write a format specifier, look to the word before
it, and add that as a key value pair
For instance, consider the following examples (all taken from spots in the
Kubernetes codebase):
- `klog.V(4).Infof("Client is returning errors: code %v, error %v",
responseCode, err)` becomes `logger.Error(err, "client returned an
error", "code", responseCode)`
- `klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v",
seconds, retries, url)` becomes `logger.V(4).Info("got a retry-after
response when requesting url", "attempt", retries, "after
seconds", seconds, "url", url)`
If you *really* must use a format string, place it as a key value, and
call `fmt.Sprintf` yourself -- for instance, `log.Printf("unable to
reflect over type %T")` becomes `logger.Info("unable to reflect over
type", "type", fmt.Sprintf("%T"))`. In general though, the cases where
this is necessary should be few and far between.
## How do I choose my V-levels?
This is basically the only hard constraint: increase V-levels to denote
more verbose or more debug-y logs.
Otherwise, you can start out with `0` as "you always want to see this",
`1` as "common logging that you might *possibly* want to turn off", and
`10` as "I would like to performance-test your log collection stack".
Then gradually choose levels in between as you need them, working your way
down from 10 (for debug and trace style logs) and up from 1 (for chattier
info-type logs).
## How do I choose my keys
- make your keys human-readable
- constant keys are generally a good idea
- be consistent across your codebase
- keys should naturally match parts of the message string
While key names are mostly unrestricted (and spaces are acceptable),
it's generally a good idea to stick to printable ascii characters, or at
least match the general character set of your log lines.
[warning-makes-no-sense]: http://dave.cheney.net/2015/11/05/lets-talk-about-logging

3
vendor/github.com/go-logr/logr/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/go-logr/logr
go 1.14

178
vendor/github.com/go-logr/logr/logr.go generated vendored Normal file
View File

@ -0,0 +1,178 @@
/*
Copyright 2019 The logr Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package logr defines abstract interfaces for logging. Packages can depend on
// these interfaces and callers can implement logging in whatever way is
// appropriate.
//
// This design derives from Dave Cheney's blog:
// http://dave.cheney.net/2015/11/05/lets-talk-about-logging
//
// This is a BETA grade API. Until there is a significant 2nd implementation,
// I don't really know how it will change.
//
// The logging specifically makes it non-trivial to use format strings, to encourage
// attaching structured information instead of unstructured format strings.
//
// Usage
//
// Logging is done using a Logger. Loggers can have name prefixes and named
// values attached, so that all log messages logged with that Logger have some
// base context associated.
//
// The term "key" is used to refer to the name associated with a particular
// value, to disambiguate it from the general Logger name.
//
// For instance, suppose we're trying to reconcile the state of an object, and
// we want to log that we've made some decision.
//
// With the traditional log package, we might write:
// log.Printf(
// "decided to set field foo to value %q for object %s/%s",
// targetValue, object.Namespace, object.Name)
//
// With logr's structured logging, we'd write:
// // elsewhere in the file, set up the logger to log with the prefix of "reconcilers",
// // and the named value target-type=Foo, for extra context.
// log := mainLogger.WithName("reconcilers").WithValues("target-type", "Foo")
//
// // later on...
// log.Info("setting field foo on object", "value", targetValue, "object", object)
//
// Depending on our logging implementation, we could then make logging decisions
// based on field values (like only logging such events for objects in a certain
// namespace), or copy the structured information into a structured log store.
//
// For logging errors, Logger has a method called Error. Suppose we wanted to
// log an error while reconciling. With the traditional log package, we might
// write:
// log.Errorf("unable to reconcile object %s/%s: %v", object.Namespace, object.Name, err)
//
// With logr, we'd instead write:
// // assuming the above setup for log
// log.Error(err, "unable to reconcile object", "object", object)
//
// This functions similarly to:
// log.Info("unable to reconcile object", "error", err, "object", object)
//
// However, it ensures that a standard key for the error value ("error") is used
// across all error logging. Furthermore, certain implementations may choose to
// attach additional information (such as stack traces) on calls to Error, so
// it's preferred to use Error to log errors.
//
// Parts of a log line
//
// Each log message from a Logger has four types of context:
// logger name, log verbosity, log message, and the named values.
//
// The Logger name constists of a series of name "segments" added by successive
// calls to WithName. These name segments will be joined in some way by the
// underlying implementation. It is strongly reccomended that name segements
// contain simple identifiers (letters, digits, and hyphen), and do not contain
// characters that could muddle the log output or confuse the joining operation
// (e.g. whitespace, commas, periods, slashes, brackets, quotes, etc).
//
// Log verbosity represents how little a log matters. Level zero, the default,
// matters most. Increasing levels matter less and less. Try to avoid lots of
// different verbosity levels, and instead provide useful keys, logger names,
// and log messages for users to filter on. It's illegal to pass a log level
// below zero.
//
// The log message consists of a constant message attached to the the log line.
// This should generally be a simple description of what's occuring, and should
// never be a format string.
//
// Variable information can then be attached using named values (key/value
// pairs). Keys are arbitrary strings, while values may be any Go value.
//
// Key Naming Conventions
//
// Keys are not strictly required to conform to any specification or regex, but
// it is recommended that they:
// * be human-readable and meaningful (not auto-generated or simple ordinals)
// * be constant (not dependent on input data)
// * contain only printable characters
// * not contain whitespace or punctuation
//
// These guidelines help ensure that log data is processed properly regardless
// of the log implementation. For example, log implementations will try to
// output JSON data or will store data for later database (e.g. SQL) queries.
//
// While users are generally free to use key names of their choice, it's
// generally best to avoid using the following keys, as they're frequently used
// by implementations:
//
// - `"caller"`: the calling information (file/line) of a particular log line.
// - `"error"`: the underlying error value in the `Error` method.
// - `"level"`: the log level.
// - `"logger"`: the name of the associated logger.
// - `"msg"`: the log message.
// - `"stacktrace"`: the stack trace associated with a particular log line or
// error (often from the `Error` message).
// - `"ts"`: the timestamp for a log line.
//
// Implementations are encouraged to make use of these keys to represent the
// above concepts, when neccessary (for example, in a pure-JSON output form, it
// would be necessary to represent at least message and timestamp as ordinary
// named values).
package logr
// TODO: consider adding back in format strings if they're really needed
// TODO: consider other bits of zap/zapcore functionality like ObjectMarshaller (for arbitrary objects)
// TODO: consider other bits of glog functionality like Flush, InfoDepth, OutputStats
// Logger represents the ability to log messages, both errors and not.
type Logger interface {
// Enabled tests whether this Logger is enabled. For example, commandline
// flags might be used to set the logging verbosity and disable some info
// logs.
Enabled() bool
// Info logs a non-error message with the given key/value pairs as context.
//
// The msg argument should be used to add some constant description to
// the log line. The key/value pairs can then be used to add additional
// variable information. The key/value pairs should alternate string
// keys and arbitrary values.
Info(msg string, keysAndValues ...interface{})
// Error logs an error, with the given message and key/value pairs as context.
// It functions similarly to calling Info with the "error" named value, but may
// have unique behavior, and should be preferred for logging errors (see the
// package documentations for more information).
//
// The msg field should be used to add context to any underlying error,
// while the err field should be used to attach the actual error that
// triggered this log line, if present.
Error(err error, msg string, keysAndValues ...interface{})
// V returns an Logger value for a specific verbosity level, relative to
// this Logger. In other words, V values are additive. V higher verbosity
// level means a log message is less important. It's illegal to pass a log
// level less than zero.
V(level int) Logger
// WithValues adds some key-value pairs of context to a logger.
// See Info for documentation on how key/value pairs work.
WithValues(keysAndValues ...interface{}) Logger
// WithName adds a new element to the logger's name.
// Successive calls with WithName continue to append
// suffixes to the logger's name. It's strongly reccomended
// that name segments contain only letters, digits, and hyphens
// (see the package documentation for more information).
WithName(name string) Logger
}

View File

@ -1,3 +1,3 @@
module github.com/golang/protobuf module github.com/golang/protobuf
go 1.12 go 1.9

View File

@ -102,7 +102,8 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// //
type Any struct { type Any struct {
// A URL/resource name that uniquely identifies the type of the serialized // A URL/resource name that uniquely identifies the type of the serialized
// protocol buffer message. The last segment of the URL's path must represent // protocol buffer message. This string must contain at least
// one "/" character. The last segment of the URL's path must represent
// the fully qualified name of the type (as in // the fully qualified name of the type (as in
// `path/google.protobuf.Duration`). The name should be in a canonical form // `path/google.protobuf.Duration`). The name should be in a canonical form
// (e.g., leading "." is not accepted). // (e.g., leading "." is not accepted).
@ -181,7 +182,9 @@ func init() {
proto.RegisterType((*Any)(nil), "google.protobuf.Any") proto.RegisterType((*Any)(nil), "google.protobuf.Any")
} }
func init() { proto.RegisterFile("google/protobuf/any.proto", fileDescriptor_b53526c13ae22eb4) } func init() {
proto.RegisterFile("google/protobuf/any.proto", fileDescriptor_b53526c13ae22eb4)
}
var fileDescriptor_b53526c13ae22eb4 = []byte{ var fileDescriptor_b53526c13ae22eb4 = []byte{
// 185 bytes of a gzipped FileDescriptorProto // 185 bytes of a gzipped FileDescriptorProto

View File

@ -121,7 +121,8 @@ option objc_class_prefix = "GPB";
// //
message Any { message Any {
// A URL/resource name that uniquely identifies the type of the serialized // A URL/resource name that uniquely identifies the type of the serialized
// protocol buffer message. The last segment of the URL's path must represent // protocol buffer message. This string must contain at least
// one "/" character. The last segment of the URL's path must represent
// the fully qualified name of the type (as in // the fully qualified name of the type (as in
// `path/google.protobuf.Duration`). The name should be in a canonical form // `path/google.protobuf.Duration`). The name should be in a canonical form
// (e.g., leading "." is not accepted). // (e.g., leading "." is not accepted).

View File

@ -41,7 +41,7 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// if (duration.seconds < 0 && duration.nanos > 0) { // if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1; // duration.seconds += 1;
// duration.nanos -= 1000000000; // duration.nanos -= 1000000000;
// } else if (durations.seconds > 0 && duration.nanos < 0) { // } else if (duration.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1; // duration.seconds -= 1;
// duration.nanos += 1000000000; // duration.nanos += 1000000000;
// } // }
@ -142,7 +142,9 @@ func init() {
proto.RegisterType((*Duration)(nil), "google.protobuf.Duration") proto.RegisterType((*Duration)(nil), "google.protobuf.Duration")
} }
func init() { proto.RegisterFile("google/protobuf/duration.proto", fileDescriptor_23597b2ebd7ac6c5) } func init() {
proto.RegisterFile("google/protobuf/duration.proto", fileDescriptor_23597b2ebd7ac6c5)
}
var fileDescriptor_23597b2ebd7ac6c5 = []byte{ var fileDescriptor_23597b2ebd7ac6c5 = []byte{
// 190 bytes of a gzipped FileDescriptorProto // 190 bytes of a gzipped FileDescriptorProto

View File

@ -61,7 +61,7 @@ option objc_class_prefix = "GPB";
// if (duration.seconds < 0 && duration.nanos > 0) { // if (duration.seconds < 0 && duration.nanos > 0) {
// duration.seconds += 1; // duration.seconds += 1;
// duration.nanos -= 1000000000; // duration.nanos -= 1000000000;
// } else if (durations.seconds > 0 && duration.nanos < 0) { // } else if (duration.seconds > 0 && duration.nanos < 0) {
// duration.seconds -= 1; // duration.seconds -= 1;
// duration.nanos += 1000000000; // duration.nanos += 1000000000;
// } // }
@ -101,7 +101,6 @@ option objc_class_prefix = "GPB";
// //
// //
message Duration { message Duration {
// Signed seconds of the span of time. Must be from -315,576,000,000 // Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive. Note: these bounds are computed from: // to +315,576,000,000 inclusive. Note: these bounds are computed from:
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years

View File

@ -20,17 +20,19 @@ var _ = math.Inf
// proto package needs to be updated. // proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// A Timestamp represents a point in time independent of any time zone // A Timestamp represents a point in time independent of any time zone or local
// or calendar, represented as seconds and fractions of seconds at // calendar, encoded as a count of seconds and fractions of seconds at
// nanosecond resolution in UTC Epoch time. It is encoded using the // nanosecond resolution. The count is relative to an epoch at UTC midnight on
// Proleptic Gregorian Calendar which extends the Gregorian calendar // January 1, 1970, in the proleptic Gregorian calendar which extends the
// backwards to year one. It is encoded assuming all minutes are 60 // Gregorian calendar backwards to year one.
// seconds long, i.e. leap seconds are "smeared" so that no leap second //
// table is needed for interpretation. Range is from // All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
// 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. // second table is needed for interpretation, using a [24-hour linear
// By restricting to that range, we ensure that we can convert to // smear](https://developers.google.com/time/smear).
// and from RFC 3339 date strings. //
// See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt). // The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
// restricting to that range, we ensure that we can convert to and from [RFC
// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
// //
// # Examples // # Examples
// //
@ -91,12 +93,14 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// 01:30 UTC on January 15, 2017. // 01:30 UTC on January 15, 2017.
// //
// In JavaScript, one can convert a Date object to this format using the // In JavaScript, one can convert a Date object to this format using the
// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString] // standard
// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
// method. In Python, a standard `datetime.datetime` object can be converted // method. In Python, a standard `datetime.datetime` object can be converted
// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) // to this format using
// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one // [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
// can use the Joda Time's [`ISODateTimeFormat.dateTime()`]( // the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime-- // the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
// ) to obtain a formatter capable of generating timestamps in this format. // ) to obtain a formatter capable of generating timestamps in this format.
// //
// //
@ -160,7 +164,9 @@ func init() {
proto.RegisterType((*Timestamp)(nil), "google.protobuf.Timestamp") proto.RegisterType((*Timestamp)(nil), "google.protobuf.Timestamp")
} }
func init() { proto.RegisterFile("google/protobuf/timestamp.proto", fileDescriptor_292007bbfe81227e) } func init() {
proto.RegisterFile("google/protobuf/timestamp.proto", fileDescriptor_292007bbfe81227e)
}
var fileDescriptor_292007bbfe81227e = []byte{ var fileDescriptor_292007bbfe81227e = []byte{
// 191 bytes of a gzipped FileDescriptorProto // 191 bytes of a gzipped FileDescriptorProto

View File

@ -40,17 +40,19 @@ option java_outer_classname = "TimestampProto";
option java_multiple_files = true; option java_multiple_files = true;
option objc_class_prefix = "GPB"; option objc_class_prefix = "GPB";
// A Timestamp represents a point in time independent of any time zone // A Timestamp represents a point in time independent of any time zone or local
// or calendar, represented as seconds and fractions of seconds at // calendar, encoded as a count of seconds and fractions of seconds at
// nanosecond resolution in UTC Epoch time. It is encoded using the // nanosecond resolution. The count is relative to an epoch at UTC midnight on
// Proleptic Gregorian Calendar which extends the Gregorian calendar // January 1, 1970, in the proleptic Gregorian calendar which extends the
// backwards to year one. It is encoded assuming all minutes are 60 // Gregorian calendar backwards to year one.
// seconds long, i.e. leap seconds are "smeared" so that no leap second //
// table is needed for interpretation. Range is from // All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
// 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. // second table is needed for interpretation, using a [24-hour linear
// By restricting to that range, we ensure that we can convert to // smear](https://developers.google.com/time/smear).
// and from RFC 3339 date strings. //
// See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt). // The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
// restricting to that range, we ensure that we can convert to and from [RFC
// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
// //
// # Examples // # Examples
// //
@ -111,17 +113,18 @@ option objc_class_prefix = "GPB";
// 01:30 UTC on January 15, 2017. // 01:30 UTC on January 15, 2017.
// //
// In JavaScript, one can convert a Date object to this format using the // In JavaScript, one can convert a Date object to this format using the
// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString] // standard
// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
// method. In Python, a standard `datetime.datetime` object can be converted // method. In Python, a standard `datetime.datetime` object can be converted
// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) // to this format using
// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one // [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
// can use the Joda Time's [`ISODateTimeFormat.dateTime()`]( // the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime-- // the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
// ) to obtain a formatter capable of generating timestamps in this format. // ) to obtain a formatter capable of generating timestamps in this format.
// //
// //
message Timestamp { message Timestamp {
// Represents seconds of UTC time since Unix epoch // Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive. // 9999-12-31T23:59:59Z inclusive.

View File

@ -341,7 +341,7 @@ func describeStruct(ctx *ctx, typ reflect2.Type) *StructDescriptor {
if ctx.onlyTaggedField && !hastag && !field.Anonymous() { if ctx.onlyTaggedField && !hastag && !field.Anonymous() {
continue continue
} }
if tag == "-" { if tag == "-" || field.Name() == "_" {
continue continue
} }
tagParts := strings.Split(tag, ",") tagParts := strings.Split(tag, ",")

View File

@ -290,16 +290,17 @@ func (encoder *sortKeysMapEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
stream.WriteObjectStart() stream.WriteObjectStart()
mapIter := encoder.mapType.UnsafeIterate(ptr) mapIter := encoder.mapType.UnsafeIterate(ptr)
subStream := stream.cfg.BorrowStream(nil) subStream := stream.cfg.BorrowStream(nil)
subStream.Attachment = stream.Attachment
subIter := stream.cfg.BorrowIterator(nil) subIter := stream.cfg.BorrowIterator(nil)
keyValues := encodedKeyValues{} keyValues := encodedKeyValues{}
for mapIter.HasNext() { for mapIter.HasNext() {
subStream.buf = make([]byte, 0, 64)
key, elem := mapIter.UnsafeNext() key, elem := mapIter.UnsafeNext()
subStreamIndex := subStream.Buffered()
encoder.keyEncoder.Encode(key, subStream) encoder.keyEncoder.Encode(key, subStream)
if subStream.Error != nil && subStream.Error != io.EOF && stream.Error == nil { if subStream.Error != nil && subStream.Error != io.EOF && stream.Error == nil {
stream.Error = subStream.Error stream.Error = subStream.Error
} }
encodedKey := subStream.Buffer() encodedKey := subStream.Buffer()[subStreamIndex:]
subIter.ResetBytes(encodedKey) subIter.ResetBytes(encodedKey)
decodedKey := subIter.ReadString() decodedKey := subIter.ReadString()
if stream.indention > 0 { if stream.indention > 0 {
@ -310,7 +311,7 @@ func (encoder *sortKeysMapEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
encoder.elemEncoder.Encode(elem, subStream) encoder.elemEncoder.Encode(elem, subStream)
keyValues = append(keyValues, encodedKV{ keyValues = append(keyValues, encodedKV{
key: decodedKey, key: decodedKey,
keyValue: subStream.Buffer(), keyValue: subStream.Buffer()[subStreamIndex:],
}) })
} }
sort.Sort(keyValues) sort.Sort(keyValues)
@ -320,6 +321,9 @@ func (encoder *sortKeysMapEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
} }
stream.Write(keyValue.keyValue) stream.Write(keyValue.keyValue)
} }
if subStream.Error != nil && stream.Error == nil {
stream.Error = subStream.Error
}
stream.WriteObjectEnd() stream.WriteObjectEnd()
stream.cfg.ReturnStream(subStream) stream.cfg.ReturnStream(subStream)
stream.cfg.ReturnIterator(subIter) stream.cfg.ReturnIterator(subIter)

View File

@ -200,6 +200,7 @@ type stringModeStringEncoder struct {
func (encoder *stringModeStringEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { func (encoder *stringModeStringEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
tempStream := encoder.cfg.BorrowStream(nil) tempStream := encoder.cfg.BorrowStream(nil)
tempStream.Attachment = stream.Attachment
defer encoder.cfg.ReturnStream(tempStream) defer encoder.cfg.ReturnStream(tempStream)
encoder.elemEncoder.Encode(ptr, tempStream) encoder.elemEncoder.Encode(ptr, tempStream)
stream.WriteString(string(tempStream.Buffer())) stream.WriteString(string(tempStream.Buffer()))

View File

@ -3,6 +3,7 @@
[![Build Status](https://travis-ci.org/opencontainers/runc.svg?branch=master)](https://travis-ci.org/opencontainers/runc) [![Build Status](https://travis-ci.org/opencontainers/runc.svg?branch=master)](https://travis-ci.org/opencontainers/runc)
[![Go Report Card](https://goreportcard.com/badge/github.com/opencontainers/runc)](https://goreportcard.com/report/github.com/opencontainers/runc) [![Go Report Card](https://goreportcard.com/badge/github.com/opencontainers/runc)](https://goreportcard.com/report/github.com/opencontainers/runc)
[![GoDoc](https://godoc.org/github.com/opencontainers/runc?status.svg)](https://godoc.org/github.com/opencontainers/runc) [![GoDoc](https://godoc.org/github.com/opencontainers/runc?status.svg)](https://godoc.org/github.com/opencontainers/runc)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/588/badge)](https://bestpractices.coreinfrastructure.org/projects/588)
## Introduction ## Introduction
@ -18,22 +19,23 @@ You can find official releases of `runc` on the [release](https://github.com/ope
Currently, the following features are not considered to be production-ready: Currently, the following features are not considered to be production-ready:
* Support for cgroup v2 * [Support for cgroup v2](./docs/cgroup-v2.md)
## Security ## Security
The reporting process and disclosure communications are outlined in [/org/security](https://github.com/opencontainers/org/blob/master/security/). The reporting process and disclosure communications are outlined [here](https://github.com/opencontainers/org/blob/master/SECURITY.md).
### Security Audit
A third party security audit was performed by Cure53, you can see the full report [here](https://github.com/opencontainers/runc/blob/master/docs/Security-Audit.pdf).
## Building ## Building
`runc` currently supports the Linux platform with various architecture support. `runc` currently supports the Linux platform with various architecture support.
It must be built with Go version 1.6 or higher in order for some features to function properly. It must be built with Go version 1.13 or higher.
In order to enable seccomp support you will need to install `libseccomp` on your platform. In order to enable seccomp support you will need to install `libseccomp` on your platform.
> e.g. `libseccomp-devel` for CentOS, or `libseccomp-dev` for Ubuntu > e.g. `libseccomp-devel` for CentOS, or `libseccomp-dev` for Ubuntu
Otherwise, if you do not want to build `runc` with seccomp support you can add `BUILDTAGS=""` when running make.
```bash ```bash
# create a 'github.com/opencontainers' in your GOPATH/src # create a 'github.com/opencontainers' in your GOPATH/src
cd github.com/opencontainers cd github.com/opencontainers
@ -58,20 +60,22 @@ sudo make install
#### Build Tags #### Build Tags
`runc` supports optional build tags for compiling support of various features. `runc` supports optional build tags for compiling support of various features,
To add build tags to the make option the `BUILDTAGS` variable must be set. with some of them enabled by default (see `BUILDTAGS` in top-level `Makefile`).
To change build tags from the default, set the `BUILDTAGS` variable for make,
e.g.
```bash ```bash
make BUILDTAGS='seccomp apparmor' make BUILDTAGS='seccomp apparmor'
``` ```
| Build Tag | Feature | Dependency | | Build Tag | Feature | Enabled by default | Dependency |
|-----------|------------------------------------|-------------| |-----------|------------------------------------|--------------------|------------|
| seccomp | Syscall filtering | libseccomp | | seccomp | Syscall filtering | yes | libseccomp |
| selinux | selinux process and mount labeling | <none> | | selinux | selinux process and mount labeling | yes | <none> |
| apparmor | apparmor profile support | <none> | | apparmor | apparmor profile support | yes | <none> |
| ambient | ambient capability support | kernel 4.3 | | nokmem | disable kernel memory accounting | no | <none> |
| nokmem | disable kernel memory account | <none> |
### Running the test suite ### Running the test suite
@ -97,17 +101,30 @@ You can run a specific integration test by setting the `TESTPATH` variable.
# make test TESTPATH="/checkpoint.bats" # make test TESTPATH="/checkpoint.bats"
``` ```
You can run a test in your proxy environment by setting `DOCKER_BUILD_PROXY` and `DOCKER_RUN_PROXY` variables. You can run a specific rootless integration test by setting the `ROOTLESS_TESTPATH` variable.
```bash ```bash
# make test DOCKER_BUILD_PROXY="--build-arg HTTP_PROXY=http://yourproxy/" DOCKER_RUN_PROXY="-e HTTP_PROXY=http://yourproxy/" # make test ROOTLESS_TESTPATH="/checkpoint.bats"
```
You can run a test using your container engine's flags by setting `CONTAINER_ENGINE_BUILD_FLAGS` and `CONTAINER_ENGINE_RUN_FLAGS` variables.
```bash
# make test CONTAINER_ENGINE_BUILD_FLAGS="--build-arg http_proxy=http://yourproxy/" CONTAINER_ENGINE_RUN_FLAGS="-e http_proxy=http://yourproxy/"
``` ```
### Dependencies Management ### Dependencies Management
`runc` uses [vndr](https://github.com/LK4D4/vndr) for dependencies management. `runc` uses [Go Modules](https://github.com/golang/go/wiki/Modules) for dependencies management.
Please refer to [vndr](https://github.com/LK4D4/vndr) for how to add or update Please refer to [Go Modules](https://github.com/golang/go/wiki/Modules) for how to add or update
new dependencies. new dependencies. When updating dependencies, be sure that you are running Go `1.14` or newer.
```
# Update vendored dependencies
make vendor
# Verify all dependencies
make verify-dependencies
```
## Using runc ## Using runc
@ -275,6 +292,9 @@ PIDFile=/run/mycontainerid.pid
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
#### cgroup v2
See [`./docs/cgroup-v2.md`](./docs/cgroup-v2.md).
## License ## License
The code and docs are released under the [Apache 2.0 license](LICENSE). The code and docs are released under the [Apache 2.0 license](LICENSE).

26
vendor/github.com/opencontainers/runc/go.mod generated vendored Normal file
View File

@ -0,0 +1,26 @@
module github.com/opencontainers/runc
go 1.14
require (
github.com/checkpoint-restore/go-criu/v4 v4.0.2
github.com/cilium/ebpf v0.0.0-20200507155900-a9f01edf17e3
github.com/containerd/console v1.0.0
github.com/coreos/go-systemd/v22 v22.0.0
github.com/cyphar/filepath-securejoin v0.2.2
github.com/docker/go-units v0.4.0
github.com/godbus/dbus/v5 v5.0.3
github.com/golang/protobuf v1.3.5
github.com/moby/sys/mountinfo v0.1.3
github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618
github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2
github.com/opencontainers/selinux v1.5.1
github.com/pkg/errors v0.9.1
github.com/seccomp/libseccomp-golang v0.9.1
github.com/sirupsen/logrus v1.6.0
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
// NOTE: urfave/cli must be <= v1.22.1 due to a regression: https://github.com/urfave/cli/issues/1092
github.com/urfave/cli v1.22.1
github.com/vishvananda/netlink v1.1.0
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775
)

View File

@ -155,8 +155,7 @@ config := &configs.Config{
Parent: "system", Parent: "system",
Resources: &configs.Resources{ Resources: &configs.Resources{
MemorySwappiness: nil, MemorySwappiness: nil,
AllowAllDevices: nil, Devices: specconv.AllowedDevices,
AllowedDevices: configs.DefaultAllowedDevices,
}, },
}, },
MaskPaths: []string{ MaskPaths: []string{
@ -166,7 +165,7 @@ config := &configs.Config{
ReadonlyPaths: []string{ ReadonlyPaths: []string{
"/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus", "/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
}, },
Devices: configs.DefaultAutoCreatedDevices, Devices: specconv.AllowedDevices,
Hostname: "testing", Hostname: "testing",
Mounts: []*configs.Mount{ Mounts: []*configs.Mount{
{ {

View File

@ -1,5 +1,9 @@
package configs package configs
import (
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
)
type FreezerState string type FreezerState string
const ( const (
@ -29,18 +33,16 @@ type Cgroup struct {
// Resources contains various cgroups settings to apply // Resources contains various cgroups settings to apply
*Resources *Resources
// SystemdProps are any additional properties for systemd,
// derived from org.systemd.property.xxx annotations.
// Ignored unless systemd is used for managing cgroups.
SystemdProps []systemdDbus.Property `json:"-"`
} }
type Resources struct { type Resources struct {
// If this is true allow access to any kind of device within the container. If false, allow access only to devices explicitly listed in the allowed_devices list. // Devices is the set of access rules for devices in the container.
// Deprecated Devices []*DeviceRule `json:"devices"`
AllowAllDevices *bool `json:"allow_all_devices,omitempty"`
// Deprecated
AllowedDevices []*Device `json:"allowed_devices,omitempty"`
// Deprecated
DeniedDevices []*Device `json:"denied_devices,omitempty"`
Devices []*Device `json:"devices"`
// Memory limit (in bytes) // Memory limit (in bytes)
Memory int64 `json:"memory"` Memory int64 `json:"memory"`
@ -124,7 +126,4 @@ type Resources struct {
// CpuWeight sets a proportional bandwidth limit. // CpuWeight sets a proportional bandwidth limit.
CpuWeight uint64 `json:"cpu_weight"` CpuWeight uint64 `json:"cpu_weight"`
// CpuMax sets she maximum bandwidth limit (format: max period).
CpuMax string `json:"cpu_max"`
} }

View File

@ -8,7 +8,7 @@ import (
"time" "time"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -72,6 +72,7 @@ type Arg struct {
type Syscall struct { type Syscall struct {
Name string `json:"name"` Name string `json:"name"`
Action Action `json:"action"` Action Action `json:"action"`
ErrnoRet *uint `json:"errnoRet"`
Args []*Arg `json:"args"` Args []*Arg `json:"args"`
} }
@ -175,7 +176,7 @@ type Config struct {
// Hooks are a collection of actions to perform at various container lifecycle events. // Hooks are a collection of actions to perform at various container lifecycle events.
// CommandHooks are serialized to JSON, but other hooks are not. // CommandHooks are serialized to JSON, but other hooks are not.
Hooks *Hooks Hooks Hooks
// Version is the version of opencontainer specification that is supported. // Version is the version of opencontainer specification that is supported.
Version string `json:"version"` Version string `json:"version"`
@ -202,17 +203,50 @@ type Config struct {
RootlessCgroups bool `json:"rootless_cgroups,omitempty"` RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
} }
type Hooks struct { type HookName string
type HookList []Hook
type Hooks map[HookName]HookList
const (
// Prestart commands are executed after the container namespaces are created, // Prestart commands are executed after the container namespaces are created,
// but before the user supplied command is executed from init. // but before the user supplied command is executed from init.
Prestart []Hook // Note: This hook is now deprecated
// Prestart commands are called in the Runtime namespace.
Prestart HookName = "prestart"
// CreateRuntime commands MUST be called as part of the create operation after
// the runtime environment has been created but before the pivot_root has been executed.
// CreateRuntime is called immediately after the deprecated Prestart hook.
// CreateRuntime commands are called in the Runtime Namespace.
CreateRuntime = "createRuntime"
// CreateContainer commands MUST be called as part of the create operation after
// the runtime environment has been created but before the pivot_root has been executed.
// CreateContainer commands are called in the Container namespace.
CreateContainer = "createContainer"
// StartContainer commands MUST be called as part of the start operation and before
// the container process is started.
// StartContainer commands are called in the Container namespace.
StartContainer = "startContainer"
// Poststart commands are executed after the container init process starts. // Poststart commands are executed after the container init process starts.
Poststart []Hook // Poststart commands are called in the Runtime Namespace.
Poststart = "poststart"
// Poststop commands are executed after the container init process exits. // Poststop commands are executed after the container init process exits.
Poststop []Hook // Poststop commands are called in the Runtime Namespace.
} Poststop = "poststop"
)
// TODO move this to runtime-spec
// See: https://github.com/opencontainers/runtime-spec/pull/1046
const (
Creating = "creating"
Created = "created"
Running = "running"
Stopped = "stopped"
)
type Capabilities struct { type Capabilities struct {
// Bounding is the set of capabilities checked by the kernel. // Bounding is the set of capabilities checked by the kernel.
@ -227,32 +261,39 @@ type Capabilities struct {
Ambient []string Ambient []string
} }
func (hooks *Hooks) UnmarshalJSON(b []byte) error { func (hooks HookList) RunHooks(state *specs.State) error {
var state struct { for i, h := range hooks {
Prestart []CommandHook if err := h.Run(state); err != nil {
Poststart []CommandHook return errors.Wrapf(err, "Running hook #%d:", i)
Poststop []CommandHook
} }
}
return nil
}
func (hooks *Hooks) UnmarshalJSON(b []byte) error {
var state map[HookName][]CommandHook
if err := json.Unmarshal(b, &state); err != nil { if err := json.Unmarshal(b, &state); err != nil {
return err return err
} }
deserialize := func(shooks []CommandHook) (hooks []Hook) { *hooks = Hooks{}
for _, shook := range shooks { for n, commandHooks := range state {
hooks = append(hooks, shook) if len(commandHooks) == 0 {
continue
} }
return hooks (*hooks)[n] = HookList{}
for _, h := range commandHooks {
(*hooks)[n] = append((*hooks)[n], h)
}
} }
hooks.Prestart = deserialize(state.Prestart)
hooks.Poststart = deserialize(state.Poststart)
hooks.Poststop = deserialize(state.Poststop)
return nil return nil
} }
func (hooks Hooks) MarshalJSON() ([]byte, error) { func (hooks *Hooks) MarshalJSON() ([]byte, error) {
serialize := func(hooks []Hook) (serializableHooks []CommandHook) { serialize := func(hooks []Hook) (serializableHooks []CommandHook) {
for _, hook := range hooks { for _, hook := range hooks {
switch chook := hook.(type) { switch chook := hook.(type) {
@ -267,9 +308,12 @@ func (hooks Hooks) MarshalJSON() ([]byte, error) {
} }
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]interface{}{
"prestart": serialize(hooks.Prestart), "prestart": serialize((*hooks)[Prestart]),
"poststart": serialize(hooks.Poststart), "createRuntime": serialize((*hooks)[CreateRuntime]),
"poststop": serialize(hooks.Poststop), "createContainer": serialize((*hooks)[CreateContainer]),
"startContainer": serialize((*hooks)[StartContainer]),
"poststart": serialize((*hooks)[Poststart]),
"poststop": serialize((*hooks)[Poststop]),
}) })
} }

View File

@ -1,8 +1,12 @@
package configs package configs
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"strconv"
"golang.org/x/sys/unix"
) )
const ( const (
@ -12,21 +16,11 @@ const (
// TODO Windows: This can be factored out in the future // TODO Windows: This can be factored out in the future
type Device struct { type Device struct {
// Device type, block, char, etc. DeviceRule
Type rune `json:"type"`
// Path to the device. // Path to the device.
Path string `json:"path"` Path string `json:"path"`
// Major is the device's major number.
Major int64 `json:"major"`
// Minor is the device's minor number.
Minor int64 `json:"minor"`
// Cgroup permissions format, rwm.
Permissions string `json:"permissions"`
// FileMode permission bits for the device. // FileMode permission bits for the device.
FileMode os.FileMode `json:"file_mode"` FileMode os.FileMode `json:"file_mode"`
@ -35,23 +29,154 @@ type Device struct {
// Gid of the device. // Gid of the device.
Gid uint32 `json:"gid"` Gid uint32 `json:"gid"`
}
// Write the file to the allowed list // DevicePermissions is a cgroupv1-style string to represent device access. It
// has to be a string for backward compatibility reasons, hence why it has
// methods to do set operations.
type DevicePermissions string
const (
deviceRead uint = (1 << iota)
deviceWrite
deviceMknod
)
func (p DevicePermissions) toSet() uint {
var set uint
for _, perm := range p {
switch perm {
case 'r':
set |= deviceRead
case 'w':
set |= deviceWrite
case 'm':
set |= deviceMknod
}
}
return set
}
func fromSet(set uint) DevicePermissions {
var perm string
if set&deviceRead == deviceRead {
perm += "r"
}
if set&deviceWrite == deviceWrite {
perm += "w"
}
if set&deviceMknod == deviceMknod {
perm += "m"
}
return DevicePermissions(perm)
}
// Union returns the union of the two sets of DevicePermissions.
func (p DevicePermissions) Union(o DevicePermissions) DevicePermissions {
lhs := p.toSet()
rhs := o.toSet()
return fromSet(lhs | rhs)
}
// Difference returns the set difference of the two sets of DevicePermissions.
// In set notation, A.Difference(B) gives you A\B.
func (p DevicePermissions) Difference(o DevicePermissions) DevicePermissions {
lhs := p.toSet()
rhs := o.toSet()
return fromSet(lhs &^ rhs)
}
// Intersection computes the intersection of the two sets of DevicePermissions.
func (p DevicePermissions) Intersection(o DevicePermissions) DevicePermissions {
lhs := p.toSet()
rhs := o.toSet()
return fromSet(lhs & rhs)
}
// IsEmpty returns whether the set of permissions in a DevicePermissions is
// empty.
func (p DevicePermissions) IsEmpty() bool {
return p == DevicePermissions("")
}
// IsValid returns whether the set of permissions is a subset of valid
// permissions (namely, {r,w,m}).
func (p DevicePermissions) IsValid() bool {
return p == fromSet(p.toSet())
}
type DeviceType rune
const (
WildcardDevice DeviceType = 'a'
BlockDevice DeviceType = 'b'
CharDevice DeviceType = 'c' // or 'u'
FifoDevice DeviceType = 'p'
)
func (t DeviceType) IsValid() bool {
switch t {
case WildcardDevice, BlockDevice, CharDevice, FifoDevice:
return true
default:
return false
}
}
func (t DeviceType) CanMknod() bool {
switch t {
case BlockDevice, CharDevice, FifoDevice:
return true
default:
return false
}
}
func (t DeviceType) CanCgroup() bool {
switch t {
case WildcardDevice, BlockDevice, CharDevice:
return true
default:
return false
}
}
type DeviceRule struct {
// Type of device ('c' for char, 'b' for block). If set to 'a', this rule
// acts as a wildcard and all fields other than Allow are ignored.
Type DeviceType `json:"type"`
// Major is the device's major number.
Major int64 `json:"major"`
// Minor is the device's minor number.
Minor int64 `json:"minor"`
// Permissions is the set of permissions that this rule applies to (in the
// cgroupv1 format -- any combination of "rwm").
Permissions DevicePermissions `json:"permissions"`
// Allow specifies whether this rule is allowed.
Allow bool `json:"allow"` Allow bool `json:"allow"`
} }
func (d *Device) CgroupString() string { func (d *DeviceRule) CgroupString() string {
return fmt.Sprintf("%c %s:%s %s", d.Type, deviceNumberString(d.Major), deviceNumberString(d.Minor), d.Permissions) var (
} major = strconv.FormatInt(d.Major, 10)
minor = strconv.FormatInt(d.Minor, 10)
func (d *Device) Mkdev() int { )
return int((d.Major << 8) | (d.Minor & 0xff) | ((d.Minor & 0xfff00) << 12)) if d.Major == Wildcard {
} major = "*"
// deviceNumberString converts the device number to a string return result.
func deviceNumberString(number int64) string {
if number == Wildcard {
return "*"
} }
return fmt.Sprint(number) if d.Minor == Wildcard {
minor = "*"
}
return fmt.Sprintf("%c %s:%s %s", d.Type, major, minor, d.Permissions)
}
func (d *DeviceRule) Mkdev() (uint64, error) {
if d.Major == Wildcard || d.Minor == Wildcard {
return 0, errors.New("cannot mkdev() device with wildcards")
}
return unix.Mkdev(uint32(d.Major), uint32(d.Minor)), nil
} }

View File

@ -1,111 +0,0 @@
// +build linux
package configs
var (
// DefaultSimpleDevices are devices that are to be both allowed and created.
DefaultSimpleDevices = []*Device{
// /dev/null and zero
{
Path: "/dev/null",
Type: 'c',
Major: 1,
Minor: 3,
Permissions: "rwm",
FileMode: 0666,
},
{
Path: "/dev/zero",
Type: 'c',
Major: 1,
Minor: 5,
Permissions: "rwm",
FileMode: 0666,
},
{
Path: "/dev/full",
Type: 'c',
Major: 1,
Minor: 7,
Permissions: "rwm",
FileMode: 0666,
},
// consoles and ttys
{
Path: "/dev/tty",
Type: 'c',
Major: 5,
Minor: 0,
Permissions: "rwm",
FileMode: 0666,
},
// /dev/urandom,/dev/random
{
Path: "/dev/urandom",
Type: 'c',
Major: 1,
Minor: 9,
Permissions: "rwm",
FileMode: 0666,
},
{
Path: "/dev/random",
Type: 'c',
Major: 1,
Minor: 8,
Permissions: "rwm",
FileMode: 0666,
},
}
DefaultAllowedDevices = append([]*Device{
// allow mknod for any device
{
Type: 'c',
Major: Wildcard,
Minor: Wildcard,
Permissions: "m",
},
{
Type: 'b',
Major: Wildcard,
Minor: Wildcard,
Permissions: "m",
},
{
Path: "/dev/console",
Type: 'c',
Major: 5,
Minor: 1,
Permissions: "rwm",
},
// /dev/pts/ - pts namespaces are "coming soon"
{
Path: "",
Type: 'c',
Major: 136,
Minor: Wildcard,
Permissions: "rwm",
},
{
Path: "",
Type: 'c',
Major: 5,
Minor: 2,
Permissions: "rwm",
},
// tuntap
{
Path: "",
Type: 'c',
Major: 10,
Minor: 200,
Permissions: "rwm",
},
}, DefaultSimpleDevices...)
DefaultAutoCreatedDevices = append([]*Device{}, DefaultSimpleDevices...)
)

View File

@ -31,30 +31,30 @@ func DeviceFromPath(path, permissions string) (*configs.Device, error) {
} }
var ( var (
devType configs.DeviceType
mode = stat.Mode
devNumber = uint64(stat.Rdev) devNumber = uint64(stat.Rdev)
major = unix.Major(devNumber) major = unix.Major(devNumber)
minor = unix.Minor(devNumber) minor = unix.Minor(devNumber)
) )
if major == 0 {
return nil, ErrNotADevice
}
var (
devType rune
mode = stat.Mode
)
switch { switch {
case mode&unix.S_IFBLK == unix.S_IFBLK: case mode&unix.S_IFBLK == unix.S_IFBLK:
devType = 'b' devType = configs.BlockDevice
case mode&unix.S_IFCHR == unix.S_IFCHR: case mode&unix.S_IFCHR == unix.S_IFCHR:
devType = 'c' devType = configs.CharDevice
case mode&unix.S_IFIFO == unix.S_IFIFO:
devType = configs.FifoDevice
default:
return nil, ErrNotADevice
} }
return &configs.Device{ return &configs.Device{
DeviceRule: configs.DeviceRule{
Type: devType, Type: devType,
Path: path,
Major: int64(major), Major: int64(major),
Minor: int64(minor), Minor: int64(minor),
Permissions: permissions, Permissions: configs.DevicePermissions(permissions),
},
Path: path,
FileMode: os.FileMode(mode), FileMode: os.FileMode(mode),
Uid: stat.Uid, Uid: stat.Uid,
Gid: stat.Gid, Gid: stat.Gid,

View File

@ -1,7 +1,14 @@
// SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
/* /*
* Copyright (C) 2019 Aleksa Sarai <cyphar@cyphar.com> * Copyright (C) 2019 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019 SUSE LLC * Copyright (C) 2019 SUSE LLC
* *
* This work is dual licensed under the following licenses. You may use,
* redistribute, and/or modify the work under the conditions of either (or
* both) licenses.
*
* === Apache-2.0 ===
*
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
@ -13,6 +20,23 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* 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.
*
* === LGPL-2.1-or-later ===
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <https://www.gnu.org/licenses/>.
*
*/ */
#define _GNU_SOURCE #define _GNU_SOURCE
@ -95,8 +119,10 @@ static int is_self_cloned(void)
struct statfs fsbuf = {}; struct statfs fsbuf = {};
fd = open("/proc/self/exe", O_RDONLY|O_CLOEXEC); fd = open("/proc/self/exe", O_RDONLY|O_CLOEXEC);
if (fd < 0) if (fd < 0) {
fprintf(stderr, "you have no read access to runc binary file\n");
return -ENOTRECOVERABLE; return -ENOTRECOVERABLE;
}
/* /*
* Is the binary a fully-sealed memfd? We don't need CLONED_BINARY_ENV for * Is the binary a fully-sealed memfd? We don't need CLONED_BINARY_ENV for

View File

@ -714,12 +714,12 @@ void nsexec(void)
* ready, so we can receive all possible error codes * ready, so we can receive all possible error codes
* generated by children. * generated by children.
*/ */
while (!ready) {
enum sync_t s;
syncfd = sync_child_pipe[1]; syncfd = sync_child_pipe[1];
close(sync_child_pipe[0]); close(sync_child_pipe[0]);
while (!ready) {
enum sync_t s;
if (read(syncfd, &s, sizeof(s)) != sizeof(s)) if (read(syncfd, &s, sizeof(s)) != sizeof(s))
bail("failed to sync with child: next state"); bail("failed to sync with child: next state");
@ -789,13 +789,13 @@ void nsexec(void)
/* Now sync with grandchild. */ /* Now sync with grandchild. */
syncfd = sync_grandchild_pipe[1];
close(sync_grandchild_pipe[0]);
ready = false; ready = false;
while (!ready) { while (!ready) {
enum sync_t s; enum sync_t s;
syncfd = sync_grandchild_pipe[1];
close(sync_grandchild_pipe[0]);
s = SYNC_GRANDCHILD; s = SYNC_GRANDCHILD;
if (write(syncfd, &s, sizeof(s)) != sizeof(s)) { if (write(syncfd, &s, sizeof(s)) != sizeof(s)) {
kill(child, SIGKILL); kill(child, SIGKILL);

View File

@ -4,6 +4,7 @@ package seccomp
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@ -34,12 +35,12 @@ const (
// of the init until they join the namespace // of the init until they join the namespace
func InitSeccomp(config *configs.Seccomp) error { func InitSeccomp(config *configs.Seccomp) error {
if config == nil { if config == nil {
return fmt.Errorf("cannot initialize Seccomp - nil config passed") return errors.New("cannot initialize Seccomp - nil config passed")
} }
defaultAction, err := getAction(config.DefaultAction) defaultAction, err := getAction(config.DefaultAction, nil)
if err != nil { if err != nil {
return fmt.Errorf("error initializing seccomp - invalid default action") return errors.New("error initializing seccomp - invalid default action")
} }
filter, err := libseccomp.NewFilter(defaultAction) filter, err := libseccomp.NewFilter(defaultAction)
@ -67,7 +68,7 @@ func InitSeccomp(config *configs.Seccomp) error {
// Add a rule for each syscall // Add a rule for each syscall
for _, call := range config.Syscalls { for _, call := range config.Syscalls {
if call == nil { if call == nil {
return fmt.Errorf("encountered nil syscall while initializing Seccomp") return errors.New("encountered nil syscall while initializing Seccomp")
} }
if err = matchCall(filter, call); err != nil { if err = matchCall(filter, call); err != nil {
@ -101,22 +102,28 @@ func IsEnabled() bool {
} }
// Convert Libcontainer Action to Libseccomp ScmpAction // Convert Libcontainer Action to Libseccomp ScmpAction
func getAction(act configs.Action) (libseccomp.ScmpAction, error) { func getAction(act configs.Action, errnoRet *uint) (libseccomp.ScmpAction, error) {
switch act { switch act {
case configs.Kill: case configs.Kill:
return actKill, nil return actKill, nil
case configs.Errno: case configs.Errno:
if errnoRet != nil {
return libseccomp.ActErrno.SetReturnCode(int16(*errnoRet)), nil
}
return actErrno, nil return actErrno, nil
case configs.Trap: case configs.Trap:
return actTrap, nil return actTrap, nil
case configs.Allow: case configs.Allow:
return actAllow, nil return actAllow, nil
case configs.Trace: case configs.Trace:
if errnoRet != nil {
return libseccomp.ActTrace.SetReturnCode(int16(*errnoRet)), nil
}
return actTrace, nil return actTrace, nil
case configs.Log: case configs.Log:
return actLog, nil return actLog, nil
default: default:
return libseccomp.ActInvalid, fmt.Errorf("invalid action, cannot use in rule") return libseccomp.ActInvalid, errors.New("invalid action, cannot use in rule")
} }
} }
@ -138,7 +145,7 @@ func getOperator(op configs.Operator) (libseccomp.ScmpCompareOp, error) {
case configs.MaskEqualTo: case configs.MaskEqualTo:
return libseccomp.CompareMaskedEqual, nil return libseccomp.CompareMaskedEqual, nil
default: default:
return libseccomp.CompareInvalid, fmt.Errorf("invalid operator, cannot use in rule") return libseccomp.CompareInvalid, errors.New("invalid operator, cannot use in rule")
} }
} }
@ -147,7 +154,7 @@ func getCondition(arg *configs.Arg) (libseccomp.ScmpCondition, error) {
cond := libseccomp.ScmpCondition{} cond := libseccomp.ScmpCondition{}
if arg == nil { if arg == nil {
return cond, fmt.Errorf("cannot convert nil to syscall condition") return cond, errors.New("cannot convert nil to syscall condition")
} }
op, err := getOperator(arg.Op) op, err := getOperator(arg.Op)
@ -161,11 +168,11 @@ func getCondition(arg *configs.Arg) (libseccomp.ScmpCondition, error) {
// Add a rule to match a single syscall // Add a rule to match a single syscall
func matchCall(filter *libseccomp.ScmpFilter, call *configs.Syscall) error { func matchCall(filter *libseccomp.ScmpFilter, call *configs.Syscall) error {
if call == nil || filter == nil { if call == nil || filter == nil {
return fmt.Errorf("cannot use nil as syscall to block") return errors.New("cannot use nil as syscall to block")
} }
if len(call.Name) == 0 { if len(call.Name) == 0 {
return fmt.Errorf("empty string is not a valid syscall") return errors.New("empty string is not a valid syscall")
} }
// If we can't resolve the syscall, assume it's not supported on this kernel // If we can't resolve the syscall, assume it's not supported on this kernel
@ -176,7 +183,7 @@ func matchCall(filter *libseccomp.ScmpFilter, call *configs.Syscall) error {
} }
// Convert the call's action to the libseccomp equivalent // Convert the call's action to the libseccomp equivalent
callAct, err := getAction(call.Action) callAct, err := getAction(call.Action, call.ErrnoRet)
if err != nil { if err != nil {
return fmt.Errorf("action in seccomp profile is invalid: %s", err) return fmt.Errorf("action in seccomp profile is invalid: %s", err)
} }

View File

@ -162,10 +162,6 @@ func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
) )
for s.Scan() { for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
line := strings.TrimSpace(s.Text()) line := strings.TrimSpace(s.Text())
if line == "" { if line == "" {
continue continue
@ -183,6 +179,9 @@ func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
out = append(out, p) out = append(out, p)
} }
} }
if err := s.Err(); err != nil {
return nil, err
}
return out, nil return out, nil
} }
@ -221,10 +220,6 @@ func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
) )
for s.Scan() { for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
text := s.Text() text := s.Text()
if text == "" { if text == "" {
continue continue
@ -242,6 +237,9 @@ func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
out = append(out, p) out = append(out, p)
} }
} }
if err := s.Err(); err != nil {
return nil, err
}
return out, nil return out, nil
} }
@ -532,10 +530,6 @@ func ParseSubIDFilter(r io.Reader, filter func(SubID) bool) ([]SubID, error) {
) )
for s.Scan() { for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
line := strings.TrimSpace(s.Text()) line := strings.TrimSpace(s.Text())
if line == "" { if line == "" {
continue continue
@ -549,6 +543,9 @@ func ParseSubIDFilter(r io.Reader, filter func(SubID) bool) ([]SubID, error) {
out = append(out, p) out = append(out, p)
} }
} }
if err := s.Err(); err != nil {
return nil, err
}
return out, nil return out, nil
} }
@ -586,10 +583,6 @@ func ParseIDMapFilter(r io.Reader, filter func(IDMap) bool) ([]IDMap, error) {
) )
for s.Scan() { for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
line := strings.TrimSpace(s.Text()) line := strings.TrimSpace(s.Text())
if line == "" { if line == "" {
continue continue
@ -603,6 +596,9 @@ func ParseIDMapFilter(r io.Reader, filter func(IDMap) bool) ([]IDMap, error) {
out = append(out, p) out = append(out, p)
} }
} }
if err := s.Err(); err != nil {
return nil, err
}
return out, nil return out, nil
} }

View File

@ -1,31 +0,0 @@
# OCI runtime-spec. When updating this, make sure you use a version tag rather
# than a commit ID so it's much more obvious what version of the spec we are
# using.
github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db
# Core libcontainer functionality.
github.com/checkpoint-restore/go-criu 17b0214f6c48980c45dc47ecb0cfd6d9e02df723 # v3.11
github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7
github.com/opencontainers/selinux 5215b1806f52b1fcc2070a8826c542c9d33cd3cf # v1.3.0 (+ CVE-2019-16884)
github.com/seccomp/libseccomp-golang 689e3c1541a84461afc49c1c87352a6cedf72e9c # v0.9.1
github.com/sirupsen/logrus 8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f # v1.4.1
github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2
github.com/vishvananda/netlink 1e2e08e8a2dcdacaae3f14ac44c5cfa31361f270
# systemd integration.
github.com/coreos/go-systemd 95778dfbb74eb7e4dbaf43bf7d71809650ef8076 # v19
github.com/godbus/dbus 2ff6f7ffd60f0f2410b3105864bdd12c7894f844 # v5.0.1
github.com/golang/protobuf 925541529c1fa6821df4e44ce2723319eb2be768 # v1.0.0
# Command-line interface.
github.com/cyphar/filepath-securejoin a261ee33d7a517f054effbf451841abaafe3e0fd # v0.2.2
github.com/docker/go-units 47565b4f722fb6ceae66b95f853feed578a4a51c # v0.3.3
github.com/urfave/cli cfb38830724cc34fedffe9a2a29fb54fa9169cd1 # v1.20.0
golang.org/x/sys 9eafafc0a87e0fd0aeeba439a4573537970c44c7 https://github.com/golang/sys
# console dependencies
github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f
github.com/pkg/errors ba968bfe8b2f7e042a574c888954fccecfa385b4 # v0.8.1
# ebpf dependencies
github.com/cilium/ebpf 95b36a581eed7b0f127306ed1d16cc0ddc06cf67

View File

@ -669,6 +669,7 @@ type LinuxSeccompArg struct {
type LinuxSyscall struct { type LinuxSyscall struct {
Names []string `json:"names"` Names []string `json:"names"`
Action LinuxSeccompAction `json:"action"` Action LinuxSeccompAction `json:"action"`
ErrnoRet *uint `json:"errnoRet,omitempty"`
Args []LinuxSeccompArg `json:"args,omitempty"` Args []LinuxSeccompArg `json:"args,omitempty"`
} }

View File

@ -11,7 +11,7 @@ const (
VersionPatch = 2 VersionPatch = 2
// VersionDev indicates development branch. Releases will be empty string. // VersionDev indicates development branch. Releases will be empty string.
VersionDev = "" VersionDev = "-dev"
) )
// Version is the specification version that the package types support. // Version is the specification version that the package types support.

View File

@ -1,8 +1,6 @@
package label package label
import ( import (
"fmt"
"github.com/opencontainers/selinux/go-selinux" "github.com/opencontainers/selinux/go-selinux"
) )
@ -48,7 +46,7 @@ var PidLabel = selinux.PidLabel
// Init initialises the labeling system // Init initialises the labeling system
func Init() { func Init() {
_ = selinux.GetEnabled() selinux.GetEnabled()
} }
// ClearLabels will clear all reserved labels // ClearLabels will clear all reserved labels
@ -77,21 +75,3 @@ func ReleaseLabel(label string) error {
// can be used to set duplicate labels on future container processes // can be used to set duplicate labels on future container processes
// Deprecated: use selinux.DupSecOpt // Deprecated: use selinux.DupSecOpt
var DupSecOpt = selinux.DupSecOpt var DupSecOpt = selinux.DupSecOpt
// FormatMountLabel returns a string to be used by the mount command.
// The format of this string will be used to alter the labeling of the mountpoint.
// The string returned is suitable to be used as the options field of the mount command.
// If you need to have additional mount point options, you can pass them in as
// the first parameter. Second parameter is the label that you wish to apply
// to all content in the mount point.
func FormatMountLabel(src, mountLabel string) string {
if mountLabel != "" {
switch src {
case "":
src = fmt.Sprintf("context=%q", mountLabel)
default:
src = fmt.Sprintf("%s,context=%q", src, mountLabel)
}
}
return src
}

View File

@ -3,6 +3,7 @@
package label package label
import ( import (
"fmt"
"os" "os"
"os/user" "os/user"
"strings" "strings"
@ -42,7 +43,7 @@ func InitLabels(options []string) (plabel string, mlabel string, Err error) {
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
mcsLevel := pcon["level"]
mcon, err := selinux.NewContext(mountLabel) mcon, err := selinux.NewContext(mountLabel)
if err != nil { if err != nil {
return "", "", err return "", "", err
@ -61,21 +62,16 @@ func InitLabels(options []string) (plabel string, mlabel string, Err error) {
} }
if con[0] == "filetype" { if con[0] == "filetype" {
mcon["type"] = con[1] mcon["type"] = con[1]
continue
} }
pcon[con[0]] = con[1] pcon[con[0]] = con[1]
if con[0] == "level" || con[0] == "user" { if con[0] == "level" || con[0] == "user" {
mcon[con[0]] = con[1] mcon[con[0]] = con[1]
} }
} }
if pcon.Get() != processLabel {
if pcon["level"] != mcsLevel {
selinux.ReleaseLabel(processLabel) selinux.ReleaseLabel(processLabel)
}
processLabel = pcon.Get() processLabel = pcon.Get()
selinux.ReserveLabel(processLabel)
}
mountLabel = mcon.Get() mountLabel = mcon.Get()
selinux.ReserveLabel(processLabel)
} }
return processLabel, mountLabel, nil return processLabel, mountLabel, nil
} }
@ -86,6 +82,24 @@ func GenLabels(options string) (string, string, error) {
return InitLabels(strings.Fields(options)) return InitLabels(strings.Fields(options))
} }
// FormatMountLabel returns a string to be used by the mount command.
// The format of this string will be used to alter the labeling of the mountpoint.
// The string returned is suitable to be used as the options field of the mount command.
// If you need to have additional mount point options, you can pass them in as
// the first parameter. Second parameter is the label that you wish to apply
// to all content in the mount point.
func FormatMountLabel(src, mountLabel string) string {
if mountLabel != "" {
switch src {
case "":
src = fmt.Sprintf("context=%q", mountLabel)
default:
src = fmt.Sprintf("%s,context=%q", src, mountLabel)
}
}
return src
}
// SetFileLabel modifies the "path" label to the specified file label // SetFileLabel modifies the "path" label to the specified file label
func SetFileLabel(path string, fileLabel string) error { func SetFileLabel(path string, fileLabel string) error {
if !selinux.GetEnabled() || fileLabel == "" { if !selinux.GetEnabled() || fileLabel == "" {

View File

@ -15,6 +15,10 @@ func GenLabels(options string) (string, string, error) {
return "", "", nil return "", "", nil
} }
func FormatMountLabel(src string, mountLabel string) string {
return src
}
func SetFileLabel(path string, fileLabel string) error { func SetFileLabel(path string, fileLabel string) error {
return nil return nil
} }

View File

@ -31,9 +31,6 @@ const (
// Disabled constant to indicate SELinux is disabled // Disabled constant to indicate SELinux is disabled
Disabled = -1 Disabled = -1
// DefaultCategoryRange is the upper bound on the category range
DefaultCategoryRange = uint32(1024)
contextFile = "/usr/share/containers/selinux/contexts" contextFile = "/usr/share/containers/selinux/contexts"
selinuxDir = "/etc/selinux/" selinuxDir = "/etc/selinux/"
selinuxConfig = selinuxDir + "config" selinuxConfig = selinuxDir + "config"
@ -60,9 +57,6 @@ var (
// InvalidLabel is returned when an invalid label is specified. // InvalidLabel is returned when an invalid label is specified.
InvalidLabel = errors.New("Invalid Label") InvalidLabel = errors.New("Invalid Label")
// CategoryRange allows the upper bound on the category range to be adjusted
CategoryRange = DefaultCategoryRange
assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`)
roFileLabel string roFileLabel string
state = selinuxState{ state = selinuxState{
@ -796,7 +790,7 @@ func ContainerLabels() (processLabel string, fileLabel string) {
func addMcs(processLabel, fileLabel string) (string, string) { func addMcs(processLabel, fileLabel string) (string, string) {
scon, _ := NewContext(processLabel) scon, _ := NewContext(processLabel)
if scon["level"] != "" { if scon["level"] != "" {
mcs := uniqMcs(CategoryRange) mcs := uniqMcs(1024)
scon["level"] = mcs scon["level"] = mcs
processLabel = scon.Get() processLabel = scon.Get()
scon, _ = NewContext(fileLabel) scon, _ = NewContext(fileLabel)

Some files were not shown because too many files have changed in this diff Show More