Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

link: use BPF links to attach Tracing and LSM prog types #837

Merged
merged 3 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ testdata/loader-%-eb.elf: testdata/loader.c
$(STRIP) -g $@

.PHONY: generate-btf
generate-btf: KERNEL_VERSION?=5.18
generate-btf: KERNEL_VERSION?=5.19
generate-btf:
$(eval TMP := $(shell mktemp -d))
curl -fL "$(CI_KERNEL_URL)/linux-$(KERNEL_VERSION).bz" -o "$(TMP)/bzImage"
Expand Down
Binary file modified btf/testdata/btf_testmod.btf
Binary file not shown.
Binary file modified btf/testdata/vmlinux.btf.gz
Binary file not shown.
9 changes: 9 additions & 0 deletions internal/cmd/gentypes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,15 @@ import (
rename("cnt", "count"),
},
},
{
"LinkCreateTracing", retFd, "link_create", "BPF_LINK_CREATE",
[]patch{
chooseNth(4, 4),
replace(enumTypes["AttachType"], "attach_type"),
flattenAnon,
replace(btfID, "target_btf_id"),
},
},
{
"LinkUpdate", retError, "link_update", "BPF_LINK_UPDATE",
nil,
Expand Down
34 changes: 32 additions & 2 deletions internal/sys/types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 63 additions & 14 deletions link/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal/sys"
"github.com/cilium/ebpf/internal/unix"
)

