| package ebpf |
| |
| import ( |
| "bufio" |
| "bytes" |
| "encoding/hex" |
| "errors" |
| "fmt" |
| "io" |
| "os" |
| "strings" |
| "syscall" |
| "time" |
| "unsafe" |
| |
| "github.com/cilium/ebpf/asm" |
| "github.com/cilium/ebpf/btf" |
| "github.com/cilium/ebpf/internal" |
| "github.com/cilium/ebpf/internal/sys" |
| "github.com/cilium/ebpf/internal/unix" |
| ) |
| |
| // MapInfo describes a map. |
| type MapInfo struct { |
| Type MapType |
| id MapID |
| KeySize uint32 |
| ValueSize uint32 |
| MaxEntries uint32 |
| Flags uint32 |
| // Name as supplied by user space at load time. Available from 4.15. |
| Name string |
| } |
| |
| func newMapInfoFromFd(fd *sys.FD) (*MapInfo, error) { |
| var info sys.MapInfo |
| err := sys.ObjInfo(fd, &info) |
| if errors.Is(err, syscall.EINVAL) { |
| return newMapInfoFromProc(fd) |
| } |
| if err != nil { |
| return nil, err |
| } |
| |
| return &MapInfo{ |
| MapType(info.Type), |
| MapID(info.Id), |
| info.KeySize, |
| info.ValueSize, |
| info.MaxEntries, |
| info.MapFlags, |
| unix.ByteSliceToString(info.Name[:]), |
| }, nil |
| } |
| |
| func newMapInfoFromProc(fd *sys.FD) (*MapInfo, error) { |
| var mi MapInfo |
| err := scanFdInfo(fd, map[string]interface{}{ |
| "map_type": &mi.Type, |
| "key_size": &mi.KeySize, |
| "value_size": &mi.ValueSize, |
| "max_entries": &mi.MaxEntries, |
| "map_flags": &mi.Flags, |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return &mi, nil |
| } |
| |
| // ID returns the map ID. |
| // |
| // Available from 4.13. |
| // |
| // The bool return value indicates whether this optional field is available. |
| func (mi *MapInfo) ID() (MapID, bool) { |
| return mi.id, mi.id > 0 |
| } |
| |
| // programStats holds statistics of a program. |
| type programStats struct { |
| // Total accumulated runtime of the program ins ns. |
| runtime time.Duration |
| // Total number of times the program was called. |
| runCount uint64 |
| } |
| |
| // ProgramInfo describes a program. |
| type ProgramInfo struct { |
| Type ProgramType |
| id ProgramID |
| // Truncated hash of the BPF bytecode. Available from 4.13. |
| Tag string |
| // Name as supplied by user space at load time. Available from 4.15. |
| Name string |
| |
| btf btf.ID |
| stats *programStats |
| |
| maps []MapID |
| insns []byte |
| } |
| |
| func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) { |
| var info sys.ProgInfo |
| err := sys.ObjInfo(fd, &info) |
| if errors.Is(err, syscall.EINVAL) { |
| return newProgramInfoFromProc(fd) |
| } |
| if err != nil { |
| return nil, err |
| } |
| |
| pi := ProgramInfo{ |
| Type: ProgramType(info.Type), |
| id: ProgramID(info.Id), |
| Tag: hex.EncodeToString(info.Tag[:]), |
| Name: unix.ByteSliceToString(info.Name[:]), |
| btf: btf.ID(info.BtfId), |
| stats: &programStats{ |
| runtime: time.Duration(info.RunTimeNs), |
| runCount: info.RunCnt, |
| }, |
| } |
| |
| // Start with a clean struct for the second call, otherwise we may get EFAULT. |
| var info2 sys.ProgInfo |
| |
| if info.NrMapIds > 0 { |
| pi.maps = make([]MapID, info.NrMapIds) |
| info2.NrMapIds = info.NrMapIds |
| info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0])) |
| } |
| |
| if info.XlatedProgLen > 0 { |
| pi.insns = make([]byte, info.XlatedProgLen) |
| info2.XlatedProgLen = info.XlatedProgLen |
| info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns) |
| } |
| |
| if info.NrMapIds > 0 || info.XlatedProgLen > 0 { |
| if err := sys.ObjInfo(fd, &info2); err != nil { |
| return nil, err |
| } |
| } |
| |
| return &pi, nil |
| } |
| |
| func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) { |
| var info ProgramInfo |
| err := scanFdInfo(fd, map[string]interface{}{ |
| "prog_type": &info.Type, |
| "prog_tag": &info.Tag, |
| }) |
| if errors.Is(err, errMissingFields) { |
| return nil, &internal.UnsupportedFeatureError{ |
| Name: "reading program info from /proc/self/fdinfo", |
| MinimumVersion: internal.Version{4, 10, 0}, |
| } |
| } |
| if err != nil { |
| return nil, err |
| } |
| |
| return &info, nil |
| } |
| |
| // ID returns the program ID. |
| // |
| // Available from 4.13. |
| // |
| // The bool return value indicates whether this optional field is available. |
| func (pi *ProgramInfo) ID() (ProgramID, bool) { |
| return pi.id, pi.id > 0 |
| } |
| |
| // BTFID returns the BTF ID associated with the program. |
| // |
| // The ID is only valid as long as the associated program is kept alive. |
| // Available from 5.0. |
| // |
| // The bool return value indicates whether this optional field is available and |
| // populated. (The field may be available but not populated if the kernel |
| // supports the field but the program was loaded without BTF information.) |
| func (pi *ProgramInfo) BTFID() (btf.ID, bool) { |
| return pi.btf, pi.btf > 0 |
| } |
| |
| // RunCount returns the total number of times the program was called. |
| // |
| // Can return 0 if the collection of statistics is not enabled. See EnableStats(). |
| // The bool return value indicates whether this optional field is available. |
| func (pi *ProgramInfo) RunCount() (uint64, bool) { |
| if pi.stats != nil { |
| return pi.stats.runCount, true |
| } |
| return 0, false |
| } |
| |
| // Runtime returns the total accumulated runtime of the program. |
| // |
| // Can return 0 if the collection of statistics is not enabled. See EnableStats(). |
| // The bool return value indicates whether this optional field is available. |
| func (pi *ProgramInfo) Runtime() (time.Duration, bool) { |
| if pi.stats != nil { |
| return pi.stats.runtime, true |
| } |
| return time.Duration(0), false |
| } |
| |
| // Instructions returns the 'xlated' instruction stream of the program |
| // after it has been verified and rewritten by the kernel. These instructions |
| // cannot be loaded back into the kernel as-is, this is mainly used for |
| // inspecting loaded programs for troubleshooting, dumping, etc. |
| // |
| // For example, map accesses are made to reference their kernel map IDs, |
| // not the FDs they had when the program was inserted. Note that before |
| // the introduction of bpf_insn_prepare_dump in kernel 4.16, xlated |
| // instructions were not sanitized, making the output even less reusable |
| // and less likely to round-trip or evaluate to the same program Tag. |
| // |
| // The first instruction is marked as a symbol using the Program's name. |
| // |
| // Available from 4.13. Requires CAP_BPF or equivalent. |
| func (pi *ProgramInfo) Instructions() (asm.Instructions, error) { |
| // If the calling process is not BPF-capable or if the kernel doesn't |
| // support getting xlated instructions, the field will be zero. |
| if len(pi.insns) == 0 { |
| return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported) |
| } |
| |
| r := bytes.NewReader(pi.insns) |
| var insns asm.Instructions |
| if err := insns.Unmarshal(r, internal.NativeEndian); err != nil { |
| return nil, fmt.Errorf("unmarshaling instructions: %w", err) |
| } |
| |
| // Tag the first instruction with the name of the program, if available. |
| insns[0] = insns[0].WithSymbol(pi.Name) |
| |
| return insns, nil |
| } |
| |
| // MapIDs returns the maps related to the program. |
| // |
| // Available from 4.15. |
| // |
| // The bool return value indicates whether this optional field is available. |
| func (pi *ProgramInfo) MapIDs() ([]MapID, bool) { |
| return pi.maps, pi.maps != nil |
| } |
| |
| func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error { |
| fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int())) |
| if err != nil { |
| return err |
| } |
| defer fh.Close() |
| |
| if err := scanFdInfoReader(fh, fields); err != nil { |
| return fmt.Errorf("%s: %w", fh.Name(), err) |
| } |
| return nil |
| } |
| |
| var errMissingFields = errors.New("missing fields") |
| |
| func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error { |
| var ( |
| scanner = bufio.NewScanner(r) |
| scanned int |
| ) |
| |
| for scanner.Scan() { |
| parts := strings.SplitN(scanner.Text(), "\t", 2) |
| if len(parts) != 2 { |
| continue |
| } |
| |
| name := strings.TrimSuffix(parts[0], ":") |
| field, ok := fields[string(name)] |
| if !ok { |
| continue |
| } |
| |
| if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 { |
| return fmt.Errorf("can't parse field %s: %v", name, err) |
| } |
| |
| scanned++ |
| } |
| |
| if err := scanner.Err(); err != nil { |
| return err |
| } |
| |
| if len(fields) > 0 && scanned == 0 { |
| return ErrNotSupported |
| } |
| |
| if scanned != len(fields) { |
| return errMissingFields |
| } |
| |
| return nil |
| } |
| |
| // EnableStats starts the measuring of the runtime |
| // and run counts of eBPF programs. |
| // |
| // Collecting statistics can have an impact on the performance. |
| // |
| // Requires at least 5.8. |
| func EnableStats(which uint32) (io.Closer, error) { |
| fd, err := sys.EnableStats(&sys.EnableStatsAttr{ |
| Type: which, |
| }) |
| if err != nil { |
| return nil, err |
| } |
| return fd, nil |
| } |