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 }