diff --git a/abi.go b/abi.go index b164fab33..999b8cc85 100644 --- a/abi.go +++ b/abi.go @@ -4,6 +4,55 @@ import ( "github.com/pkg/errors" ) +// CollectionABI describes the interface of an eBPF collection. +type CollectionABI struct { + Maps map[string]*MapABI + Programs map[string]*ProgramABI +} + +// CheckSpec verifies that all maps and programs mentioned +// in the ABI are present in the spec. +func (abi *CollectionABI) CheckSpec(cs *CollectionSpec) error { + for name := range abi.Maps { + if cs.Maps[name] == nil { + return errors.Errorf("missing map %s", name) + } + } + + for name := range abi.Programs { + if cs.Programs[name] == nil { + return errors.Errorf("missing program %s", name) + } + } + + return nil +} + +// Check verifies that all items in a collection conform to this ABI. +func (abi *CollectionABI) Check(coll *Collection) error { + for name, mapABI := range abi.Maps { + m := coll.Maps[name] + if m == nil { + return errors.Errorf("missing map %s", name) + } + if err := mapABI.Check(m); err != nil { + return errors.Wrapf(err, "map %s", name) + } + } + + for name, progABI := range abi.Programs { + p := coll.Programs[name] + if p == nil { + return errors.Errorf("missing program %s", name) + } + if err := progABI.Check(p); err != nil { + return errors.Wrapf(err, "program %s", name) + } + } + + return nil +} + // MapABI describes a Map. // // Use it to assert that a Map matches what your code expects. diff --git a/abi_test.go b/abi_test.go index 2a91d7657..1e9d2f327 100644 --- a/abi_test.go +++ b/abi_test.go @@ -4,6 +4,68 @@ import ( "testing" ) +func TestCollectionABI(t *testing.T) { + cabi := &CollectionABI{ + Maps: map[string]*MapABI{ + "a": { + Type: ArrayOfMaps, + KeySize: 4, + ValueSize: 2, + MaxEntries: 3, + InnerMap: &MapABI{ + Type: Array, + }, + }, + }, + Programs: map[string]*ProgramABI{ + "1": {Type: SocketFilter}, + }, + } + + if err := cabi.CheckSpec(abiFixtureCollectionSpec()); err != nil { + t.Error("ABI check found error:", err) + } + + cs := abiFixtureCollectionSpec() + delete(cs.Maps, "a") + if err := cabi.CheckSpec(cs); err == nil { + t.Error("Did not detect missing map") + } + + cs = abiFixtureCollectionSpec() + delete(cs.Programs, "1") + if err := cabi.CheckSpec(cs); err == nil { + t.Error("Did not detect missing program") + } + + if err := cabi.Check(abiFixtureCollection()); err != nil { + t.Error("ABI check found error:", err) + + } + + coll := abiFixtureCollection() + coll.Maps["a"].abi.KeySize = 12 + if err := cabi.Check(coll); err == nil { + t.Error("Check not check map ABI") + } + + delete(coll.Maps, "a") + if err := cabi.Check(coll); err == nil { + t.Error("Check did not detect missing map") + } + + coll = abiFixtureCollection() + coll.Programs["1"].abi.Type = TracePoint + if err := cabi.Check(coll); err == nil { + t.Error("Did not check program ABI") + } + + delete(coll.Programs, "1") + if err := cabi.Check(coll); err == nil { + t.Error("Check did not detect missing program") + } +} + func TestMapABI(t *testing.T) { mabi := &MapABI{ Type: ArrayOfMaps, @@ -70,6 +132,28 @@ func TestProgramABI(t *testing.T) { } } +func abiFixtureCollectionSpec() *CollectionSpec { + return &CollectionSpec{ + Maps: map[string]*MapSpec{ + "a": abiFixtureMapSpec(), + }, + Programs: map[string]*ProgramSpec{ + "1": abiFixtureProgramSpec(), + }, + } +} + +func abiFixtureCollection() *Collection { + return &Collection{ + Maps: map[string]*Map{ + "a": abiFixtureMap(), + }, + Programs: map[string]*Program{ + "1": abiFixtureProgram(), + }, + } +} + func abiFixtureMapSpec() *MapSpec { return &MapSpec{ Type: ArrayOfMaps, @@ -100,3 +184,43 @@ func abiFixtureProgram() *Program { abi: *newProgramABIFromSpec(abiFixtureProgramSpec()), } } + +func ExampleCollectionABI() { + abi := CollectionABI{ + Maps: map[string]*MapABI{ + "a": { + Type: Array, + // Members which aren't specified are not checked + }, + // Use an empty ABI if you just want to make sure + // something is present. + "b": {}, + }, + Programs: map[string]*ProgramABI{ + "1": {Type: XDP}, + }, + } + + spec, err := LoadCollectionSpec("from-somewhere.elf") + if err != nil { + panic(err) + } + + // CheckSpec only makes sure that all entries of the ABI + // are present. It doesn't check whether the ABI is correct. + // See below for how to do that. + if err := abi.CheckSpec(spec); err != nil { + panic(err) + } + + coll, err := NewCollection(spec) + if err != nil { + panic(err) + } + + // Check finally compares the ABI and the collection, and + // makes sure that they match. + if err := abi.Check(coll); err != nil { + panic(err) + } +} diff --git a/collection.go b/collection.go new file mode 100644 index 000000000..91be868aa --- /dev/null +++ b/collection.go @@ -0,0 +1,275 @@ +package ebpf + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +// CollectionOptions control loading a collection into the kernel. +type CollectionOptions struct { + Programs ProgramOptions +} + +// CollectionSpec describes a collection. +type CollectionSpec struct { + Maps map[string]*MapSpec + Programs map[string]*ProgramSpec +} + +// Copy returns a recursive copy of the spec. +func (cs *CollectionSpec) Copy() *CollectionSpec { + if cs == nil { + return nil + } + + cpy := CollectionSpec{ + Maps: make(map[string]*MapSpec, len(cs.Maps)), + Programs: make(map[string]*ProgramSpec, len(cs.Programs)), + } + + for name, spec := range cs.Maps { + cpy.Maps[name] = spec.Copy() + } + + for name, spec := range cs.Programs { + cpy.Programs[name] = spec.Copy() + } + + return &cpy +} + +// LoadCollectionSpec parse an object file and convert it to a collection +func LoadCollectionSpec(file string) (*CollectionSpec, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + defer f.Close() + + return LoadCollectionSpecFromReader(f) +} + +// Collection is a collection of Programs and Maps associated +// with their symbols +type Collection struct { + Programs map[string]*Program + Maps map[string]*Map +} + +// NewCollection creates a Collection from a specification. +// +// Only maps referenced by at least one of the programs are initialized. +func NewCollection(spec *CollectionSpec) (*Collection, error) { + return NewCollectionWithOptions(spec, CollectionOptions{}) +} + +// NewCollectionWithOptions creates a Collection from a specification. +// +// Only maps referenced by at least one of the programs are initialized. +func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) { + maps := make(map[string]*Map) + for mapName, mapSpec := range spec.Maps { + m, err := NewMap(mapSpec) + if err != nil { + return nil, errors.Wrapf(err, "map %s", mapName) + } + maps[mapName] = m + } + + progs := make(map[string]*Program) + for progName, origProgSpec := range spec.Programs { + progSpec := origProgSpec.Copy() + editor := Edit(&progSpec.Instructions) + + // Rewrite any Symbol which is a valid Map. + for sym := range editor.ReferenceOffsets { + m, ok := maps[sym] + if !ok { + continue + } + + // don't overwrite maps already rewritten, users can rewrite programs in the spec themselves + if err := editor.rewriteMap(sym, m, false); err != nil { + return nil, errors.Wrapf(err, "program %s", progName) + } + } + + prog, err := NewProgramWithOptions(progSpec, opts.Programs) + if err != nil { + return nil, errors.Wrapf(err, "program %s", progName) + } + progs[progName] = prog + } + + return &Collection{ + progs, + maps, + }, nil +} + +// LoadCollection parses an object file and converts it to a collection. +func LoadCollection(file string) (*Collection, error) { + spec, err := LoadCollectionSpec(file) + if err != nil { + return nil, err + } + return NewCollection(spec) +} + +// Close frees all maps and programs associated with the collection. +// +// The collection mustn't be used afterwards. +func (coll *Collection) Close() { + for _, prog := range coll.Programs { + prog.Close() + } + for _, m := range coll.Maps { + m.Close() + } +} + +// DetachMap removes the named map from the Collection. +// +// This means that a later call to Close() will not affect this map. +// +// Returns nil if no map of that name exists. +func (coll *Collection) DetachMap(name string) *Map { + m := coll.Maps[name] + delete(coll.Maps, name) + return m +} + +// DetachProgram removes the named program from the Collection. +// +// This means that a later call to Close() will not affect this program. +// +// Returns nil if no program of that name exists. +func (coll *Collection) DetachProgram(name string) *Program { + p := coll.Programs[name] + delete(coll.Programs, name) + return p +} + +// Pin persits a Collection beyond the lifetime of the process that created it +// +// This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional +func (coll *Collection) Pin(dirName string, fileMode os.FileMode) error { + err := mkdirIfNotExists(dirName, fileMode) + if err != nil { + return err + } + if len(coll.Maps) > 0 { + mapPath := filepath.Join(dirName, "maps") + err = mkdirIfNotExists(mapPath, fileMode) + if err != nil { + return err + } + for k, v := range coll.Maps { + err := v.Pin(filepath.Join(mapPath, k)) + if err != nil { + return errors.Wrapf(err, "map %s", k) + } + } + } + if len(coll.Programs) > 0 { + progPath := filepath.Join(dirName, "programs") + err = mkdirIfNotExists(progPath, fileMode) + if err != nil { + return err + } + for k, v := range coll.Programs { + err = v.Pin(filepath.Join(progPath, k)) + if err != nil { + return errors.Wrapf(err, "program %s", k) + } + } + } + return nil +} + +func mkdirIfNotExists(dirName string, fileMode os.FileMode) error { + _, err := os.Stat(dirName) + if err != nil && os.IsNotExist(err) { + err = os.Mkdir(dirName, fileMode) + } + if err != nil { + return err + } + return nil +} + +// LoadPinnedCollection loads a Collection from the pinned directory. +// +// Requires at least Linux 4.13, use LoadPinnedCollectionExplicit on +// earlier versions. +func LoadPinnedCollection(dirName string) (*Collection, error) { + return loadCollection( + dirName, + func(_ string, path string) (*Map, error) { + return LoadPinnedMap(path) + }, + func(_ string, path string) (*Program, error) { + return LoadPinnedProgram(path) + }, + ) +} + +// LoadPinnedCollectionExplicit loads a Collection from the pinned directory with explicit parameters. +func LoadPinnedCollectionExplicit(dirName string, maps map[string]*MapABI, progs map[string]*ProgramABI) (*Collection, error) { + return loadCollection( + dirName, + func(name string, path string) (*Map, error) { + return LoadPinnedMapExplicit(path, maps[name]) + }, + func(name string, path string) (*Program, error) { + return LoadPinnedProgramExplicit(path, progs[name]) + }, + ) +} + +func loadCollection(dirName string, loadMap func(string, string) (*Map, error), loadProgram func(string, string) (*Program, error)) (*Collection, error) { + maps, err := readFileNames(filepath.Join(dirName, "maps")) + if err != nil { + return nil, err + } + progs, err := readFileNames(filepath.Join(dirName, "programs")) + if err != nil { + return nil, err + } + bpfColl := &Collection{ + Maps: make(map[string]*Map), + Programs: make(map[string]*Program), + } + for _, mf := range maps { + name := filepath.Base(mf) + m, err := loadMap(name, mf) + if err != nil { + return nil, errors.Wrapf(err, "map %s", name) + } + bpfColl.Maps[name] = m + } + for _, pf := range progs { + name := filepath.Base(pf) + prog, err := loadProgram(name, pf) + if err != nil { + return nil, errors.Wrapf(err, "program %s", name) + } + bpfColl.Programs[name] = prog + } + return bpfColl, nil +} + +func readFileNames(dirName string) ([]string, error) { + var fileNames []string + files, err := ioutil.ReadDir(dirName) + if err != nil && err != os.ErrNotExist { + return nil, err + } + for _, fi := range files { + fileNames = append(fileNames, fi.Name()) + } + return fileNames, nil +} diff --git a/collection_test.go b/collection_test.go new file mode 100644 index 000000000..10e8a3ebc --- /dev/null +++ b/collection_test.go @@ -0,0 +1,155 @@ +package ebpf + +import ( + "testing" + + "github.com/cilium/ebpf/asm" +) + +func TestCollectionSpecNotModified(t *testing.T) { + cs := CollectionSpec{ + Maps: map[string]*MapSpec{ + "my-map": { + Type: Array, + KeySize: 4, + ValueSize: 4, + MaxEntries: 1, + }, + }, + Programs: map[string]*ProgramSpec{ + "test": { + Type: SocketFilter, + Instructions: asm.Instructions{ + asm.LoadMapPtr(asm.R1, 0), + asm.LoadImm(asm.R0, 0, asm.DWord), + asm.Return(), + }, + License: "MIT", + }, + }, + } + + cs.Programs["test"].Instructions[0].Reference = "my-map" + + coll, err := NewCollection(&cs) + if err != nil { + t.Fatal(err) + } + coll.Close() + + if cs.Programs["test"].Instructions[0].Constant != 0 { + t.Error("Creating a collection modifies input spec") + } +} + +func TestCollectionSpecCopy(t *testing.T) { + cs := &CollectionSpec{ + Maps: map[string]*MapSpec{ + "my-map": { + Type: Array, + KeySize: 4, + ValueSize: 4, + MaxEntries: 1, + }, + }, + Programs: map[string]*ProgramSpec{ + "test": { + Type: SocketFilter, + Instructions: asm.Instructions{ + asm.LoadMapPtr(asm.R1, 0), + asm.LoadImm(asm.R0, 0, asm.DWord), + asm.Return(), + }, + License: "MIT", + }, + }, + } + cpy := cs.Copy() + + if cpy == cs { + t.Error("Copy returned the same pointner") + } + + if cpy.Maps["my-map"] == cs.Maps["my-map"] { + t.Error("Copy returned same Maps") + } + + if cpy.Programs["test"] == cs.Programs["test"] { + t.Error("Copy returned same Programs") + } +} + +func TestCollectionSpecOverwriteMaps(t *testing.T) { + insns := asm.Instructions{ + // R1 map + asm.LoadMapPtr(asm.R1, 0), + // R2 key + asm.Mov.Reg(asm.R2, asm.R10), + asm.Add.Imm(asm.R2, -4), + asm.StoreImm(asm.R2, 0, 0, asm.Word), + // Lookup map[0] + asm.FnMapLookupElem.Call(), + asm.JEq.Imm(asm.R0, 0, "ret"), + asm.LoadMem(asm.R0, asm.R0, 0, asm.Word), + asm.Return().Sym("ret"), + } + insns[0].Reference = "test-map" + + cs := &CollectionSpec{ + Maps: map[string]*MapSpec{ + "test-map": { + Type: Array, + KeySize: 4, + ValueSize: 4, + MaxEntries: 1, + }, + }, + Programs: map[string]*ProgramSpec{ + "test-prog": { + Type: SocketFilter, + Instructions: insns, + License: "MIT", + }, + }, + } + + // Override the map with another one + newMap, err := NewMap(cs.Maps["test-map"]) + if err != nil { + t.Fatal(err) + } + defer newMap.Close() + + err = newMap.Put(uint32(0), uint32(2)) + if err != nil { + t.Fatal(err) + } + + err = Edit(&cs.Programs["test-prog"].Instructions).RewriteMap("test-map", newMap) + if err != nil { + t.Fatal(err) + } + + coll, err := NewCollection(cs) + if err != nil { + t.Fatal(err) + } + defer coll.Close() + + oldMap := coll.Maps["test-map"] + err = oldMap.Put(uint32(0), uint32(5)) + if err != nil { + t.Fatal(err) + } + + ret, _, err := coll.Programs["test-prog"].Test(make([]byte, 14)) + if err != nil { + t.Fatal(err) + } + + t.Log(ret) + + if ret != 2 { + t.Fatal("new / override map not used") + } +} diff --git a/editor.go b/editor.go new file mode 100644 index 000000000..0083137b1 --- /dev/null +++ b/editor.go @@ -0,0 +1,182 @@ +package ebpf + +import ( + "fmt" + + "github.com/cilium/ebpf/asm" + "github.com/pkg/errors" +) + +// Editor modifies eBPF instructions. +type Editor struct { + instructions *asm.Instructions + ReferenceOffsets map[string][]int +} + +// Edit creates a new Editor. +// +// The editor retains a reference to insns and modifies its +// contents. +func Edit(insns *asm.Instructions) *Editor { + refs := insns.ReferenceOffsets() + return &Editor{insns, refs} +} + +// RewriteMap rewrites a symbol to point at a Map. +// +// Use IsUnreferencedSymbol if you want to rewrite potentially +// unused maps. +func (ed *Editor) RewriteMap(symbol string, m *Map) error { + return ed.rewriteMap(symbol, m, true) +} + +func (ed *Editor) rewriteMap(symbol string, m *Map, overwrite bool) error { + indices := ed.ReferenceOffsets[symbol] + if len(indices) == 0 { + return &unreferencedSymbolError{symbol} + } + + fd, err := m.fd.value() + if err != nil { + return err + } + + loadOp := asm.LoadImmOp(asm.DWord) + + for _, index := range indices { + load := &(*ed.instructions)[index] + if load.OpCode != loadOp { + return errors.Errorf("symbol %v: missing load instruction", symbol) + } + + if !overwrite && load.Constant != 0 { + return nil + } + + load.Src = 1 + load.Constant = int64(fd) + } + + return nil +} + +// RewriteConstant rewrites all loads of a symbol to a constant value. +// +// This is a way to parameterize clang-compiled eBPF byte code at load +// time. +// +// The following macro should be used to access the constant: +// +// #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) +// +// int xdp() { +// bool my_constant; +// LOAD_CONSTANT("SYMBOL_NAME", my_constant); +// +// if (my_constant) ... +// +// Caveats: +// - The symbol name you pick must be unique +// +// - Failing to rewrite a symbol will not result in an error, +// 0 will be loaded instead (subject to change) +// +// Use IsUnreferencedSymbol if you want to rewrite potentially +// unused symbols. +func (ed *Editor) RewriteConstant(symbol string, value uint64) error { + indices := ed.ReferenceOffsets[symbol] + if len(indices) == 0 { + return &unreferencedSymbolError{symbol} + } + + ldDWImm := asm.LoadImmOp(asm.DWord) + for _, index := range indices { + load := &(*ed.instructions)[index] + if load.OpCode != ldDWImm { + return errors.Errorf("symbol %v: load: found %v instead of %v", symbol, load.OpCode, ldDWImm) + } + + load.Constant = int64(value) + } + return nil +} + +// Link resolves bpf-to-bpf calls. +// +// Each section may contain multiple functions / labels, and is only linked +// if the program being edited references one of these functions. +// +// Sections must not require linking themselves. +func (ed *Editor) Link(sections ...asm.Instructions) error { + sections = append(sections, *ed.instructions) + + // A map of symbols to the libraries which contain them. + symbols := make(map[string]*asm.Instructions) + for i, section := range sections { + offsets, err := section.SymbolOffsets() + if err != nil { + return err + } + for symbol := range offsets { + if symbols[symbol] != nil { + return errors.Errorf("symbol %s is present in multiple sections", symbol) + } + symbols[symbol] = §ions[i] + } + } + + // Appending to ed.instructions would invalidate the pointers in + // ed, so instead we append to a new slice and join them at the end. + var linkedInsns asm.Instructions + + // A list of already linked sections to avoid linking multiple times. + linkedSections := map[*asm.Instructions]struct{}{ + ed.instructions: struct{}{}, + } + + for symbol, indices := range ed.ReferenceOffsets { + for _, index := range indices { + ins := &(*ed.instructions)[index] + + if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.R1 { + continue + } + + if ins.Constant != -1 { + // This is already a valid call, no need to link again. + continue + } + + section := symbols[symbol] + if section == nil { + return errors.Errorf("symbol %s missing from libaries", symbol) + } + + if _, ok := linkedSections[section]; !ok { + linkedInsns = append(linkedInsns, *section...) + linkedSections[section] = struct{}{} + } + } + } + + // ed.instructions has been fixed up. Append linked instructions and + // recalculate ed. + *ed.instructions = append(*ed.instructions, linkedInsns...) + *ed = *Edit(ed.instructions) + return nil +} + +type unreferencedSymbolError struct { + symbol string +} + +func (use *unreferencedSymbolError) Error() string { + return fmt.Sprintf("unreferenced symbol %s", use.symbol) +} + +// IsUnreferencedSymbol returns true if err was caused by +// an unreferenced symbol. +func IsUnreferencedSymbol(err error) bool { + _, ok := err.(*unreferencedSymbolError) + return ok +} diff --git a/editor_test.go b/editor_test.go new file mode 100644 index 000000000..b03d5b2ec --- /dev/null +++ b/editor_test.go @@ -0,0 +1,250 @@ +package ebpf + +import ( + "fmt" + "math" + "testing" + + "github.com/cilium/ebpf/asm" +) + +// ExampleEditor_rewriteConstant shows how to change constants in +// compiled eBPF byte code. +// +// The C should look something like this: +// +// #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) +// +// int xdp() { +// bool my_constant; +// LOAD_CONSTANT("SYMBOL_NAME", my_constant); +// +// if (my_constant) ... +func ExampleEditor_rewriteConstant() { + // This assembly is roughly equivalent to what clang + // would emit for the C above. + insns := asm.Instructions{ + asm.LoadImm(asm.R0, 0, asm.DWord), + asm.Return(), + } + + insns[0].Reference = "my_ret" + + editor := Edit(&insns) + if err := editor.RewriteConstant("my_ret", 42); err != nil { + panic(err) + } + + fmt.Printf("%0.0s", insns) + + // Output: 0: LdImmDW dst: r0 imm: 42 + // 2: Exit +} + +func TestEditorRewriteConstant(t *testing.T) { + spec, err := LoadCollectionSpec("testdata/rewrite.elf") + if err != nil { + t.Fatal(err) + } + + progSpec := spec.Programs["rewrite"] + editor := Edit(&progSpec.Instructions) + + if err := editor.RewriteConstant("constant", 0x01); err != nil { + t.Fatal(err) + } + + if err := editor.RewriteConstant("bogus", 0x01); !IsUnreferencedSymbol(err) { + t.Error("Rewriting unreferenced symbol doesn't return appropriate error") + } + + t.Log(progSpec.Instructions) + + prog, err := NewProgram(progSpec) + if err != nil { + t.Fatal(err) + } + defer prog.Close() + + ret, _, err := prog.Test(make([]byte, 14)) + if err != nil { + t.Fatal(err) + } + + const N = 1 // number of rewrites + if expected := uint32(1<= len(ec.Sections) { + return nil, errors.Errorf("found relocation section %v for missing section %v", i, sec.Info) + } + + // Store relocations under the section index of the target + idx := int(sec.Info) + if relSections[idx] != nil { + return nil, errors.Errorf("section %d has multiple relocation sections", idx) + } + relSections[idx] = sec + case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0: + progSections[i] = sec + } + } + + license, err := loadLicense(licenseSection) + if err != nil { + return nil, errors.Wrap(err, "load license") + } + + version, err := loadVersion(versionSection, ec.ByteOrder) + if err != nil { + return nil, errors.Wrap(err, "load version") + } + + maps, err := ec.loadMaps(mapSections) + if err != nil { + return nil, errors.Wrap(err, "load maps") + } + + progs, libs, err := ec.loadPrograms(progSections, relSections, license, version) + if err != nil { + return nil, errors.Wrap(err, "load programs") + } + + if len(libs) > 0 { + for name, prog := range progs { + editor := Edit(&prog.Instructions) + if err := editor.Link(libs...); err != nil { + return nil, errors.Wrapf(err, "program %s", name) + } + } + } + + return &CollectionSpec{maps, progs}, nil +} + +func loadLicense(sec *elf.Section) (string, error) { + if sec == nil { + return "", errors.Errorf("missing license section") + } + data, err := sec.Data() + if err != nil { + return "", errors.Wrapf(err, "section %s", sec.Name) + } + return string(bytes.TrimRight(data, "\000")), nil +} + +func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) { + if sec == nil { + return 0, nil + } + + var version uint32 + err := binary.Read(sec.Open(), bo, &version) + return version, errors.Wrapf(err, "section %s", sec.Name) +} + +func (ec *elfCode) loadPrograms(progSections, relSections map[int]*elf.Section, license string, version uint32) (map[string]*ProgramSpec, []asm.Instructions, error) { + progs := make(map[string]*ProgramSpec) + var libs []asm.Instructions + for idx, prog := range progSections { + funcSym := ec.symtab.forSectionOffset(idx, 0) + if funcSym == nil { + return nil, nil, errors.Errorf("section %v: no label at start", prog.Name) + } + + var insns asm.Instructions + offsets, err := insns.Unmarshal(prog.Open(), ec.ByteOrder) + if err != nil { + return nil, nil, errors.Wrapf(err, "program %s", funcSym.Name) + } + + err = assignSymbols(ec.symtab.forSection(idx), offsets, insns) + if err != nil { + return nil, nil, errors.Wrapf(err, "program %s", funcSym.Name) + } + + if rels := relSections[idx]; rels != nil { + err = ec.applyRelocations(insns, rels, offsets) + if err != nil { + return nil, nil, errors.Wrapf(err, "program %s: section %s", funcSym.Name, rels.Name) + } + } + + if progType, attachType := getProgType(prog.Name); progType == UnspecifiedProgram { + // There is no single name we can use for "library" sections, + // since they may contain multiple functions. We'll decode the + // labels they contain later on, and then link sections that way. + libs = append(libs, insns) + } else { + progs[funcSym.Name] = &ProgramSpec{ + Name: funcSym.Name, + Type: progType, + AttachType: attachType, + License: license, + KernelVersion: version, + Instructions: insns, + } + } + } + return progs, libs, nil +} + +func (ec *elfCode) loadMaps(mapSections map[int]*elf.Section) (map[string]*MapSpec, error) { + maps := make(map[string]*MapSpec) + for idx, sec := range mapSections { + // TODO: Iterate symbols + n := len(ec.symtab.forSection(idx)) + if n == 0 { + return nil, errors.Errorf("section %v: no symbols", sec.Name) + } + + data, err := sec.Data() + if err != nil { + return nil, err + } + + if len(data)%n != 0 { + return nil, errors.Errorf("map descriptors are not of equal size") + } + + size := len(data) / n + var ordered []*MapSpec + for i := 0; i < n; i++ { + rd := bytes.NewReader(data[i*size : i*size+size]) + mapSym := ec.symtab.forSectionOffset(idx, uint64(i*size)) + if mapSym == nil { + return nil, errors.Errorf("section %s: missing symbol for map #%d", sec.Name, i) + } + + name := mapSym.Name + if maps[name] != nil { + return nil, errors.Errorf("section %v: map %v already exists", sec.Name, name) + } + + var spec MapSpec + var inner uint32 + switch { + case binary.Read(rd, ec.ByteOrder, &spec.Type) != nil: + return nil, errors.Errorf("map %v: missing type", name) + case binary.Read(rd, ec.ByteOrder, &spec.KeySize) != nil: + return nil, errors.Errorf("map %v: missing key size", name) + case binary.Read(rd, ec.ByteOrder, &spec.ValueSize) != nil: + return nil, errors.Errorf("map %v: missing value size", name) + case binary.Read(rd, ec.ByteOrder, &spec.MaxEntries) != nil: + return nil, errors.Errorf("map %v: missing max entries", name) + case binary.Read(rd, ec.ByteOrder, &spec.Flags) != nil: + return nil, errors.Errorf("map %v: missing flags", name) + case rd.Len() > 0 && binary.Read(rd, ec.ByteOrder, &inner) != nil: + return nil, errors.Errorf("map %v: can't read inner map index", name) + } + + for rd.Len() > 0 { + b, err := rd.ReadByte() + if err != nil { + return nil, err + } + if b != 0 { + return nil, errors.Errorf("map %v: unknown and non-zero fields in definition", name) + } + } + + if spec.Type == ArrayOfMaps || spec.Type == HashOfMaps { + if int(inner) > len(ordered) { + return nil, errors.Errorf("map %v: invalid inner map index %d", name, inner) + } + + innerSpec := ordered[int(inner)] + if innerSpec.InnerMap != nil { + return nil, errors.Errorf("map %v: can't nest map of map", name) + } + spec.InnerMap = innerSpec.Copy() + } + + maps[name] = &spec + ordered = append(ordered, &spec) + } + } + return maps, nil +} + +func getProgType(v string) (ProgramType, AttachType) { + types := map[string]ProgramType{ + // From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c#n3568 + "socket": SocketFilter, + "seccomp": SocketFilter, + "kprobe/": Kprobe, + "kretprobe/": Kprobe, + "tracepoint/": TracePoint, + "xdp": XDP, + "perf_event": PerfEvent, + "sockops": SockOps, + "sk_skb": SkSKB, + "sk_msg": SkMsg, + "lirc_mode2": LircMode2, + "flow_dissector": FlowDissector, + + "cgroup_skb/": CGroupSKB, + "cgroup/dev": CGroupDevice, + "cgroup/skb": CGroupSKB, + "cgroup/sock": CGroupSock, + "cgroup/post_bind": CGroupSock, + "cgroup/bind": CGroupSockAddr, + "cgroup/connect": CGroupSockAddr, + "cgroup/sendmsg": CGroupSockAddr, + "cgroup/recvmsg": CGroupSockAddr, + "cgroup/sysctl": CGroupSysctl, + "cgroup/getsockopt": CGroupSockopt, + "cgroup/setsockopt": CGroupSockopt, + "classifier": SchedCLS, + "action": SchedACT, + } + attachTypes := map[string]AttachType{ + "cgroup_skb/ingress": AttachCGroupInetIngress, + "cgroup_skb/egress": AttachCGroupInetEgress, + "cgroup/sock": AttachCGroupInetSockCreate, + "cgroup/post_bind4": AttachCGroupInet4PostBind, + "cgroup/post_bind6": AttachCGroupInet6PostBind, + "cgroup/dev": AttachCGroupDevice, + "sockops": AttachCGroupSockOps, + "sk_skb/stream_parser": AttachSkSKBStreamParser, + "sk_skb/stream_verdict": AttachSkSKBStreamVerdict, + "sk_msg": AttachSkSKBStreamVerdict, + "lirc_mode2": AttachLircMode2, + "flow_dissector": AttachFlowDissector, + "cgroup/bind4": AttachCGroupInet4Bind, + "cgroup/bind6": AttachCGroupInet6Bind, + "cgroup/connect4": AttachCGroupInet4Connect, + "cgroup/connect6": AttachCGroupInet6Connect, + "cgroup/sendmsg4": AttachCGroupUDP4Sendmsg, + "cgroup/sendmsg6": AttachCGroupUDP6Sendmsg, + "cgroup/recvmsg4": AttachCGroupUDP4Recvmsg, + "cgroup/recvmsg6": AttachCGroupUDP6Recvmsg, + "cgroup/sysctl": AttachCGroupSysctl, + "cgroup/getsockopt": AttachCGroupGetsockopt, + "cgroup/setsockopt": AttachCGroupSetsockopt, + } + attachType := AttachNone + for k, t := range attachTypes { + if strings.HasPrefix(v, k) { + attachType = t + } + } + + for k, t := range types { + if strings.HasPrefix(v, k) { + return t, attachType + } + } + return UnspecifiedProgram, AttachNone +} + +func assignSymbols(symbolOffsets map[uint64]*elf.Symbol, insOffsets map[uint64]int, insns asm.Instructions) error { + for offset, sym := range symbolOffsets { + i, ok := insOffsets[offset] + if !ok { + return errors.Errorf("symbol %s: no instruction at offset %d", sym.Name, offset) + } + insns[i].Symbol = sym.Name + } + return nil +} + +func (ec *elfCode) applyRelocations(insns asm.Instructions, sec *elf.Section, offsets map[uint64]int) error { + if sec.Entsize < 16 { + return errors.New("rls are less than 16 bytes") + } + + r := sec.Open() + for off := uint64(0); off < sec.Size; off += sec.Entsize { + ent := io.LimitReader(r, int64(sec.Entsize)) + + var rel elf.Rel64 + if binary.Read(ent, ec.ByteOrder, &rel) != nil { + return errors.Errorf("can't parse relocation at offset %v", off) + } + + sym, err := ec.symtab.forRelocation(rel) + if err != nil { + return errors.Wrapf(err, "relocation at offset %v", off) + } + + idx, ok := offsets[rel.Off] + if !ok { + return errors.Errorf("symbol %v: invalid instruction offset %x", sym, rel.Off) + } + insns[idx].Reference = sym.Name + } + return nil +} + +type symtab struct { + Symbols []elf.Symbol + index map[int]map[uint64]*elf.Symbol +} + +func newSymtab(symbols []elf.Symbol) *symtab { + index := make(map[int]map[uint64]*elf.Symbol) + for i, sym := range symbols { + switch elf.ST_TYPE(sym.Info) { + case elf.STT_NOTYPE: + // Older versions of LLVM doesn't tag + // symbols correctly. + break + case elf.STT_OBJECT: + break + case elf.STT_FUNC: + break + default: + continue + } + + if sym.Name == "" { + continue + } + + idx := int(sym.Section) + if _, ok := index[idx]; !ok { + index[idx] = make(map[uint64]*elf.Symbol) + } + index[idx][sym.Value] = &symbols[i] + } + return &symtab{ + symbols, + index, + } +} + +func (st *symtab) forSection(sec int) map[uint64]*elf.Symbol { + return st.index[sec] +} + +func (st *symtab) forSectionOffset(sec int, offset uint64) *elf.Symbol { + offsets := st.index[sec] + if offsets == nil { + return nil + } + return offsets[offset] +} + +func (st *symtab) forRelocation(rel elf.Rel64) (*elf.Symbol, error) { + symNo := int(elf.R_SYM64(rel.Info) - 1) + if symNo >= len(st.Symbols) { + return nil, errors.Errorf("symbol %v doesnt exist", symNo) + } + return &st.Symbols[symNo], nil +} diff --git a/elf_test.go b/elf_test.go new file mode 100644 index 000000000..b70381557 --- /dev/null +++ b/elf_test.go @@ -0,0 +1,169 @@ +package ebpf + +import ( + "path/filepath" + "reflect" + "testing" +) + +func TestLoadCollectionSpec(t *testing.T) { + files, err := filepath.Glob("testdata/loader-*.elf") + if err != nil { + t.Fatal(err) + } + + for _, file := range files { + t.Run(file, func(t *testing.T) { + spec, err := LoadCollectionSpec(file) + if err != nil { + t.Fatal("Can't parse ELF:", err) + } + + hashMapSpec := &MapSpec{ + "hash_map", + Hash, + 4, + 2, + 1, + 0, + nil, + } + checkMapSpec(t, spec.Maps, "hash_map", hashMapSpec) + checkMapSpec(t, spec.Maps, "array_of_hash_map", &MapSpec{ + "hash_map", ArrayOfMaps, 4, 0, 2, 0, hashMapSpec, + }) + + hashMap2Spec := &MapSpec{ + "", + Hash, + 4, + 1, + 2, + 1, + nil, + } + checkMapSpec(t, spec.Maps, "hash_map2", hashMap2Spec) + checkMapSpec(t, spec.Maps, "hash_of_hash_map", &MapSpec{ + "", HashOfMaps, 4, 0, 2, 0, hashMap2Spec, + }) + + checkProgramSpec(t, spec.Programs, "xdp_prog", &ProgramSpec{ + Type: XDP, + License: "MIT", + KernelVersion: 0, + }) + checkProgramSpec(t, spec.Programs, "no_relocation", &ProgramSpec{ + Type: SocketFilter, + License: "MIT", + KernelVersion: 0, + }) + + t.Log(spec.Programs["xdp_prog"].Instructions) + + coll, err := NewCollection(spec) + if err != nil { + t.Fatal(err) + } + defer coll.Close() + + hash := coll.DetachMap("hash_map") + if hash == nil { + t.Fatal("Program not returned from DetachMap") + } + defer hash.Close() + + if _, ok := coll.Programs["hash_map"]; ok { + t.Error("DetachMap doesn't remove map from Maps") + } + + prog := coll.DetachProgram("xdp_prog") + if prog == nil { + t.Fatal("Program not returned from DetachProgram") + } + defer prog.Close() + + if _, ok := coll.Programs["xdp_prog"]; ok { + t.Error("DetachProgram doesn't remove program from Programs") + } + }) + } +} + +func checkMapSpec(t *testing.T, maps map[string]*MapSpec, name string, want *MapSpec) { + t.Helper() + + have, ok := maps[name] + if !ok { + t.Errorf("Missing map %s", name) + return + } + + mapSpecEqual(t, name, have, want) +} + +func mapSpecEqual(t *testing.T, name string, have, want *MapSpec) { + t.Helper() + + if have.Type != want.Type { + t.Errorf("%s: expected type %v, got %v", name, want.Type, have.Type) + } + + if have.KeySize != want.KeySize { + t.Errorf("%s: expected key size %v, got %v", name, want.KeySize, have.KeySize) + } + + if have.ValueSize != want.ValueSize { + t.Errorf("%s: expected value size %v, got %v", name, want.ValueSize, have.ValueSize) + } + + if have.MaxEntries != want.MaxEntries { + t.Errorf("%s: expected max entries %v, got %v", name, want.MaxEntries, have.MaxEntries) + } + + if have.Flags != want.Flags { + t.Errorf("%s: expected flags %v, got %v", name, want.Flags, have.Flags) + } + + switch { + case have.InnerMap != nil && want.InnerMap == nil: + t.Errorf("%s: extraneous InnerMap", name) + case have.InnerMap == nil && want.InnerMap != nil: + t.Errorf("%s: missing InnerMap", name) + case have.InnerMap != nil && want.InnerMap != nil: + mapSpecEqual(t, name+".InnerMap", have.InnerMap, want.InnerMap) + } +} + +func checkProgramSpec(t *testing.T, progs map[string]*ProgramSpec, name string, want *ProgramSpec) { + t.Helper() + + have, ok := progs[name] + if !ok { + t.Fatalf("Missing program %s", name) + return + } + + if have.License != want.License { + t.Errorf("%s: expected %v license, got %v", name, want.License, have.License) + } + + if have.Type != want.Type { + t.Errorf("%s: expected %v program, got %v", name, want.Type, have.Type) + } + + if want.Instructions != nil && !reflect.DeepEqual(have.Instructions, want.Instructions) { + t.Log("Expected program") + t.Log(want.Instructions) + t.Log("Actual program") + t.Log(want.Instructions) + t.Error("Instructions do not match") + } +} + +func TestLoadInvalidMap(t *testing.T) { + _, err := LoadCollectionSpec("testdata/invalid_map.elf") + t.Log(err) + if err == nil { + t.Fatal("should be fail") + } +} diff --git a/example_sock_elf_test.go b/example_sock_elf_test.go new file mode 100644 index 000000000..ccc1ab55e --- /dev/null +++ b/example_sock_elf_test.go @@ -0,0 +1,187 @@ +// +build linux + +package ebpf_test + +import ( + "bytes" + "flag" + "fmt" + "os" + "syscall" + "time" + + "github.com/cilium/ebpf" +) + +var program = [...]byte{ + 0177, 0105, 0114, 0106, 0002, 0001, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0001, 0000, 0367, 0000, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0340, 0001, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0100, 0000, 0000, 0000, 0000, 0000, 0100, 0000, 0010, 0000, 0001, 0000, + 0277, 0026, 0000, 0000, 0000, 0000, 0000, 0000, 0060, 0000, 0000, 0000, 0027, 0000, 0000, 0000, + 0143, 0012, 0374, 0377, 0000, 0000, 0000, 0000, 0141, 0141, 0004, 0000, 0000, 0000, 0000, 0000, + 0125, 0001, 0010, 0000, 0004, 0000, 0000, 0000, 0277, 0242, 0000, 0000, 0000, 0000, 0000, 0000, + 0007, 0002, 0000, 0000, 0374, 0377, 0377, 0377, 0030, 0001, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0205, 0000, 0000, 0000, 0001, 0000, 0000, 0000, + 0025, 0000, 0002, 0000, 0000, 0000, 0000, 0000, 0141, 0141, 0000, 0000, 0000, 0000, 0000, 0000, + 0333, 0020, 0000, 0000, 0000, 0000, 0000, 0000, 0267, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0225, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0002, 0000, 0000, 0000, 0004, 0000, 0000, 0000, + 0010, 0000, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0002, 0000, 0000, 0000, + 0004, 0000, 0000, 0000, 0010, 0000, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0000, 0000, 0000, + 0107, 0120, 0114, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0065, 0000, 0000, 0000, 0000, 0000, 0003, 0000, 0150, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0034, 0000, 0000, 0000, 0020, 0000, 0006, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0110, 0000, 0000, 0000, 0020, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0014, 0000, 0000, 0000, 0020, 0000, 0005, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0023, 0000, 0000, 0000, 0020, 0000, 0005, 0000, 0024, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0070, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0001, 0000, 0000, 0000, 0004, 0000, 0000, 0000, 0000, 0056, 0164, 0145, 0170, 0164, 0000, 0155, + 0141, 0160, 0163, 0000, 0155, 0171, 0137, 0155, 0141, 0160, 0000, 0164, 0145, 0163, 0164, 0137, + 0155, 0141, 0160, 0000, 0137, 0154, 0151, 0143, 0145, 0156, 0163, 0145, 0000, 0056, 0163, 0164, + 0162, 0164, 0141, 0142, 0000, 0056, 0163, 0171, 0155, 0164, 0141, 0142, 0000, 0114, 0102, 0102, + 0060, 0137, 0063, 0000, 0056, 0162, 0145, 0154, 0163, 0157, 0143, 0153, 0145, 0164, 0061, 0000, + 0142, 0160, 0146, 0137, 0160, 0162, 0157, 0147, 0061, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0045, 0000, 0000, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0210, 0001, 0000, 0000, 0000, 0000, 0000, 0000, + 0122, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0001, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0006, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0100, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0004, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0100, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0006, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0100, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0170, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0010, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0074, 0000, 0000, 0000, 0011, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0170, 0001, 0000, 0000, 0000, 0000, 0000, 0000, + 0020, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0007, 0000, 0000, 0000, 0003, 0000, 0000, 0000, + 0010, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0020, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0007, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0270, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0050, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0004, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0035, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0340, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0004, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0055, 0000, 0000, 0000, 0002, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0350, 0000, 0000, 0000, 0000, 0000, 0000, 0000, + 0220, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0002, 0000, 0000, 0000, + 0010, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0030, 0000, 0000, 0000, 0000, 0000, 0000, 0000, +} + +const sockexPin = "/sys/fs/bpf/sockex1" + +// ExampleSocketELF demonstrates how to load an eBPF program from an ELF, +// pin it into the filesystem and attach it to a raw socket. +func Example_socketELF() { + const SO_ATTACH_BPF = 50 + + index := flag.Int("index", 0, "specify ethernet index") + flag.Parse() + fi, err := os.Lstat(sockexPin) + if err != nil && !os.IsNotExist(err) { + panic(err) + } + var coll *ebpf.Collection + if fi != nil { + coll, err = ebpf.LoadPinnedCollection(sockexPin) + if err != nil { + panic(err) + } + } else { + spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(program[:])) + if err != nil { + panic(err) + } + coll, err = ebpf.NewCollection(spec) + if err != nil { + panic(err) + } + err = coll.Pin(sockexPin, 0600) + if err != nil { + panic(err) + } + } + defer coll.Close() + + sock, err := openRawSock(*index) + if err != nil { + panic(err) + } + defer syscall.Close(sock) + + prog := coll.DetachProgram("bpf_prog1") + if prog == nil { + panic("no program named bpf_prog1 found") + } + defer prog.Close() + + if err := syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, SO_ATTACH_BPF, prog.FD()); err != nil { + panic(err) + } + + fmt.Printf("Filtering on eth index: %d\n", *index) + fmt.Println("Packet stats:") + + protoStats := coll.DetachMap("my_map") + if protoStats == nil { + panic(fmt.Errorf("no map named my_map found")) + } + defer protoStats.Close() + + for { + const ( + ICMP = 0x01 + TCP = 0x06 + UDP = 0x11 + ) + + time.Sleep(time.Second) + var icmp uint64 + var tcp uint64 + var udp uint64 + err := protoStats.Lookup(uint32(ICMP), &icmp) + if err != nil { + panic(err) + } + err = protoStats.Lookup(uint32(TCP), &tcp) + if err != nil { + panic(err) + } + err = protoStats.Lookup(uint32(UDP), &udp) + if err != nil { + panic(err) + } + fmt.Printf("\r\033[m\tICMP: %d TCP: %d UDP: %d", icmp, tcp, udp) + } +} + +func assertTrue(b bool, msg string) { + if !b { + panic(fmt.Errorf("%s", msg)) + } +} + +func openRawSock(index int) (int, error) { + const ETH_P_ALL uint16 = 0x00<<8 | 0x03 + sock, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, int(ETH_P_ALL)) + if err != nil { + return 0, err + } + sll := syscall.SockaddrLinklayer{} + sll.Protocol = ETH_P_ALL + sll.Ifindex = index + if err := syscall.Bind(sock, &sll); err != nil { + return 0, err + } + return sock, nil +} diff --git a/testdata/Makefile b/testdata/Makefile new file mode 100644 index 000000000..60d21cfe1 --- /dev/null +++ b/testdata/Makefile @@ -0,0 +1,18 @@ +LLVM_PREFIX ?= /usr/bin +CLANG ?= $(LLVM_PREFIX)/clang + +all: loader-clang-6.0.elf loader-clang-7.elf loader-clang-8.elf rewrite.elf perf_output.elf invalid_map.elf + +clean: + -$(RM) *.elf + +loader-%.elf: loader.c + $* -target bpf -O2 -g \ + -Wall -Werror \ + -c $< -o $@ + +%.elf : %.c + $(CLANG) -target bpf -O2 -g \ + -Wall -Werror \ + -c $< -o $@ + diff --git a/testdata/common.h b/testdata/common.h new file mode 100644 index 000000000..0b2a45ef0 --- /dev/null +++ b/testdata/common.h @@ -0,0 +1,28 @@ +#pragma once + +typedef unsigned int uint32_t; +typedef unsigned long uint64_t; + +#define __section(NAME) __attribute__((section(NAME), used)) + +#define BPF_MAP_TYPE_ARRAY (1) +#define BPF_MAP_TYPE_PERF_EVENT_ARRAY (4) +#define BPF_MAP_TYPE_ARRAY_OF_MAPS (12) +#define BPF_MAP_TYPE_HASH_OF_MAPS (13) + +#define BPF_F_NO_PREALLOC (1U << 0) +#define BPF_F_CURRENT_CPU (0xffffffffULL) + +struct map { + uint32_t type; + uint32_t key_size; + uint32_t value_size; + uint32_t max_entries; + uint32_t flags; + uint32_t inner_map_idx; + uint32_t dummy; +}; + +static void* (*map_lookup_elem)(const void *map, const void *key) = (void*)1; +static int (*perf_event_output)(const void *ctx, const void *map, uint64_t index, const void *data, uint64_t size) = (void*)25; +static uint32_t (*get_smp_processor_id)(void) = (void*)8; diff --git a/testdata/invalid_map.c b/testdata/invalid_map.c new file mode 100644 index 000000000..eb27381ea --- /dev/null +++ b/testdata/invalid_map.c @@ -0,0 +1,15 @@ +/* This file excercises the ELF loader. It is not a valid BPF program. + */ + +#include "common.h" + +char __license[] __section("license") = "MIT"; + +struct map invalid_map __section("maps") = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = 4, + .value_size = 2, + .max_entries = 1, + .flags = 0, + .dummy = 1, +}; diff --git a/testdata/invalid_map.elf b/testdata/invalid_map.elf new file mode 100644 index 000000000..24059adc4 Binary files /dev/null and b/testdata/invalid_map.elf differ diff --git a/testdata/loader-clang-6.0.elf b/testdata/loader-clang-6.0.elf new file mode 100644 index 000000000..9e4b15126 Binary files /dev/null and b/testdata/loader-clang-6.0.elf differ diff --git a/testdata/loader-clang-7.elf b/testdata/loader-clang-7.elf new file mode 100644 index 000000000..d231c1abd Binary files /dev/null and b/testdata/loader-clang-7.elf differ diff --git a/testdata/loader-clang-8.elf b/testdata/loader-clang-8.elf new file mode 100644 index 000000000..8510049a5 Binary files /dev/null and b/testdata/loader-clang-8.elf differ diff --git a/testdata/loader.c b/testdata/loader.c new file mode 100644 index 000000000..3b8e12d3a --- /dev/null +++ b/testdata/loader.c @@ -0,0 +1,57 @@ +/* This file excercises the ELF loader. It is not a valid BPF program. + */ + +#include "common.h" + +char __license[] __section("license") = "MIT"; + +struct map hash_map __section("maps") = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = 4, + .value_size = 2, + .max_entries = 1, + .flags = 0, +}; + +struct map hash_map2 __section("maps") = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = 4, + .value_size = 1, + .max_entries = 2, + .flags = BPF_F_NO_PREALLOC, +}; + +struct map array_of_hash_map __section("maps") = { + .type = BPF_MAP_TYPE_ARRAY_OF_MAPS, + .key_size = sizeof(uint32_t), + .max_entries = 2, + .inner_map_idx = 0, // points to "hash_map" +}; + +struct map hash_of_hash_map __section("maps") = { + .type = BPF_MAP_TYPE_HASH_OF_MAPS, + .key_size = sizeof(uint32_t), + .max_entries = 2, + .inner_map_idx = 1, // points to "hash_map2" +}; + +int __attribute__((noinline)) helper_func2(int arg) { + return arg > 5; +} + +int __attribute__((noinline)) helper_func(int arg) { + // Enforce bpf-to-bpf call in .text section + return helper_func2(arg); +} + +__section("xdp") int xdp_prog() { + unsigned int key = 0; + map_lookup_elem(&hash_map, &key); + map_lookup_elem(&hash_map2, &key); + return helper_func(1); +} + +// This function has no relocations, and is thus parsed differently. +__section("socket") int no_relocation() { + return 0; +} diff --git a/testdata/rewrite.c b/testdata/rewrite.c new file mode 100644 index 000000000..ab65c0cae --- /dev/null +++ b/testdata/rewrite.c @@ -0,0 +1,32 @@ +/* This file tests rewriting constants from C compiled code. + */ + +#include "common.h" + +char __license[] __section("license") = "MIT"; + +struct map map_val __section("maps") = { + .type = 1, + .key_size = sizeof(unsigned int), + .value_size = sizeof(unsigned int), + .max_entries = 1, +}; + +#define CONSTANT "constant" + +#define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) + +__section("socket") int rewrite() { + unsigned long acc = 0; + LOAD_CONSTANT(CONSTANT, acc); + return acc; +} + +__section("socket/map") int rewrite_map() { + unsigned int key = 0; + unsigned int *value = map_lookup_elem(&map_val, &key); + if (!value) { + return 0; + } + return *value; +} diff --git a/testdata/rewrite.elf b/testdata/rewrite.elf new file mode 100644 index 000000000..6325e2c5d Binary files /dev/null and b/testdata/rewrite.elf differ