github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/program_abstract.go (about)

     1  package gobpfld
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"syscall"
     7  	"unsafe"
     8  
     9  	"github.com/dylandreimerink/gobpfld/bpfsys"
    10  	"github.com/dylandreimerink/gobpfld/bpftypes"
    11  	"github.com/dylandreimerink/gobpfld/ebpf"
    12  	"github.com/dylandreimerink/gobpfld/internal/cstr"
    13  	bpfSyscall "github.com/dylandreimerink/gobpfld/internal/syscall"
    14  	"github.com/dylandreimerink/gobpfld/kernelsupport"
    15  )
    16  
    17  type AbstractBPFProgram struct {
    18  	// The program type as which it was loaded into the kernel
    19  	ProgramType bpftypes.BPFProgType
    20  	// Name of the program
    21  	Name    ObjName
    22  	License string
    23  	// The actual instructions of the program
    24  	Instructions []ebpf.RawInstruction
    25  	// Locations where map fds need to be inserted into the
    26  	// program before loading
    27  	MapFDLocations map[string][]uint64
    28  	Maps           map[string]BPFMap
    29  
    30  	BTF      *BTF
    31  	BTFLines []BTFKernelLine
    32  	BTFFuncs []BTFKernelFunc
    33  
    34  	// Indicates if the program is already loaded into the kernel
    35  	loaded bool
    36  	// The file descriptor of the program assigned by the kernel
    37  	fd bpfsys.BPFfd
    38  }
    39  
    40  const defaultBPFVerifierLogSize = 1 * 1024 * 1024 // 1MB
    41  
    42  func (p *AbstractBPFProgram) Fd() (bpfsys.BPFfd, error) {
    43  	if !p.loaded {
    44  		return 0, fmt.Errorf("program is not loaded")
    45  	}
    46  
    47  	return p.fd, nil
    48  }
    49  
    50  func (p AbstractBPFProgram) GetAbstractProgram() AbstractBPFProgram {
    51  	return p
    52  }
    53  
    54  func (p *AbstractBPFProgram) load(attr bpfsys.BPFAttrProgramLoad) (log string, err error) {
    55  	if p.ProgramType == bpftypes.BPF_PROG_TYPE_UNSPEC {
    56  		return "", fmt.Errorf("program type unspecified")
    57  	}
    58  
    59  	// If the given program type is not supported by the current kernel version
    60  	// return a verbose error instead of a syscall error
    61  	kernProgFeat, found := progTypeToKFeature[p.ProgramType]
    62  	// If there is no feature defined for a type, assume it is always supported
    63  	if found {
    64  		if !kernelsupport.CurrentFeatures.Program.Has(kernProgFeat) {
    65  			return "", fmt.Errorf(
    66  				"program type '%s' not supported: %w",
    67  				p.ProgramType,
    68  				bpfsys.ErrNotSupported,
    69  			)
    70  		}
    71  	}
    72  
    73  	// TODO validate attach types. In order to use some map types, features or helpers the
    74  	// proper attach type must be specified at program loadtime, we can attempt to detect this
    75  	// requirement based on the linked maps and decompiling the program.
    76  
    77  	// TODO validate of used attach type is supported by current kernel version
    78  
    79  	// TODO check if helper functions used in program are supported by current kernel version
    80  
    81  	licenseCStr := cstr.StringToCStrBytes(p.License)
    82  
    83  	// Rewrite / patch instructions with map fds
    84  	for mapName, offsets := range p.MapFDLocations {
    85  		bpfMap, found := p.Maps[mapName]
    86  		if !found {
    87  			return "", fmt.Errorf("program requires unknown map '%s'", mapName)
    88  		}
    89  
    90  		// if the map is not yet loaded, load it now
    91  		if !bpfMap.IsLoaded() {
    92  			err = bpfMap.Load()
    93  			if err != nil {
    94  				return "", fmt.Errorf("error while loading map '%s': %w", mapName, err)
    95  			}
    96  		}
    97  
    98  		// For every location the program needs the map fd, insert it
    99  		for _, offset := range offsets {
   100  			instIndex := offset / uint64(ebpf.BPFInstSize)
   101  			inst := &p.Instructions[instIndex]
   102  
   103  			// BPF_PSEUDO_MAP_FD_VALUE is set if this is an access into a global data section.
   104  			// In this case, imm of the first inst contains the offset which must be moved to the second inst
   105  			if inst.GetSourceReg() == ebpf.BPF_PSEUDO_MAP_FD_VALUE {
   106  				inst2 := &p.Instructions[instIndex+1]
   107  				inst2.Imm = inst.Imm
   108  			} else {
   109  				inst.SetSourceReg(ebpf.BPF_PSEUDO_MAP_FD)
   110  			}
   111  
   112  			inst.Imm = int32(bpfMap.GetFD())
   113  		}
   114  	}
   115  
   116  	// If we have BTF info and the kernel supports BTF loading
   117  	if p.BTF != nil && kernelsupport.CurrentFeatures.API.Has(kernelsupport.KFeatAPIBTFLoad) {
   118  		// Load BTF if not already loaded
   119  		if !p.BTF.loaded {
   120  			_, err = p.BTF.Load(BTFLoadOpts{
   121  				LogLevel: bpftypes.BPFLogLevelVerbose,
   122  			})
   123  			if err != nil {
   124  				// TODO make custom error type which includes the verifier log
   125  				return "", fmt.Errorf("load BTF: %w", err)
   126  			}
   127  		}
   128  
   129  		attr.ProgBTFFD, err = p.BTF.Fd()
   130  		if err != nil {
   131  			return "", fmt.Errorf("get BTF fd: %w", err)
   132  		}
   133  
   134  		if p.BTFLines != nil {
   135  			attr.LineInfo = uintptr(unsafe.Pointer(&p.BTFLines[0]))
   136  			attr.LineInfoCnt = uint32(len(p.BTFLines))
   137  			attr.LineInfoRecSize = uint32(BTFKernelLineSize)
   138  		}
   139  
   140  		if p.BTFFuncs != nil {
   141  			attr.FuncInfo = uintptr(unsafe.Pointer(&p.BTFFuncs[0]))
   142  			attr.FuncInfoCnt = uint32(len(p.BTFFuncs))
   143  			attr.FuncInfoRecSize = uint32(BTFKernelFuncSize)
   144  		}
   145  	}
   146  
   147  	// If undefined, use default
   148  	if attr.LogSize == 0 {
   149  		attr.LogSize = defaultBPFVerifierLogSize
   150  	}
   151  
   152  	verifierLogBytes := make([]byte, attr.LogSize)
   153  
   154  	attr.ProgramType = p.ProgramType
   155  	attr.InsnCnt = uint32(len(p.Instructions))
   156  	attr.Insns = uintptr(unsafe.Pointer(&p.Instructions[0]))
   157  	attr.License = uintptr(unsafe.Pointer(&licenseCStr[0]))
   158  	attr.LogBuf = uintptr(unsafe.Pointer(&verifierLogBytes[0]))
   159  	attr.ProgName = p.Name.GetCstr()
   160  
   161  	for i := 0; i < 5; i++ {
   162  		p.fd, err = bpfsys.LoadProgram(&attr)
   163  		if err != nil {
   164  			// EAGAIN basically means "there is no data available right now, try again later"
   165  			if sysErr, ok := err.(*bpfSyscall.Error); ok && sysErr.Errno == syscall.EAGAIN {
   166  				continue
   167  			}
   168  
   169  			return cstr.BytesToString(verifierLogBytes), fmt.Errorf("bpf syscall error: %w", err)
   170  		}
   171  
   172  		// We encountered no error, so stop trying to load the program
   173  		break
   174  	}
   175  	if err != nil {
   176  		return cstr.BytesToString(verifierLogBytes), fmt.Errorf("bpf syscall error: %w", err)
   177  	}
   178  
   179  	p.loaded = true
   180  
   181  	return cstr.BytesToString(verifierLogBytes), nil
   182  }
   183  
   184  var progTypeToKFeature = map[bpftypes.BPFProgType]kernelsupport.ProgramSupport{
   185  	bpftypes.BPF_PROG_TYPE_SOCKET_FILTER:           kernelsupport.KFeatProgSocketFilter,
   186  	bpftypes.BPF_PROG_TYPE_KPROBE:                  kernelsupport.KFeatProgKProbe,
   187  	bpftypes.BPF_PROG_TYPE_SCHED_CLS:               kernelsupport.KFeatProgSchedCLS,
   188  	bpftypes.BPF_PROG_TYPE_SCHED_ACT:               kernelsupport.KFeatProgSchedACT,
   189  	bpftypes.BPF_PROG_TYPE_TRACEPOINT:              kernelsupport.KFeatProgTracepoint,
   190  	bpftypes.BPF_PROG_TYPE_XDP:                     kernelsupport.KFeatProgXDP,
   191  	bpftypes.BPF_PROG_TYPE_PERF_EVENT:              kernelsupport.KFeatProgPerfEvent,
   192  	bpftypes.BPF_PROG_TYPE_CGROUP_SKB:              kernelsupport.KFeatProgCGroupSKB,
   193  	bpftypes.BPF_PROG_TYPE_CGROUP_SOCK:             kernelsupport.KFeatProgCGroupSocket,
   194  	bpftypes.BPF_PROG_TYPE_LWT_IN:                  kernelsupport.KFeatProgLWTIn,
   195  	bpftypes.BPF_PROG_TYPE_LWT_OUT:                 kernelsupport.KFeatProgLWTOut,
   196  	bpftypes.BPF_PROG_TYPE_LWT_XMIT:                kernelsupport.KFeatProgLWTXmit,
   197  	bpftypes.BPF_PROG_TYPE_SOCK_OPS:                kernelsupport.KFeatProgSocketOps,
   198  	bpftypes.BPF_PROG_TYPE_SK_SKB:                  kernelsupport.KFeatProgSKSKB,
   199  	bpftypes.BPF_PROG_TYPE_CGROUP_DEVICE:           kernelsupport.KFeatProgCGroupDevice,
   200  	bpftypes.BPF_PROG_TYPE_SK_MSG:                  kernelsupport.KFeatProgSKMsg,
   201  	bpftypes.BPF_PROG_TYPE_RAW_TRACEPOINT:          kernelsupport.KFeatProgRawTracepoint,
   202  	bpftypes.BPF_PROG_TYPE_CGROUP_SOCK_ADDR:        kernelsupport.KFeatProgCGroupSocketAddr,
   203  	bpftypes.BPF_PROG_TYPE_LWT_SEG6LOCAL:           kernelsupport.KFeatProgLWTSeg6Local,
   204  	bpftypes.BPF_PROG_TYPE_LIRC_MODE2:              kernelsupport.KFeatProgLIRCMode2,
   205  	bpftypes.BPF_PROG_TYPE_SK_REUSEPORT:            kernelsupport.KFeatProgSKReusePort,
   206  	bpftypes.BPF_PROG_TYPE_FLOW_DISSECTOR:          kernelsupport.KFeatProgFlowDissector,
   207  	bpftypes.BPF_PROG_TYPE_CGROUP_SYSCTL:           kernelsupport.KFeatProgCGroupSysctl,
   208  	bpftypes.BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE: kernelsupport.KFeatProgRawTracepointWritable,
   209  	bpftypes.BPF_PROG_TYPE_CGROUP_SOCKOPT:          kernelsupport.KFeatProgCgroupSocketOpt,
   210  	bpftypes.BPF_PROG_TYPE_TRACING:                 kernelsupport.KFeatProgTracing,
   211  	bpftypes.BPF_PROG_TYPE_STRUCT_OPS:              kernelsupport.KFeatProgStructOps,
   212  	bpftypes.BPF_PROG_TYPE_EXT:                     kernelsupport.KFeatProgExt,
   213  	bpftypes.BPF_PROG_TYPE_LSM:                     kernelsupport.KFeatProgLSM,
   214  	bpftypes.BPF_PROG_TYPE_SK_LOOKUP:               kernelsupport.KFeatProgSKLookup,
   215  }
   216  
   217  // Pin pins the program to a location in the bpf filesystem, since the file system now also holds a reference
   218  // to the program, the original creator of the program can terminate without triggering the program to be
   219  // closed as well. A program can be unpinned from the bpf FS by another process thus transferring it or persisting
   220  // it across multiple runs of the same program.
   221  func (p *AbstractBPFProgram) Pin(relativePath string) error {
   222  	if !p.loaded {
   223  		return fmt.Errorf("can't pin an unloaded program")
   224  	}
   225  
   226  	return PinFD(relativePath, p.fd)
   227  }
   228  
   229  // Unpin captures the file descriptor of the program at the given 'relativePath' from the kernel.
   230  // If 'deletePin' is true the bpf FS pin will be removed after successfully loading the program, thus transferring
   231  // ownership of the program in a scenario where the program is not shared between multiple userspace programs.
   232  // Otherwise the pin will keep existing which will cause the map to not be deleted when this program exits.
   233  func (p *AbstractBPFProgram) unpin(relativePath string, deletePin bool) error {
   234  	if p.loaded {
   235  		return fmt.Errorf("can't unpin an loaded program")
   236  	}
   237  
   238  	var err error
   239  	p.fd, err = UnpinFD(relativePath, deletePin)
   240  	if err != nil {
   241  		return fmt.Errorf("unpin error: %w", err)
   242  	}
   243  
   244  	progInfo, err := GetProgramInfo(p.fd)
   245  	if err != nil {
   246  		return fmt.Errorf("get prog info: %w", err)
   247  	}
   248  
   249  	p.Name = progInfo.Name
   250  
   251  	p.License = "Not GPL compatible"
   252  	if progInfo.Flags&bpftypes.ProgInfoFlagGPLCompatible > 0 {
   253  		// This is technically incorrect, but since there is no way to interrogate the kernel for the exact license
   254  		// this is the only way to ensure that after reloading the program the kernel recognizes the program as
   255  		// GPL compatible.
   256  		p.License = "GPL"
   257  	}
   258  
   259  	p.Instructions = progInfo.XlatedProgInsns
   260  
   261  	p.Maps = make(map[string]BPFMap, len(progInfo.MapIDs))
   262  	for _, mapID := range progInfo.MapIDs {
   263  		bpfMap, err := MapFromID(mapID)
   264  		if err != nil {
   265  			return fmt.Errorf("map from id: %w", err)
   266  		}
   267  		p.Maps[bpfMap.GetName().str] = bpfMap
   268  	}
   269  
   270  	p.loaded = true
   271  	p.ProgramType = progInfo.Type
   272  
   273  	return nil
   274  }
   275  
   276  // DecodeToReader decodes the eBPF program and writes the human readable format to the provided w.
   277  // The output that is generated is inspired by the llvm-objdump -S output format of eBPF programs
   278  func (p *AbstractBPFProgram) DecodeToReader(w io.Writer) error {
   279  	decoded, err := ebpf.Decode(p.Instructions)
   280  	if err != nil {
   281  		return fmt.Errorf("error while decoding program: %w", err)
   282  	}
   283  
   284  	// The eBPF program has no lables, just offsets within the program.
   285  	// Since those are hard to interpret over long distance jumps we add
   286  	// fake labels called LBLxx, since jumps can occur backwards we will
   287  	// first need to loop over the program to calculate labels and label
   288  	// references.
   289  	labelIndex := 0
   290  	labels := map[int]string{}
   291  	labelRefs := map[int]string{}
   292  	for i, inst := range p.Instructions {
   293  		// We are only interested in the jump class of opcodes
   294  		class := inst.Op & 0b111
   295  		if class != ebpf.BPF_JMP && class != ebpf.BPF_JMP32 {
   296  			continue
   297  		}
   298  
   299  		// The offset of the jump
   300  		offset := int(inst.Off)
   301  
   302  		op := inst.Op & 0xF0
   303  
   304  		// Helper function calls don't need labels, but BPF to BPF calls do
   305  		if op == ebpf.BPF_CALL {
   306  			if inst.GetSourceReg() != ebpf.PSEUDO_CALL {
   307  				continue
   308  			}
   309  
   310  			// If we have a BPF to BPF call, the imm is the offset used, not the
   311  			// actual offset of the instruction
   312  			offset = int(inst.Imm)
   313  		}
   314  
   315  		// Ignore exit "jumps", they don't need labels
   316  		if op == ebpf.BPF_EXIT {
   317  			continue
   318  		}
   319  
   320  		// Multiple jumps can reference the same address
   321  		// so check if a label already exists for the target address.
   322  		label := labels[i+offset+1]
   323  		if label == "" {
   324  			// If not, create one
   325  			label = fmt.Sprintf("LBL%d", labelIndex)
   326  			labels[i+offset+1] = label
   327  			labelIndex++
   328  		}
   329  
   330  		labelRefs[i] = label
   331  	}
   332  
   333  	for i, inst := range decoded {
   334  		labelRef := labelRefs[i]
   335  		label := labels[i]
   336  		raw := p.Instructions[i]
   337  
   338  		// If this address has a label, print it first
   339  		if label != "" {
   340  			fmt.Fprintf(w, "%s:\n", label)
   341  		}
   342  
   343  		// print the instruction number with 8 chars padding, should be more than enough
   344  		// since the max program size is 131072 at the moment.
   345  		//
   346  		// Print the raw instruction as hex and then the human readable translation
   347  		fmt.Fprintf(w, "%8d:   %02x %02x %02x %02x %02x %02x %02x %02x   %s",
   348  			i,
   349  			raw.Op,
   350  			raw.Reg,
   351  			(raw.Off>>8)&0xFF,
   352  			raw.Off&0xFF,
   353  			(raw.Imm>>24)&0xFF,
   354  			(raw.Imm>>16)&0xFF,
   355  			(raw.Imm>>8)&0xFF,
   356  			raw.Imm&0xFF,
   357  			inst,
   358  		)
   359  
   360  		if labelRef == "" {
   361  			fmt.Fprint(w, "\n")
   362  		} else {
   363  			// If this instruction references another row, append it to the end
   364  			fmt.Fprint(w, " <", labelRef, ">\n")
   365  		}
   366  	}
   367  
   368  	return nil
   369  }