Repository: cilium/ebpf
Pull request #460 (2021-10-26 03:58:29)

Add BTFIDLink to support fentry/fexit/fmod_ret/tp_raw/lsm BPF programs
The attachBTFID links Tracing/LSM BPF programs which they attach to a btf_id.

mehrdadrad: @lmb I recompiled [kernel 5.14.14](https://github.com/cilium/ci-kernels) with adding the below to the config and the tests passed at my local vm through run-tests.sh 5.14 ./link
```
CONFIG_DYNAMIC_FTRACE=y
CONFIG_STACK_TRACER=y
```

Patch:
diff --git a/link/btf_id.go b/link/btf_id.go
new file mode 100644
index 0000000..a0392ae
--- /dev/null
+++ b/link/btf_id.go
@@ -0,0 +1,85 @@
+package link
+
+import (
+	"fmt"
+
+	"github.com/cilium/ebpf"
+	"github.com/cilium/ebpf/internal/sys"
+)
+
+var _ Link = (*btfIDLink)(nil)
+
+// btfIDLink is a program attached to a btf_id.
+type btfIDLink struct {
+	RawLink
+}
+
+type TraceOptions struct {
+	// Program must be of type Tracing with attach type
+	// AttachTraceFEntry/AttachTraceFExit/AttachModifyReturn or
+	// AttachTraceRawTp.
+	Program *ebpf.Program
+}
+type LSMOptions struct {
+	// Program must be of type LSM with attach type
+	// AttachLSMMac.
+	Program *ebpf.Program
+}
+
+// Update implements the Link interface.
+func (*btfIDLink) Update(_ *ebpf.Program) error {
+	return fmt.Errorf("can't update fentry/fexit/fmod_ret/tp_raw/lsm: %w", ErrNotSupported)
+}
+
+// attachBTFID links all BPF program types (Tracing/LSM) that they attach to a btf_id.
+func attachBTFID(program *ebpf.Program) (Link, error) {
+	if t := program.Type(); t != ebpf.Tracing && t != ebpf.LSM {
+		return nil, fmt.Errorf("invalid program type %s, expected Tracing/LSM", t)
+	}
+
+	if program.FD() < 0 {
+		return nil, fmt.Errorf("invalid program %w", sys.ErrClosedFd)
+	}
+
+	fd, err := sys.RawTracepointOpen(&sys.RawTracepointOpenAttr{
+		ProgFd: uint32(program.FD()),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return &btfIDLink{RawLink: RawLink{fd: fd}}, nil
+}
+
+// AttachTrace links a tracing (fentry/fexit/fmod_ret) BPF program or
+// a BTF-powered raw tracepoint (tp_btf) BPF Program to a BPF hook defined
+// in kernel modules.
+func AttachTrace(opts TraceOptions) (Link, error) {
+	return attachBTFID(opts.Program)
+}
+
+// AttachLSM links a Linux security module (LSM) BPF Program to a BPF
+// hook defined in kernel modules.
+func AttachLSM(opts LSMOptions) (Link, error) {
+	return attachBTFID(opts.Program)
+}
+
+// LoadPinnedTrace loads a tracing/LSM link from a bpffs.
+func LoadPinnedTrace(fileName string, opts *ebpf.LoadPinOptions) (Link, error) {
+	link, err := LoadPinnedRawLink(fileName, TracingType, opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return &btfIDLink{*link}, err
+}
+
+// LoadPinnedTraceRawTP loads a tp_btf link from a bpffs.
+func LoadPinnedTraceRawTP(fileName string, opts *ebpf.LoadPinOptions) (Link, error) {
+	link, err := LoadPinnedRawLink(fileName, RawTracepointType, opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return &btfIDLink{*link}, err
+}
diff --git a/prog.go b/prog.go
index afa5bb6..f538730 100644
--- a/prog.go
+++ b/prog.go
@@ -703,7 +703,11 @@ func resolveBTFType(spec *btf.Spec, name string, progType ProgramType, attachTyp
 		a AttachType
 	}
 
-	var typeName, featureName string
+	var (
+		typeName, featureName string
+		isBTFTypeFunc         = true
+	)
+
 	switch (match{progType, attachType}) {
 	case match{LSM, AttachLSMMac}:
 		typeName = "bpf_lsm_" + name
@@ -714,26 +718,50 @@ func resolveBTFType(spec *btf.Spec, name string, progType ProgramType, attachTyp
 	case match{Extension, AttachNone}:
 		typeName = name
 		featureName = fmt.Sprintf("freplace %s", name)
+	case match{Tracing, AttachTraceFEntry}:
+		typeName = name
+		featureName = fmt.Sprintf("fentry %s", name)
+	case match{Tracing, AttachTraceFExit}:
+		typeName = name
+		featureName = fmt.Sprintf("fexit %s", name)
+	case match{Tracing, AttachModifyReturn}:
+		typeName = name
+		featureName = fmt.Sprintf("fmod_ret %s", name)
+	case match{Tracing, AttachTraceRawTp}:
+		typeName = fmt.Sprintf("btf_trace_%s", name)
+		featureName = fmt.Sprintf("raw_tp %s", name)
+		isBTFTypeFunc = false
 	default:
 		return nil, nil
 	}
 
+	var (
+		target btf.Type
+		err    error
+	)
 	if spec == nil {
-		var err error
 		spec, err = btf.LoadKernelSpec()
 		if err != nil {
 			return nil, fmt.Errorf("load kernel spec: %w", err)
 		}
 	}
 
-	var target *btf.Func
-	err := spec.FindType(typeName, &target)
-	if errors.Is(err, btf.ErrNotFound) {
-		return nil, &internal.UnsupportedFeatureError{
-			Name: featureName,
-		}
+	if isBTFTypeFunc {
+		var targetFunc *btf.Func
+		err = spec.FindType(typeName, &targetFunc)
+		target = targetFunc
+	} else {
+		var targetTypedef *btf.Typedef
+		err = spec.FindType(typeName, &targetTypedef)
+		target = targetTypedef
 	}
+
 	if err != nil {
+		if errors.Is(err, btf.ErrNotFound) {
+			return nil, &internal.UnsupportedFeatureError{
+				Name: featureName,
+			}
+		}
 		return nil, fmt.Errorf("resolve BTF for %s: %w", featureName, err)
 	}
 


Test patch:
diff --git a/link/btf_id_test.go b/link/btf_id_test.go
new file mode 100644
index 0000000..fc26590
--- /dev/null
+++ b/link/btf_id_test.go
@@ -0,0 +1,89 @@
+package link
+
+import (
+	"testing"
+
+	"github.com/cilium/ebpf"
+	"github.com/cilium/ebpf/asm"
+	"github.com/cilium/ebpf/internal/testutils"
+)
+
+func TestTraceLSM(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:        "AttachTraceFEntry",
+			attachTo:    "inet_dgram_connect",
+			programType: ebpf.Tracing,
+			attachType:  ebpf.AttachTraceFEntry,
+		},
+		{
+			name:        "AttachTraceFExit",
+			attachTo:    "inet_dgram_connect",
+			programType: ebpf.Tracing,
+			attachType:  ebpf.AttachTraceFExit,
+		},
+		{
+			name:        "AttachModifyReturn",
+			attachTo:    "bpf_modify_return_test",
+			programType: ebpf.Tracing,
+			attachType:  ebpf.AttachModifyReturn,
+		},
+		{
+			name:        "AttachTraceRawTp",
+			attachTo:    "kfree_skb",
+			programType: ebpf.Tracing,
+			attachType:  ebpf.AttachTraceRawTp,
+		},
+		{
+			name:        "AttachLSMMac",
+			attachTo:    "file_mprotect",
+			programType: ebpf.LSM,
+			attachType:  ebpf.AttachLSMMac,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
+				Type:       tt.programType,
+				AttachType: tt.attachType,
+				AttachTo:   tt.attachTo,
+				Instructions: asm.Instructions{
+					asm.LoadImm(asm.R0, 0, asm.DWord),
+					asm.Return(),
+				},
+				License: "GPL",
+			})
+			if err != nil {
+				t.Fatal(err)
+			}
+			defer prog.Close()
+
+			link, err := AttachTrace(TraceOptions{Program: prog})
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			testLink(t, link, testLinkOptions{
+				prog: prog,
+				loadPinned: func(s string, opts *ebpf.LoadPinOptions) (Link, error) {
+					if tt.attachType != ebpf.AttachTraceRawTp {
+						return LoadPinnedTrace(s, opts)
+					}
+					return LoadPinnedTraceRawTP(s, opts)
+				},
+			})
+
+			err = link.Close()
+			if err != nil {
+				t.Fatal(err)
+			}
+		})
+	}
+}