From 5727d650c09d79ac987b3c2d4e5420e6393cd5c7 Mon Sep 17 00:00:00 2001 From: Lorenz Bauer Date: Mon, 15 Jun 2020 16:57:01 +0100 Subject: [PATCH] link: move BPF_PROG_ATTACH support into new package Program.Attach and Program.Detach are low level interfaces: the semantics of flags, etc. depends on the kind of program you are trying to attach. At the same time they are useful as an escape hatch for features that the library doesn't yet support with a nicer API. Move the two functions into a separate link package, which will contain other primitives that handle attaching programs to various hooks. Future proof the API by using argument structs. --- internal/syscall.go | 24 ++++++++++++ internal/unix/types_linux.go | 1 + internal/unix/types_other.go | 1 + link/program.go | 76 ++++++++++++++++++++++++++++++++++++ link/program_test.go | 58 +++++++++++++++++++++++++++ link/syscalls.go | 28 +++++++++++++ link/syscalls_test.go | 11 ++++++ prog.go | 35 ++++++++++------- prog_test.go | 38 ------------------ 9 files changed, 220 insertions(+), 52 deletions(-) create mode 100644 link/program.go create mode 100644 link/program_test.go create mode 100644 link/syscalls.go create mode 100644 link/syscalls_test.go diff --git a/internal/syscall.go b/internal/syscall.go index 586f2e4df..f08fdf3bb 100644 --- a/internal/syscall.go +++ b/internal/syscall.go @@ -64,3 +64,27 @@ func BPF(cmd BPFCmd, attr unsafe.Pointer, size uintptr) (uintptr, error) { return r1, err } + +type BPFProgAttachAttr struct { + TargetFd uint32 + AttachBpfFd uint32 + AttachType uint32 + AttachFlags uint32 + ReplaceBpfFd uint32 +} + +func BPFProgAttach(attr *BPFProgAttachAttr) error { + _, err := BPF(BPF_PROG_ATTACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) + return err +} + +type BPFProgDetachAttr struct { + TargetFd uint32 + AttachBpfFd uint32 + AttachType uint32 +} + +func BPFProgDetach(attr *BPFProgDetachAttr) error { + _, err := BPF(BPF_PROG_DETACH, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) + return err +} diff --git a/internal/unix/types_linux.go b/internal/unix/types_linux.go index 0545fe945..9363d0be8 100644 --- a/internal/unix/types_linux.go +++ b/internal/unix/types_linux.go @@ -16,6 +16,7 @@ const ( EINVAL = linux.EINVAL EPOLLIN = linux.EPOLLIN EINTR = linux.EINTR + EPERM = linux.EPERM ESRCH = linux.ESRCH ENODEV = linux.ENODEV BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG diff --git a/internal/unix/types_other.go b/internal/unix/types_other.go index 555a5ce35..2dea950f8 100644 --- a/internal/unix/types_other.go +++ b/internal/unix/types_other.go @@ -17,6 +17,7 @@ const ( ENOSPC = syscall.ENOSPC EINVAL = syscall.EINVAL EINTR = syscall.EINTR + EPERM = syscall.EPERM ESRCH = syscall.ESRCH ENODEV = syscall.ENODEV BPF_F_RDONLY_PROG = 0 diff --git a/link/program.go b/link/program.go new file mode 100644 index 000000000..7c7d347bb --- /dev/null +++ b/link/program.go @@ -0,0 +1,76 @@ +package link + +import ( + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/internal" + + "golang.org/x/xerrors" +) + +type RawAttachProgramOptions struct { + // File descriptor to attach to. This differs for each attach type. + Target int + // Program to attach. + Program *ebpf.Program + // Program to replace (cgroups). + Replace *ebpf.Program + // Attach must match the attach type of Program (and Replace). + Attach ebpf.AttachType + // Flags control the attach behaviour. This differs for each attach type. + Flags uint32 +} + +// RawAttachProgram is a low level wrapper around BPF_PROG_ATTACH. +// +// You should use one of the higher level abstractions available in this +// package if possible. +func RawAttachProgram(opts RawAttachProgramOptions) error { + if err := haveProgAttach(); err != nil { + return err + } + + var replaceFd uint32 + if opts.Replace != nil { + replaceFd = uint32(opts.Replace.FD()) + } + + attr := internal.BPFProgAttachAttr{ + TargetFd: uint32(opts.Target), + AttachBpfFd: uint32(opts.Program.FD()), + ReplaceBpfFd: replaceFd, + AttachType: uint32(opts.Attach), + AttachFlags: uint32(opts.Flags), + } + + if err := internal.BPFProgAttach(&attr); err != nil { + return xerrors.Errorf("can't attach program: %s", err) + } + return nil +} + +type RawDetachProgramOptions struct { + Target int + Program *ebpf.Program + Attach ebpf.AttachType +} + +// RawDetachProgram is a low level wrapper around BPF_PROG_DETACH. +// +// You should use one of the higher level abstractions available in this +// package if possible. +func RawDetachProgram(opts RawDetachProgramOptions) error { + if err := haveProgAttach(); err != nil { + return err + } + + attr := internal.BPFProgDetachAttr{ + TargetFd: uint32(opts.Target), + AttachBpfFd: uint32(opts.Program.FD()), + AttachType: uint32(opts.Attach), + } + if err := internal.BPFProgDetach(&attr); err != nil { + return xerrors.Errorf("can't detach program: %s", err) + } + + return nil +} diff --git a/link/program_test.go b/link/program_test.go new file mode 100644 index 000000000..f4c2ccc6a --- /dev/null +++ b/link/program_test.go @@ -0,0 +1,58 @@ +package link + +import ( + "testing" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "github.com/cilium/ebpf/internal/testutils" +) + +func TestProgramAlter(t *testing.T) { + testutils.SkipOnOldKernel(t, "4.13", "SkSKB type") + + var err error + var prog *ebpf.Program + prog, err = ebpf.NewProgram(&ebpf.ProgramSpec{ + Type: ebpf.SkSKB, + Instructions: asm.Instructions{ + asm.LoadImm(asm.R0, 0, asm.DWord), + asm.Return(), + }, + License: "MIT", + }) + if err != nil { + t.Fatal(err) + } + defer prog.Close() + + var sockMap *ebpf.Map + sockMap, err = ebpf.NewMap(&ebpf.MapSpec{ + Type: ebpf.MapType(15), // BPF_MAP_TYPE_SOCKMAP + KeySize: 4, + ValueSize: 4, + MaxEntries: 2, + }) + if err != nil { + t.Fatal(err) + } + defer sockMap.Close() + + err = RawAttachProgram(RawAttachProgramOptions{ + Target: sockMap.FD(), + Program: prog, + Attach: ebpf.AttachSkSKBStreamParser, + }) + if err != nil { + t.Fatal(err) + } + + err = RawDetachProgram(RawDetachProgramOptions{ + Target: sockMap.FD(), + Program: prog, + Attach: ebpf.AttachSkSKBStreamParser, + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/link/syscalls.go b/link/syscalls.go new file mode 100644 index 000000000..44c5177c2 --- /dev/null +++ b/link/syscalls.go @@ -0,0 +1,28 @@ +package link + +import ( + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "github.com/cilium/ebpf/internal" +) + +var haveProgAttach = internal.FeatureTest("BPF_PROG_ATTACH", "4.10", func() (bool, error) { + prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ + Type: ebpf.CGroupSKB, + AttachType: ebpf.AttachCGroupInetIngress, + License: "MIT", + Instructions: asm.Instructions{ + asm.Mov.Imm(asm.R0, 0), + asm.Return(), + }, + }) + if err != nil { + return false, nil + } + + // BPF_PROG_ATTACH was introduced at the same time as CGgroupSKB, + // so being able to load the program is enough to infer that we + // have the syscall. + prog.Close() + return true, nil +}) diff --git a/link/syscalls_test.go b/link/syscalls_test.go new file mode 100644 index 000000000..4f44993f9 --- /dev/null +++ b/link/syscalls_test.go @@ -0,0 +1,11 @@ +package link + +import ( + "testing" + + "github.com/cilium/ebpf/internal/testutils" +) + +func TestHaveProgAttach(t *testing.T) { + testutils.CheckFeatureTest(t, haveProgAttach) +} diff --git a/prog.go b/prog.go index 68e239d98..c4f75825a 100644 --- a/prog.go +++ b/prog.go @@ -454,7 +454,9 @@ func (p *Program) MarshalBinary() ([]byte, error) { return buf, nil } -// Attach a Program to a container object fd +// Attach a Program. +// +// Deprecated: use link.RawAttachProgram instead. func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error { if fd < 0 { return xerrors.New("invalid fd") @@ -465,35 +467,40 @@ func (p *Program) Attach(fd int, typ AttachType, flags AttachFlags) error { return err } - attr := bpfProgAlterAttr{ - targetFd: uint32(fd), - attachBpfFd: pfd, - attachType: uint32(typ), - attachFlags: uint32(flags), + attr := internal.BPFProgAttachAttr{ + TargetFd: uint32(fd), + AttachBpfFd: pfd, + AttachType: uint32(typ), + AttachFlags: uint32(flags), } - return bpfProgAlter(internal.BPF_PROG_ATTACH, &attr) + return internal.BPFProgAttach(&attr) } -// Detach a Program from a container object fd +// Detach a Program. +// +// Deprecated: use link.RawDetachProgram instead. func (p *Program) Detach(fd int, typ AttachType, flags AttachFlags) error { if fd < 0 { return xerrors.New("invalid fd") } + if flags != 0 { + return xerrors.New("flags must be zero") + } + pfd, err := p.fd.Value() if err != nil { return err } - attr := bpfProgAlterAttr{ - targetFd: uint32(fd), - attachBpfFd: pfd, - attachType: uint32(typ), - attachFlags: uint32(flags), + attr := internal.BPFProgDetachAttr{ + TargetFd: uint32(fd), + AttachBpfFd: pfd, + AttachType: uint32(typ), } - return bpfProgAlter(internal.BPF_PROG_DETACH, &attr) + return internal.BPFProgDetach(&attr) } // LoadPinnedProgram loads a Program from a BPF file. diff --git a/prog_test.go b/prog_test.go index 04168fc9b..f896cc6e5 100644 --- a/prog_test.go +++ b/prog_test.go @@ -388,44 +388,6 @@ func TestProgramFromFD(t *testing.T) { prog2.Close() } -func TestProgramAlter(t *testing.T) { - testutils.SkipOnOldKernel(t, "4.13", "SkSKB type") - - var err error - var prog *Program - prog, err = NewProgram(&ProgramSpec{ - Type: SkSKB, - Instructions: asm.Instructions{ - asm.LoadImm(asm.R0, 0, asm.DWord), - asm.Return(), - }, - License: "MIT", - }) - if err != nil { - t.Fatal(err) - } - defer prog.Close() - - var sockMap *Map - sockMap, err = NewMap(&MapSpec{ - Type: MapType(15), // BPF_MAP_TYPE_SOCKMAP - KeySize: 4, - ValueSize: 4, - MaxEntries: 2, - }) - if err != nil { - t.Fatal(err) - } - defer sockMap.Close() - - if err := prog.Attach(sockMap.FD(), AttachSkSKBStreamParser, AttachFlags(0)); err != nil { - t.Fatal(err) - } - if err := prog.Detach(sockMap.FD(), AttachSkSKBStreamParser, AttachFlags(0)); err != nil { - t.Fatal(err) - } -} - func TestHaveProgTestRun(t *testing.T) { testutils.CheckFeatureTest(t, haveProgTestRun) }