gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

info.go (8120B)


      1 package ebpf
      2 
      3 import (
      4 	"bufio"
      5 	"bytes"
      6 	"encoding/hex"
      7 	"errors"
      8 	"fmt"
      9 	"io"
     10 	"os"
     11 	"strings"
     12 	"syscall"
     13 	"time"
     14 	"unsafe"
     15 
     16 	"github.com/cilium/ebpf/asm"
     17 	"github.com/cilium/ebpf/btf"
     18 	"github.com/cilium/ebpf/internal"
     19 	"github.com/cilium/ebpf/internal/sys"
     20 	"github.com/cilium/ebpf/internal/unix"
     21 )
     22 
     23 // MapInfo describes a map.
     24 type MapInfo struct {
     25 	Type       MapType
     26 	id         MapID
     27 	KeySize    uint32
     28 	ValueSize  uint32
     29 	MaxEntries uint32
     30 	Flags      uint32
     31 	// Name as supplied by user space at load time. Available from 4.15.
     32 	Name string
     33 }
     34 
     35 func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) {
     36 	var info sys.MapInfo
     37 	err := sys.ObjInfo(fd, &info)
     38 	if errors.Is(err, syscall.EINVAL) {
     39 		return newMapInfoFromProc(fd)
     40 	}
     41 	if err != nil {
     42 		return nil, err
     43 	}
     44 
     45 	return &MapInfo{
     46 		MapType(info.Type),
     47 		MapID(info.Id),
     48 		info.KeySize,
     49 		info.ValueSize,
     50 		info.MaxEntries,
     51 		info.MapFlags,
     52 		unix.ByteSliceToString(info.Name[:]),
     53 	}, nil
     54 }
     55 
     56 func newMapInfoFromProc(fd *sys.FD) (*MapInfo, error) {
     57 	var mi MapInfo
     58 	err := scanFdInfo(fd, map[string]interface{}{
     59 		"map_type":    &mi.Type,
     60 		"key_size":    &mi.KeySize,
     61 		"value_size":  &mi.ValueSize,
     62 		"max_entries": &mi.MaxEntries,
     63 		"map_flags":   &mi.Flags,
     64 	})
     65 	if err != nil {
     66 		return nil, err
     67 	}
     68 	return &mi, nil
     69 }
     70 
     71 // ID returns the map ID.
     72 //
     73 // Available from 4.13.
     74 //
     75 // The bool return value indicates whether this optional field is available.
     76 func (mi *MapInfo) ID() (MapID, bool) {
     77 	return mi.id, mi.id > 0
     78 }
     79 
     80 // programStats holds statistics of a program.
     81 type programStats struct {
     82 	// Total accumulated runtime of the program ins ns.
     83 	runtime time.Duration
     84 	// Total number of times the program was called.
     85 	runCount uint64
     86 }
     87 
     88 // ProgramInfo describes a program.
     89 type ProgramInfo struct {
     90 	Type ProgramType
     91 	id   ProgramID
     92 	// Truncated hash of the BPF bytecode. Available from 4.13.
     93 	Tag string
     94 	// Name as supplied by user space at load time. Available from 4.15.
     95 	Name string
     96 
     97 	btf   btf.ID
     98 	stats *programStats
     99 
    100 	maps  []MapID
    101 	insns []byte
    102 }
    103 
    104 func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
    105 	var info sys.ProgInfo
    106 	err := sys.ObjInfo(fd, &info)
    107 	if errors.Is(err, syscall.EINVAL) {
    108 		return newProgramInfoFromProc(fd)
    109 	}
    110 	if err != nil {
    111 		return nil, err
    112 	}
    113 
    114 	pi := ProgramInfo{
    115 		Type: ProgramType(info.Type),
    116 		id:   ProgramID(info.Id),
    117 		Tag:  hex.EncodeToString(info.Tag[:]),
    118 		Name: unix.ByteSliceToString(info.Name[:]),
    119 		btf:  btf.ID(info.BtfId),
    120 		stats: &programStats{
    121 			runtime:  time.Duration(info.RunTimeNs),
    122 			runCount: info.RunCnt,
    123 		},
    124 	}
    125 
    126 	// Start with a clean struct for the second call, otherwise we may get EFAULT.
    127 	var info2 sys.ProgInfo
    128 
    129 	if info.NrMapIds > 0 {
    130 		pi.maps = make([]MapID, info.NrMapIds)
    131 		info2.NrMapIds = info.NrMapIds
    132 		info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0]))
    133 	}
    134 
    135 	if info.XlatedProgLen > 0 {
    136 		pi.insns = make([]byte, info.XlatedProgLen)
    137 		info2.XlatedProgLen = info.XlatedProgLen
    138 		info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns)
    139 	}
    140 
    141 	if info.NrMapIds > 0 || info.XlatedProgLen > 0 {
    142 		if err := sys.ObjInfo(fd, &info2); err != nil {
    143 			return nil, err
    144 		}
    145 	}
    146 
    147 	return &pi, nil
    148 }
    149 
    150 func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) {
    151 	var info ProgramInfo
    152 	err := scanFdInfo(fd, map[string]interface{}{
    153 		"prog_type": &info.Type,
    154 		"prog_tag":  &info.Tag,
    155 	})
    156 	if errors.Is(err, errMissingFields) {
    157 		return nil, &internal.UnsupportedFeatureError{
    158 			Name:           "reading program info from /proc/self/fdinfo",
    159 			MinimumVersion: internal.Version{4, 10, 0},
    160 		}
    161 	}
    162 	if err != nil {
    163 		return nil, err
    164 	}
    165 
    166 	return &info, nil
    167 }
    168 
    169 // ID returns the program ID.
    170 //
    171 // Available from 4.13.
    172 //
    173 // The bool return value indicates whether this optional field is available.
    174 func (pi *ProgramInfo) ID() (ProgramID, bool) {
    175 	return pi.id, pi.id > 0
    176 }
    177 
    178 // BTFID returns the BTF ID associated with the program.
    179 //
    180 // The ID is only valid as long as the associated program is kept alive.
    181 // Available from 5.0.
    182 //
    183 // The bool return value indicates whether this optional field is available and
    184 // populated. (The field may be available but not populated if the kernel
    185 // supports the field but the program was loaded without BTF information.)
    186 func (pi *ProgramInfo) BTFID() (btf.ID, bool) {
    187 	return pi.btf, pi.btf > 0
    188 }
    189 
    190 // RunCount returns the total number of times the program was called.
    191 //
    192 // Can return 0 if the collection of statistics is not enabled. See EnableStats().
    193 // The bool return value indicates whether this optional field is available.
    194 func (pi *ProgramInfo) RunCount() (uint64, bool) {
    195 	if pi.stats != nil {
    196 		return pi.stats.runCount, true
    197 	}
    198 	return 0, false
    199 }
    200 
    201 // Runtime returns the total accumulated runtime of the program.
    202 //
    203 // Can return 0 if the collection of statistics is not enabled. See EnableStats().
    204 // The bool return value indicates whether this optional field is available.
    205 func (pi *ProgramInfo) Runtime() (time.Duration, bool) {
    206 	if pi.stats != nil {
    207 		return pi.stats.runtime, true
    208 	}
    209 	return time.Duration(0), false
    210 }
    211 
    212 // Instructions returns the 'xlated' instruction stream of the program
    213 // after it has been verified and rewritten by the kernel. These instructions
    214 // cannot be loaded back into the kernel as-is, this is mainly used for
    215 // inspecting loaded programs for troubleshooting, dumping, etc.
    216 //
    217 // For example, map accesses are made to reference their kernel map IDs,
    218 // not the FDs they had when the program was inserted. Note that before
    219 // the introduction of bpf_insn_prepare_dump in kernel 4.16, xlated
    220 // instructions were not sanitized, making the output even less reusable
    221 // and less likely to round-trip or evaluate to the same program Tag.
    222 //
    223 // The first instruction is marked as a symbol using the Program's name.
    224 //
    225 // Available from 4.13. Requires CAP_BPF or equivalent.
    226 func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
    227 	// If the calling process is not BPF-capable or if the kernel doesn't
    228 	// support getting xlated instructions, the field will be zero.
    229 	if len(pi.insns) == 0 {
    230 		return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
    231 	}
    232 
    233 	r := bytes.NewReader(pi.insns)
    234 	var insns asm.Instructions
    235 	if err := insns.Unmarshal(r, internal.NativeEndian); err != nil {
    236 		return nil, fmt.Errorf("unmarshaling instructions: %w", err)
    237 	}
    238 
    239 	// Tag the first instruction with the name of the program, if available.
    240 	insns[0] = insns[0].WithSymbol(pi.Name)
    241 
    242 	return insns, nil
    243 }
    244 
    245 // MapIDs returns the maps related to the program.
    246 //
    247 // Available from 4.15.
    248 //
    249 // The bool return value indicates whether this optional field is available.
    250 func (pi *ProgramInfo) MapIDs() ([]MapID, bool) {
    251 	return pi.maps, pi.maps != nil
    252 }
    253 
    254 func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
    255 	fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int()))
    256 	if err != nil {
    257 		return err
    258 	}
    259 	defer fh.Close()
    260 
    261 	if err := scanFdInfoReader(fh, fields); err != nil {
    262 		return fmt.Errorf("%s: %w", fh.Name(), err)
    263 	}
    264 	return nil
    265 }
    266 
    267 var errMissingFields = errors.New("missing fields")
    268 
    269 func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
    270 	var (
    271 		scanner = bufio.NewScanner(r)
    272 		scanned int
    273 	)
    274 
    275 	for scanner.Scan() {
    276 		parts := strings.SplitN(scanner.Text(), "\t", 2)
    277 		if len(parts) != 2 {
    278 			continue
    279 		}
    280 
    281 		name := strings.TrimSuffix(parts[0], ":")
    282 		field, ok := fields[string(name)]
    283 		if !ok {
    284 			continue
    285 		}
    286 
    287 		if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 {
    288 			return fmt.Errorf("can't parse field %s: %v", name, err)
    289 		}
    290 
    291 		scanned++
    292 	}
    293 
    294 	if err := scanner.Err(); err != nil {
    295 		return err
    296 	}
    297 
    298 	if len(fields) > 0 && scanned == 0 {
    299 		return ErrNotSupported
    300 	}
    301 
    302 	if scanned != len(fields) {
    303 		return errMissingFields
    304 	}
    305 
    306 	return nil
    307 }
    308 
    309 // EnableStats starts the measuring of the runtime
    310 // and run counts of eBPF programs.
    311 //
    312 // Collecting statistics can have an impact on the performance.
    313 //
    314 // Requires at least 5.8.
    315 func EnableStats(which uint32) (io.Closer, error) {
    316 	fd, err := sys.EnableStats(&sys.EnableStatsAttr{
    317 		Type: which,
    318 	})
    319 	if err != nil {
    320 		return nil, err
    321 	}
    322 	return fd, nil
    323 }