type tracing struct {
Expand Down Expand Up @@ -87,29 +88,71 @@ type TracingOptions struct {
// AttachTraceFEntry/AttachTraceFExit/AttachModifyReturn or
// AttachTraceRawTp.
Program *ebpf.Program
// Program attach type. Can be one of:
// - AttachTraceFEntry
// - AttachTraceFExit
// - AttachModifyReturn
// - AttachTraceRawTp
// This field is optional.
AttachType ebpf.AttachType
// Arbitrary value that can be fetched from an eBPF program
// via `bpf_get_attach_cookie()`.
Cookie uint64
}

type LSMOptions struct {
// Program must be of type LSM with attach type
// AttachLSMMac.
Program *ebpf.Program
// Arbitrary value that can be fetched from an eBPF program
// via `bpf_get_attach_cookie()`.
Cookie uint64
}

// attachBTFID links all BPF program types (Tracing/LSM) that they attach to a btf_id.
func attachBTFID(program *ebpf.Program) (Link, error) {
func attachBTFID(program *ebpf.Program, at ebpf.AttachType, cookie uint64) (Link, error) {
if program.FD() < 0 {
return nil, fmt.Errorf("invalid program %w", sys.ErrClosedFd)
}

fd, err := sys.RawTracepointOpen(&sys.RawTracepointOpenAttr{
ProgFd: uint32(program.FD()),
})
if errors.Is(err, sys.ENOTSUPP) {
// This may be returned by bpf_tracing_prog_attach via bpf_arch_text_poke.
return nil, fmt.Errorf("create raw tracepoint: %w", ErrNotSupported)
}
if err != nil {
return nil, fmt.Errorf("create raw tracepoint: %w", err)
var (
fd *sys.FD
err error
)
switch at {
case ebpf.AttachTraceFEntry, ebpf.AttachTraceFExit, ebpf.AttachTraceRawTp,
ebpf.AttachModifyReturn, ebpf.AttachLSMMac:
// Attach via BPF link
fd, err = sys.LinkCreateTracing(&sys.LinkCreateTracingAttr{
ProgFd: uint32(program.FD()),
AttachType: sys.AttachType(at),
Cookie: cookie,
})
if err == nil {
break
}
if !errors.Is(err, unix.EINVAL) && !errors.Is(err, sys.ENOTSUPP) {
return nil, fmt.Errorf("create tracing link: %w", err)
}
fallthrough
case ebpf.AttachNone:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason the default attach type can't use bpf_link?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AttachType needs to be specified in LinkCreateTracingAttr

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://elixir.bootlin.com/linux/v6.2.2/source/kernel/bpf/syscall.c#L4577

		ret = bpf_tracing_prog_attach(prog,
					      attr->link_create.target_fd,
					      attr->link_create.target_btf_id,
					      attr->link_create.tracing.cookie);

https://elixir.bootlin.com/linux/v6.2.2/source/kernel/bpf/syscall.c#L4595

			ret = bpf_tracing_prog_attach(prog,
						      attr->link_create.target_fd,
						      attr->link_create.target_btf_id,
						      attr->link_create.tracing.cookie);

https://elixir.bootlin.com/linux/v6.2.2/source/kernel/bpf/syscall.c#L3310

		return bpf_tracing_prog_attach(prog, 0, 0, 0);
  • Both RAW_TRACEPOINT_OPEN and BPF_LINK_CREATE return a link fd (on newer kernels)
  • But RAW_TRACEPOINT_OPEN doesn't allow specifying target or cookie

On new kernels, RAW_TRACEPOINT_OPEN == BPF_LINK_CREATE if target_fd, cookie, etc. are 0.

Thinking through the compatibility story here: seems like specifying AttachType on a kernel without LINK_CREATE will fail. So it's not possible to just always set AttachType if you want maximum compat, instead you should only set it if Cookie != 0. Can we do better?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, to recap:

  • on new kernels it's always better to use LINK_CREATE as it behaves the same way if cookie etc. are 0
  • on old kernels we can fail and fallback (fallthrough) to RAW_TRACEPOINT_OPEN

seems like specifying AttachType on a kernel without LINK_CREATE will fail

this should be handled by fallthrough, right?

So it's not possible to just always set AttachType if you want maximum compat, instead you should only set it if Cookie != 0

could you clarify this point? If I set opts.AttachType on a kernel that doesn't support LINK_CREATE, I expect it to fail and fallback to RAW_TRACEPOINT_OPEN

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be handled by fallthrough, right?

yes, my bad, sorry!

  • on new kernels it's always better to use LINK_CREATE as it behaves the same way if cookie etc. are 0

Nit: seems to me like LINK_CREATE vs RAW_TRACEPOINT_OPEN makes no difference if cookie is 0. They end up calling the same function?

Agreed that we should prefer LINK_CREATE.

// Attach via RawTracepointOpen
if cookie > 0 {
return nil, fmt.Errorf("create raw tracepoint with cookie: %w", ErrNotSupported)
}

fd, err = sys.RawTracepointOpen(&sys.RawTracepointOpenAttr{
ProgFd: uint32(program.FD()),
})
if errors.Is(err, sys.ENOTSUPP) {
// This may be returned by bpf_tracing_prog_attach via bpf_arch_text_poke.
return nil, fmt.Errorf("create raw tracepoint: %w", ErrNotSupported)
}
if err != nil {
return nil, fmt.Errorf("create raw tracepoint: %w", err)
}
default:
return nil, fmt.Errorf("invalid attach type: %s", at.String())
}

raw := RawLink{fd: fd}
Expand All @@ -124,8 +167,7 @@ func attachBTFID(program *ebpf.Program) (Link, error) {
// a raw_tracepoint link. Other types return a tracing link.
return &rawTracepoint{raw}, nil
}

return &tracing{RawLink: RawLink{fd: fd}}, nil
return &tracing{raw}, nil
}

// AttachTracing links a tracing (fentry/fexit/fmod_ret) BPF program or
Expand All @@ -136,7 +178,14 @@ func AttachTracing(opts TracingOptions) (Link, error) {
return nil, fmt.Errorf("invalid program type %s, expected Tracing", t)
}

return attachBTFID(opts.Program)
switch opts.AttachType {
case ebpf.AttachTraceFEntry, ebpf.AttachTraceFExit, ebpf.AttachModifyReturn,
ebpf.AttachTraceRawTp, ebpf.AttachNone:
default:
return nil, fmt.Errorf("invalid attach type: %s", opts.AttachType.String())
}

return attachBTFID(opts.Program, opts.AttachType, opts.Cookie)
}

// AttachLSM links a Linux security module (LSM) BPF Program to a BPF
Expand All @@ -146,5 +195,5 @@ func AttachLSM(opts LSMOptions) (Link, error) {
return nil, fmt.Errorf("invalid program type %s, expected LSM", t)
}

return attachBTFID(opts.Program)
return attachBTFID(opts.Program, ebpf.AttachLSMMac, opts.Cookie)
}
64 changes: 41 additions & 23 deletions link/tracing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,48 +51,66 @@ func TestTracing(t *testing.T) {
testutils.SkipOnOldKernel(t, "5.11", "BPF_LINK_TYPE_TRACING")

tests := []struct {
name string
attachTo string
programType ebpf.ProgramType
attachType ebpf.AttachType
name string
attachTo string
programType ebpf.ProgramType
programAttachType, attachTypeOpt ebpf.AttachType
cookie uint64
}{
{
name: "AttachTraceFEntry",
attachTo: "inet_dgram_connect",
programType: ebpf.Tracing,
attachType: ebpf.AttachTraceFEntry,
name: "AttachTraceFEntry",
attachTo: "inet_dgram_connect",
programType: ebpf.Tracing,
programAttachType: ebpf.AttachTraceFEntry,
},
{
name: "AttachTraceFExit",
attachTo: "inet_dgram_connect",
programType: ebpf.Tracing,
attachType: ebpf.AttachTraceFExit,
name: "AttachTraceFEntry",
attachTo: "inet_dgram_connect",
programType: ebpf.Tracing,
programAttachType: ebpf.AttachTraceFEntry,
attachTypeOpt: ebpf.AttachTraceFEntry,
cookie: 1,
},
{
name: "AttachModifyReturn",
attachTo: "bpf_modify_return_test",
programType: ebpf.Tracing,
attachType: ebpf.AttachModifyReturn,
name: "AttachTraceFEntry",
attachTo: "inet_dgram_connect",
programType: ebpf.Tracing,
programAttachType: ebpf.AttachTraceFEntry,
},
{
name: "AttachTraceRawTp",
attachTo: "kfree_skb",
programType: ebpf.Tracing,
attachType: ebpf.AttachTraceRawTp,
name: "AttachTraceFExit",
attachTo: "inet_dgram_connect",
programType: ebpf.Tracing,
programAttachType: ebpf.AttachTraceFExit,
},
{
name: "AttachModifyReturn",
attachTo: "bpf_modify_return_test",
programType: ebpf.Tracing,
programAttachType: ebpf.AttachModifyReturn,
},
{
name: "AttachTraceRawTp",
attachTo: "kfree_skb",
programType: ebpf.Tracing,
programAttachType: ebpf.AttachTraceRawTp,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
prog := mustLoadProgram(t, tt.programType, tt.attachType, tt.attachTo)
prog := mustLoadProgram(t, tt.programType, tt.programAttachType, tt.attachTo)

link, err := AttachTracing(TracingOptions{Program: prog})
opts := TracingOptions{Program: prog, AttachType: tt.attachTypeOpt, Cookie: tt.cookie}
link, err := AttachTracing(opts)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal(err)
}

testLink(t, link, prog)
if err = link.Close(); err != nil {
t.Fatal(err)
}
})
}
}
Expand Down