github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/info.go (about)

     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  		uint32(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  	createdByUID     uint32
    98  	haveCreatedByUID bool
    99  	btf              btf.ID
   100  	stats            *programStats
   101  
   102  	maps  []MapID
   103  	insns []byte
   104  
   105  	lineInfos    []byte
   106  	numLineInfos uint32
   107  	funcInfos    []byte
   108  	numFuncInfos uint32
   109  }
   110  
   111  func newProgramInfoFromFd(fd *sys.FD) (*ProgramInfo, error) {
   112  	var info sys.ProgInfo
   113  	err := sys.ObjInfo(fd, &info)
   114  	if errors.Is(err, syscall.EINVAL) {
   115  		return newProgramInfoFromProc(fd)
   116  	}
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	pi := ProgramInfo{
   122  		Type: ProgramType(info.Type),
   123  		id:   ProgramID(info.Id),
   124  		Tag:  hex.EncodeToString(info.Tag[:]),
   125  		Name: unix.ByteSliceToString(info.Name[:]),
   126  		btf:  btf.ID(info.BtfId),
   127  		stats: &programStats{
   128  			runtime:  time.Duration(info.RunTimeNs),
   129  			runCount: info.RunCnt,
   130  		},
   131  	}
   132  
   133  	// Start with a clean struct for the second call, otherwise we may get EFAULT.
   134  	var info2 sys.ProgInfo
   135  
   136  	makeSecondCall := false
   137  
   138  	if info.NrMapIds > 0 {
   139  		pi.maps = make([]MapID, info.NrMapIds)
   140  		info2.NrMapIds = info.NrMapIds
   141  		info2.MapIds = sys.NewPointer(unsafe.Pointer(&pi.maps[0]))
   142  		makeSecondCall = true
   143  	} else if haveProgramInfoMapIDs() == nil {
   144  		// This program really has no associated maps.
   145  		pi.maps = make([]MapID, 0)
   146  	} else {
   147  		// The kernel doesn't report associated maps.
   148  		pi.maps = nil
   149  	}
   150  
   151  	// createdByUID and NrMapIds were introduced in the same kernel version.
   152  	if pi.maps != nil {
   153  		pi.createdByUID = info.CreatedByUid
   154  		pi.haveCreatedByUID = true
   155  	}
   156  
   157  	if info.XlatedProgLen > 0 {
   158  		pi.insns = make([]byte, info.XlatedProgLen)
   159  		info2.XlatedProgLen = info.XlatedProgLen
   160  		info2.XlatedProgInsns = sys.NewSlicePointer(pi.insns)
   161  		makeSecondCall = true
   162  	}
   163  
   164  	if info.NrLineInfo > 0 {
   165  		pi.lineInfos = make([]byte, btf.LineInfoSize*info.NrLineInfo)
   166  		info2.LineInfo = sys.NewSlicePointer(pi.lineInfos)
   167  		info2.LineInfoRecSize = btf.LineInfoSize
   168  		info2.NrLineInfo = info.NrLineInfo
   169  		pi.numLineInfos = info.NrLineInfo
   170  		makeSecondCall = true
   171  	}
   172  
   173  	if info.NrFuncInfo > 0 {
   174  		pi.funcInfos = make([]byte, btf.FuncInfoSize*info.NrFuncInfo)
   175  		info2.FuncInfo = sys.NewSlicePointer(pi.funcInfos)
   176  		info2.FuncInfoRecSize = btf.FuncInfoSize
   177  		info2.NrFuncInfo = info.NrFuncInfo
   178  		pi.numFuncInfos = info.NrFuncInfo
   179  		makeSecondCall = true
   180  	}
   181  
   182  	if makeSecondCall {
   183  		if err := sys.ObjInfo(fd, &info2); err != nil {
   184  			return nil, err
   185  		}
   186  	}
   187  
   188  	return &pi, nil
   189  }
   190  
   191  func newProgramInfoFromProc(fd *sys.FD) (*ProgramInfo, error) {
   192  	var info ProgramInfo
   193  	err := scanFdInfo(fd, map[string]interface{}{
   194  		"prog_type": &info.Type,
   195  		"prog_tag":  &info.Tag,
   196  	})
   197  	if errors.Is(err, errMissingFields) {
   198  		return nil, &internal.UnsupportedFeatureError{
   199  			Name:           "reading program info from /proc/self/fdinfo",
   200  			MinimumVersion: internal.Version{4, 10, 0},
   201  		}
   202  	}
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	return &info, nil
   208  }
   209  
   210  // ID returns the program ID.
   211  //
   212  // Available from 4.13.
   213  //
   214  // The bool return value indicates whether this optional field is available.
   215  func (pi *ProgramInfo) ID() (ProgramID, bool) {
   216  	return pi.id, pi.id > 0
   217  }
   218  
   219  // CreatedByUID returns the Uid that created the program.
   220  //
   221  // Available from 4.15.
   222  //
   223  // The bool return value indicates whether this optional field is available.
   224  func (pi *ProgramInfo) CreatedByUID() (uint32, bool) {
   225  	return pi.createdByUID, pi.haveCreatedByUID
   226  }
   227  
   228  // BTFID returns the BTF ID associated with the program.
   229  //
   230  // The ID is only valid as long as the associated program is kept alive.
   231  // Available from 5.0.
   232  //
   233  // The bool return value indicates whether this optional field is available and
   234  // populated. (The field may be available but not populated if the kernel
   235  // supports the field but the program was loaded without BTF information.)
   236  func (pi *ProgramInfo) BTFID() (btf.ID, bool) {
   237  	return pi.btf, pi.btf > 0
   238  }
   239  
   240  // RunCount returns the total number of times the program was called.
   241  //
   242  // Can return 0 if the collection of statistics is not enabled. See EnableStats().
   243  // The bool return value indicates whether this optional field is available.
   244  func (pi *ProgramInfo) RunCount() (uint64, bool) {
   245  	if pi.stats != nil {
   246  		return pi.stats.runCount, true
   247  	}
   248  	return 0, false
   249  }
   250  
   251  // Runtime returns the total accumulated runtime of the program.
   252  //
   253  // Can return 0 if the collection of statistics is not enabled. See EnableStats().
   254  // The bool return value indicates whether this optional field is available.
   255  func (pi *ProgramInfo) Runtime() (time.Duration, bool) {
   256  	if pi.stats != nil {
   257  		return pi.stats.runtime, true
   258  	}
   259  	return time.Duration(0), false
   260  }
   261  
   262  // Instructions returns the 'xlated' instruction stream of the program
   263  // after it has been verified and rewritten by the kernel. These instructions
   264  // cannot be loaded back into the kernel as-is, this is mainly used for
   265  // inspecting loaded programs for troubleshooting, dumping, etc.
   266  //
   267  // For example, map accesses are made to reference their kernel map IDs,
   268  // not the FDs they had when the program was inserted. Note that before
   269  // the introduction of bpf_insn_prepare_dump in kernel 4.16, xlated
   270  // instructions were not sanitized, making the output even less reusable
   271  // and less likely to round-trip or evaluate to the same program Tag.
   272  //
   273  // The first instruction is marked as a symbol using the Program's name.
   274  //
   275  // If available, the instructions will be annotated with metadata from the
   276  // BTF. This includes line information and function information. Reading
   277  // this metadata requires CAP_SYS_ADMIN or equivalent. If capability is
   278  // unavailable, the instructions will be returned without metadata.
   279  //
   280  // Available from 4.13. Requires CAP_BPF or equivalent for plain instructions.
   281  // Requires CAP_SYS_ADMIN for instructions with metadata.
   282  func (pi *ProgramInfo) Instructions() (asm.Instructions, error) {
   283  	// If the calling process is not BPF-capable or if the kernel doesn't
   284  	// support getting xlated instructions, the field will be zero.
   285  	if len(pi.insns) == 0 {
   286  		return nil, fmt.Errorf("insufficient permissions or unsupported kernel: %w", ErrNotSupported)
   287  	}
   288  
   289  	r := bytes.NewReader(pi.insns)
   290  	var insns asm.Instructions
   291  	if err := insns.Unmarshal(r, internal.NativeEndian); err != nil {
   292  		return nil, fmt.Errorf("unmarshaling instructions: %w", err)
   293  	}
   294  
   295  	if pi.btf != 0 {
   296  		btfh, err := btf.NewHandleFromID(pi.btf)
   297  		if err != nil {
   298  			// Getting a BTF handle requires CAP_SYS_ADMIN, if not available we get an -EPERM.
   299  			// Ignore it and fall back to instructions without metadata.
   300  			if !errors.Is(err, unix.EPERM) {
   301  				return nil, fmt.Errorf("unable to get BTF handle: %w", err)
   302  			}
   303  		}
   304  
   305  		// If we have a BTF handle, we can use it to assign metadata to the instructions.
   306  		if btfh != nil {
   307  			defer btfh.Close()
   308  
   309  			spec, err := btfh.Spec(nil)
   310  			if err != nil {
   311  				return nil, fmt.Errorf("unable to get BTF spec: %w", err)
   312  			}
   313  
   314  			lineInfos, err := btf.LoadLineInfos(
   315  				bytes.NewReader(pi.lineInfos),
   316  				internal.NativeEndian,
   317  				pi.numLineInfos,
   318  				spec,
   319  			)
   320  			if err != nil {
   321  				return nil, fmt.Errorf("parse line info: %w", err)
   322  			}
   323  
   324  			funcInfos, err := btf.LoadFuncInfos(
   325  				bytes.NewReader(pi.funcInfos),
   326  				internal.NativeEndian,
   327  				pi.numFuncInfos,
   328  				spec,
   329  			)
   330  			if err != nil {
   331  				return nil, fmt.Errorf("parse func info: %w", err)
   332  			}
   333  
   334  			btf.AssignMetadataToInstructions(insns, funcInfos, lineInfos, btf.CORERelocationInfos{})
   335  		}
   336  	}
   337  
   338  	fn := btf.FuncMetadata(&insns[0])
   339  	name := pi.Name
   340  	if fn != nil {
   341  		name = fn.Name
   342  	}
   343  	insns[0] = insns[0].WithSymbol(name)
   344  
   345  	return insns, nil
   346  }
   347  
   348  // MapIDs returns the maps related to the program.
   349  //
   350  // Available from 4.15.
   351  //
   352  // The bool return value indicates whether this optional field is available.
   353  func (pi *ProgramInfo) MapIDs() ([]MapID, bool) {
   354  	return pi.maps, pi.maps != nil
   355  }
   356  
   357  func scanFdInfo(fd *sys.FD, fields map[string]interface{}) error {
   358  	fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", fd.Int()))
   359  	if err != nil {
   360  		return err
   361  	}
   362  	defer fh.Close()
   363  
   364  	if err := scanFdInfoReader(fh, fields); err != nil {
   365  		return fmt.Errorf("%s: %w", fh.Name(), err)
   366  	}
   367  	return nil
   368  }
   369  
   370  var errMissingFields = errors.New("missing fields")
   371  
   372  func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error {
   373  	var (
   374  		scanner = bufio.NewScanner(r)
   375  		scanned int
   376  	)
   377  
   378  	for scanner.Scan() {
   379  		parts := strings.SplitN(scanner.Text(), "\t", 2)
   380  		if len(parts) != 2 {
   381  			continue
   382  		}
   383  
   384  		name := strings.TrimSuffix(parts[0], ":")
   385  		field, ok := fields[string(name)]
   386  		if !ok {
   387  			continue
   388  		}
   389  
   390  		if n, err := fmt.Sscanln(parts[1], field); err != nil || n != 1 {
   391  			return fmt.Errorf("can't parse field %s: %v", name, err)
   392  		}
   393  
   394  		scanned++
   395  	}
   396  
   397  	if err := scanner.Err(); err != nil {
   398  		return err
   399  	}
   400  
   401  	if len(fields) > 0 && scanned == 0 {
   402  		return ErrNotSupported
   403  	}
   404  
   405  	if scanned != len(fields) {
   406  		return errMissingFields
   407  	}
   408  
   409  	return nil
   410  }
   411  
   412  // EnableStats starts the measuring of the runtime
   413  // and run counts of eBPF programs.
   414  //
   415  // Collecting statistics can have an impact on the performance.
   416  //
   417  // Requires at least 5.8.
   418  func EnableStats(which uint32) (io.Closer, error) {
   419  	fd, err := sys.EnableStats(&sys.EnableStatsAttr{
   420  		Type: which,
   421  	})
   422  	if err != nil {
   423  		return nil, err
   424  	}
   425  	return fd, nil
   426  }
   427  
   428  var haveProgramInfoMapIDs = internal.NewFeatureTest("map IDs in program info", "4.15", func() error {
   429  	prog, err := progLoad(asm.Instructions{
   430  		asm.LoadImm(asm.R0, 0, asm.DWord),
   431  		asm.Return(),
   432  	}, SocketFilter, "MIT")
   433  	if err != nil {
   434  		return err
   435  	}
   436  	defer prog.Close()
   437  
   438  	err = sys.ObjInfo(prog, &sys.ProgInfo{
   439  		// NB: Don't need to allocate MapIds since the program isn't using
   440  		// any maps.
   441  		NrMapIds: 1,
   442  	})
   443  	if errors.Is(err, unix.EINVAL) {
   444  		// Most likely the syscall doesn't exist.
   445  		return internal.ErrNotSupported
   446  	}
   447  	if errors.Is(err, unix.E2BIG) {
   448  		// We've hit check_uarg_tail_zero on older kernels.
   449  		return internal.ErrNotSupported
   450  	}
   451  
   452  	return err
   453  })