Repository: cilium/ebpf
Pull request #486 (2021-11-15 11:58:10)

link: clean up tracing and raw_tp links
The link package currently has `FreplaceLink` and `btfIDLink`, however those types don't map cleanly to what the kernel exposes.

By looking at their bpf_link_type we can discover the following:
* freplaceLink has type BPF_LINK_TRACING and is created via BPF_LINK_CREATE
* btfIDLink usually has type BPF_LINK_TRACING and is created via BPF_RAW_TRACEPOINT_OPEN
* BPF_RAW_TRACEPOINT_OPEN returns type BPF_LINK_RAW_TP for some program types

Reorganise the code to always use the same Go type for a given bpf_link_type. This means `btfIDLink` is gone, we instead use `rawTracepoint` and `tracing` as appropriate.

lmb: cc @mehrdadrad please take a look.

Patch:
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 6cbb31b..8cd7e24 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -78,3 +78,9 @@ tend to use bpf_link to do so. Older hooks unfortunately use a combination of
 syscalls, netlink messages, etc. Adding support for a new link type should not
 pull in large dependencies like netlink, so XDP programs or tracepoints are
 out of scope.
+
+Each bpf_link_type has one corresponding Go type, e.g. `link.tracing` corresponds
+to BPF_LINK_TRACING. In general, these types should be unexported as long as they
+don't export methods outside of the Link interface. Each Go type may have multiple
+exported constructors. For example `AttachTracing` and `AttachLSM` create a
+tracing link, but are distinct functions since they may require different arguments.
diff --git a/link/btf_id.go b/link/btf_id.go
deleted file mode 100644
index a0392ae..0000000
--- a/link/btf_id.go
+++ /dev/null
@@ -1,85 +0,0 @@
-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/link/freplace.go b/link/freplace.go
deleted file mode 100644
index a698e1a..0000000
--- a/link/freplace.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package link
-
-import (
-	"fmt"
-
-	"github.com/cilium/ebpf"
-	"github.com/cilium/ebpf/internal/btf"
-)
-
-type FreplaceLink struct {
-	RawLink
-}
-
-// AttachFreplace attaches the given eBPF program to the function it replaces.
-//
-// The program and name can either be provided at link time, or can be provided
-// at program load time. If they were provided at load time, they should be nil
-// and empty respectively here, as they will be ignored by the kernel.
-// Examples:
-//
-//	AttachFreplace(dispatcher, "function", replacement)
-//	AttachFreplace(nil, "", replacement)
-func AttachFreplace(targetProg *ebpf.Program, name string, prog *ebpf.Program) (*FreplaceLink, error) {
-	if (name == "") != (targetProg == nil) {
-		return nil, fmt.Errorf("must provide both or neither of name and targetProg: %w", errInvalidInput)
-	}
-	if prog == nil {
-		return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput)
-	}
-	if prog.Type() != ebpf.Extension {
-		return nil, fmt.Errorf("eBPF program type %s is not an Extension: %w", prog.Type(), errInvalidInput)
-	}
-
-	var (
-		target int
-		typeID btf.TypeID
-	)
-	if targetProg != nil {
-		info, err := targetProg.Info()
-		if err != nil {
-			return nil, err
-		}
-		btfID, ok := info.BTFID()
-		if !ok {
-			return nil, fmt.Errorf("could not get BTF ID for program %s: %w", info.Name, errInvalidInput)
-		}
-		btfHandle, err := btf.NewHandleFromID(btfID)
-		if err != nil {
-			return nil, err
-		}
-		defer btfHandle.Close()
-
-		var function *btf.Func
-		if err := btfHandle.Spec().FindType(name, &function); err != nil {
-			return nil, err
-		}
-
-		target = targetProg.FD()
-		typeID = function.ID()
-	}
-
-	link, err := AttachRawLink(RawLinkOptions{
-		Target:  target,
-		Program: prog,
-		Attach:  ebpf.AttachNone,
-		BTF:     typeID,
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	return &FreplaceLink{*link}, nil
-}
-
-// Update implements the Link interface.
-func (f *FreplaceLink) Update(new *ebpf.Program) error {
-	return fmt.Errorf("freplace update: %w", ErrNotSupported)
-}
-
-// LoadPinnedFreplace loads a pinned iterator from a bpffs.
-func LoadPinnedFreplace(fileName string, opts *ebpf.LoadPinOptions) (*FreplaceLink, error) {
-	link, err := LoadPinnedRawLink(fileName, TracingType, opts)
-	if err != nil {
-		return nil, err
-	}
-
-	return &FreplaceLink{*link}, err
-}
diff --git a/link/raw_tracepoint.go b/link/raw_tracepoint.go
index cd9b21a..183994e 100644
--- a/link/raw_tracepoint.go
+++ b/link/raw_tracepoint.go
@@ -1,6 +1,7 @@
 package link
 
 import (
+	"errors"
 	"fmt"
 
 	"github.com/cilium/ebpf"
@@ -33,29 +34,60 @@ func AttachRawTracepoint(opts RawTracepointOptions) (Link, error) {
 		return nil, err
 	}
 
-	return &progAttachRawTracepoint{fd: fd}, nil
+	err = haveBPFLink()
+	if errors.Is(err, ErrNotSupported) {
+		// Prior to commit 70ed506c3bbc ("bpf: Introduce pinnable bpf_link abstraction")
+		// raw_tracepoints are just a plain fd.
+		return &simpleRawTracepoint{fd}, nil
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	return &rawTracepoint{RawLink{fd: fd}}, nil
 }
 
-type progAttachRawTracepoint struct {
+// LoadPinnedRawTracepoint loads a raw_tracepoint link from a bpffs.
+func LoadPinnedRawTracepoint(fileName string, opts *ebpf.LoadPinOptions) (Link, error) {
+	link, err := LoadPinnedRawLink(fileName, RawTracepointType, opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return &rawTracepoint{*link}, err
+}
+
+type simpleRawTracepoint struct {
 	fd *sys.FD
 }
 
-var _ Link = (*progAttachRawTracepoint)(nil)
+var _ Link = (*simpleRawTracepoint)(nil)
 
-func (rt *progAttachRawTracepoint) isLink() {}
+func (frt *simpleRawTracepoint) isLink() {}
 
-func (rt *progAttachRawTracepoint) Close() error {
-	return rt.fd.Close()
+func (frt *simpleRawTracepoint) Close() error {
+	return frt.fd.Close()
 }
 
-func (rt *progAttachRawTracepoint) Update(_ *ebpf.Program) error {
-	return fmt.Errorf("can't update raw_tracepoint: %w", ErrNotSupported)
+func (frt *simpleRawTracepoint) Update(_ *ebpf.Program) error {
+	return fmt.Errorf("update raw_tracepoint: %w", ErrNotSupported)
 }
 
-func (rt *progAttachRawTracepoint) Pin(_ string) error {
-	return fmt.Errorf("can't pin raw_tracepoint: %w", ErrNotSupported)
+func (frt *simpleRawTracepoint) Pin(string) error {
+	return fmt.Errorf("pin raw_tracepoint: %w", ErrNotSupported)
 }
 
-func (rt *progAttachRawTracepoint) Unpin() error {
+func (frt *simpleRawTracepoint) Unpin() error {
 	return fmt.Errorf("unpin raw_tracepoint: %w", ErrNotSupported)
 }
+
+type rawTracepoint struct {
+	RawLink
+}
+
+var _ Link = (*rawTracepoint)(nil)
+
+func (rt *rawTracepoint) Update(_ *ebpf.Program) error {
+	return fmt.Errorf("update raw_tracepoint: %w", ErrNotSupported)
+}
diff --git a/link/tracing.go b/link/tracing.go
new file mode 100644
index 0000000..0d3e371
--- /dev/null
+++ b/link/tracing.go
@@ -0,0 +1,161 @@
+package link
+
+import (
+	"fmt"
+
+	"github.com/cilium/ebpf"
+	"github.com/cilium/ebpf/internal/btf"
+	"github.com/cilium/ebpf/internal/sys"
+)
+
+type tracing struct {
+	RawLink
+}
+
+func (f *tracing) Update(new *ebpf.Program) error {
+	return fmt.Errorf("tracing update: %w", ErrNotSupported)
+}
+
+// AttachFreplace attaches the given eBPF program to the function it replaces.
+//
+// The program and name can either be provided at link time, or can be provided
+// at program load time. If they were provided at load time, they should be nil
+// and empty respectively here, as they will be ignored by the kernel.
+// Examples:
+//
+//	AttachFreplace(dispatcher, "function", replacement)
+//	AttachFreplace(nil, "", replacement)
+func AttachFreplace(targetProg *ebpf.Program, name string, prog *ebpf.Program) (Link, error) {
+	if (name == "") != (targetProg == nil) {
+		return nil, fmt.Errorf("must provide both or neither of name and targetProg: %w", errInvalidInput)
+	}
+	if prog == nil {
+		return nil, fmt.Errorf("prog cannot be nil: %w", errInvalidInput)
+	}
+	if prog.Type() != ebpf.Extension {
+		return nil, fmt.Errorf("eBPF program type %s is not an Extension: %w", prog.Type(), errInvalidInput)
+	}
+
+	var (
+		target int
+		typeID btf.TypeID
+	)
+	if targetProg != nil {
+		info, err := targetProg.Info()
+		if err != nil {
+			return nil, err
+		}
+		btfID, ok := info.BTFID()
+		if !ok {
+			return nil, fmt.Errorf("could not get BTF ID for program %s: %w", info.Name, errInvalidInput)
+		}
+		btfHandle, err := btf.NewHandleFromID(btfID)
+		if err != nil {
+			return nil, err
+		}
+		defer btfHandle.Close()
+
+		var function *btf.Func
+		if err := btfHandle.Spec().FindType(name, &function); err != nil {
+			return nil, err
+		}
+
+		target = targetProg.FD()
+		typeID = function.ID()
+	}
+
+	link, err := AttachRawLink(RawLinkOptions{
+		Target:  target,
+		Program: prog,
+		Attach:  ebpf.AttachNone,
+		BTF:     typeID,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return &tracing{*link}, nil
+}
+
+// LoadPinnedFreplace loads a pinned iterator from a bpffs.
+func LoadPinnedFreplace(fileName string, opts *ebpf.LoadPinOptions) (Link, error) {
+	link, err := LoadPinnedRawLink(fileName, TracingType, opts)
+	if err != nil {
+		return nil, err
+	}
+
+	return &tracing{*link}, err
+}
+
+type TracingOptions 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
+}
+
+// attachBTFID links all BPF program types (Tracing/LSM) that they attach to a btf_id.
+func attachBTFID(program *ebpf.Program) (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 err != nil {
+		return nil, err
+	}
+
+	raw := RawLink{fd: fd}
+	info, err := raw.Info()
+	if err != nil {
+		raw.Close()
+		return nil, err
+	}
+
+	if info.Type == RawTracepointType {
+		// Sadness upon sadness: a Tracing program with AttachRawTp returns
+		// a raw_tracepoint link. Other types return a tracing link.
+		return &rawTracepoint{raw}, nil
+	}
+
+	return &tracing{RawLink: RawLink{fd: fd}}, nil
+}
+
+// AttachTracing 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 AttachTracing(opts TracingOptions) (Link, error) {
+	if t := opts.Program.Type(); t != ebpf.Tracing {
+		return nil, fmt.Errorf("invalid program type %s, expected Tracing", t)
+	}
+
+	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) {
+	if t := opts.Program.Type(); t != ebpf.LSM {
+		return nil, fmt.Errorf("invalid program type %s, expected LSM", t)
+	}
+
+	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 &tracing{*link}, err
+}


Test patch:
diff --git a/internal/testutils/feature.go b/internal/testutils/feature.go
index 70dbfe3..86092c4 100644
--- a/internal/testutils/feature.go
+++ b/internal/testutils/feature.go
@@ -33,6 +33,12 @@ func CheckFeatureTest(t *testing.T, fn func() error) {
 }
 
 func SkipIfNotSupported(tb testing.TB, err error) {
+	tb.Helper()
+
+	if err == internal.ErrNotSupported {
+		tb.Fatal("Unwrapped ErrNotSupported")
+	}
+
 	var ufe *internal.UnsupportedFeatureError
 	if errors.As(err, &ufe) {
 		checkKernelVersion(tb, ufe)
diff --git a/link/btf_id_test.go b/link/btf_id_test.go
deleted file mode 100644
index fc26590..0000000
--- a/link/btf_id_test.go
+++ /dev/null
@@ -1,89 +0,0 @@
-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)
-			}
-		})
-	}
-}
diff --git a/link/freplace_test.go b/link/freplace_test.go
deleted file mode 100644
index 93d5105..0000000
--- a/link/freplace_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package link
-
-import (
-	"testing"
-
-	"github.com/cilium/ebpf"
-	"github.com/cilium/ebpf/internal"
-	"github.com/cilium/ebpf/internal/testutils"
-)
-
-func TestFreplace(t *testing.T) {
-	testutils.SkipOnOldKernel(t, "5.10", "freplace")
-
-	testutils.Files(t, testutils.Glob(t, "../testdata/freplace-*.elf"), func(t *testing.T, file string) {
-		spec, err := ebpf.LoadCollectionSpec(file)
-		if err != nil {
-			t.Fatal("Can't parse ELF:", err)
-		}
-
-		if spec.ByteOrder != internal.NativeEndian {
-			return
-		}
-
-		target, err := ebpf.NewProgram(spec.Programs["sched_process_exec"])
-		testutils.SkipIfNotSupported(t, err)
-		if err != nil {
-			t.Fatal("Can't create target program:", err)
-		}
-		defer target.Close()
-
-		// Test attachment specified at load time
-		spec.Programs["replacement"].AttachTarget = target
-		replacement, err := ebpf.NewProgram(spec.Programs["replacement"])
-		testutils.SkipIfNotSupported(t, err)
-		if err != nil {
-			t.Fatal("Can't create replacement program:", err)
-		}
-		defer replacement.Close()
-
-		freplace, err := AttachFreplace(nil, "", replacement)
-		if err != nil {
-			t.Fatal("Can't create freplace:", err)
-		}
-
-		testLink(t, freplace, testLinkOptions{
-			prog: replacement,
-			loadPinned: func(s string, opts *ebpf.LoadPinOptions) (Link, error) {
-				return LoadPinnedFreplace(s, opts)
-			},
-		})
-	})
-}
diff --git a/link/link_test.go b/link/link_test.go
index f09530a..7db0550 100644
--- a/link/link_test.go
+++ b/link/link_test.go
@@ -129,17 +129,10 @@ func testLink(t *testing.T, link Link, opts testLinkOptions) {
 	}
 	defer os.RemoveAll(tmp)
 
-	path := filepath.Join(tmp, "link")
-	err = link.Pin(path)
-	if err == ErrNotSupported {
-		t.Errorf("%T.Pin returns unwrapped ErrNotSupported", link)
-	}
-
-	if opts.loadPinned == nil {
-		if !errors.Is(err, ErrNotSupported) {
-			t.Errorf("%T.Pin doesn't return ErrNotSupported: %s", link, err)
-		}
-	} else {
+	t.Run("pinning", func(t *testing.T) {
+		path := filepath.Join(tmp, "link")
+		err = link.Pin(path)
+		testutils.SkipIfNotSupported(t, err)
 		if err != nil {
 			t.Fatalf("Can't pin %T: %s", link, err)
 		}
@@ -160,16 +153,11 @@ func testLink(t *testing.T, link Link, opts testLinkOptions) {
 		if !errors.Is(err, unix.EINVAL) {
 			t.Errorf("Loading a pinned %T doesn't respect flags", link)
 		}
-	}
+	})
 
 	t.Run("update", func(t *testing.T) {
 		err := link.Update(opts.prog)
-		if err == ErrNotSupported {
-			t.Fatal("Update returns unwrapped ErrNotSupported", link)
-		}
-		if errors.Is(err, ErrNotSupported) {
-			return
-		}
+		testutils.SkipIfNotSupported(t, err)
 		if err != nil {
 			t.Fatal("Update returns an error:", err)
 		}
diff --git a/link/raw_tracepoint_test.go b/link/raw_tracepoint_test.go
index 7d169c7..470fefb 100644
--- a/link/raw_tracepoint_test.go
+++ b/link/raw_tracepoint_test.go
@@ -35,6 +35,9 @@ func TestRawTracepoint(t *testing.T) {
 
 	testLink(t, link, testLinkOptions{
 		prog: prog,
+		loadPinned: func(s string, opts *ebpf.LoadPinOptions) (Link, error) {
+			return LoadPinnedRawTracepoint(s, opts)
+		},
 	})
 }
 
@@ -65,5 +68,8 @@ func TestRawTracepoint_writable(t *testing.T) {
 
 	testLink(t, link, testLinkOptions{
 		prog: prog,
+		loadPinned: func(s string, opts *ebpf.LoadPinOptions) (Link, error) {
+			return LoadPinnedRawTracepoint(s, opts)
+		},
 	})
 }
diff --git a/link/tracing_test.go b/link/tracing_test.go
new file mode 100644
index 0000000..ab85151
--- /dev/null
+++ b/link/tracing_test.go
@@ -0,0 +1,153 @@
+package link
+
+import (
+	"testing"
+
+	"github.com/cilium/ebpf"
+	"github.com/cilium/ebpf/asm"
+	"github.com/cilium/ebpf/internal"
+	"github.com/cilium/ebpf/internal/testutils"
+)
+
+func TestFreplace(t *testing.T) {
+	testutils.SkipOnOldKernel(t, "5.10", "freplace")
+
+	testutils.Files(t, testutils.Glob(t, "../testdata/freplace-*.elf"), func(t *testing.T, file string) {
+		spec, err := ebpf.LoadCollectionSpec(file)
+		if err != nil {
+			t.Fatal("Can't parse ELF:", err)
+		}
+
+		if spec.ByteOrder != internal.NativeEndian {
+			return
+		}
+
+		target, err := ebpf.NewProgram(spec.Programs["sched_process_exec"])
+		testutils.SkipIfNotSupported(t, err)
+		if err != nil {
+			t.Fatal("Can't create target program:", err)
+		}
+		defer target.Close()
+
+		// Test attachment specified at load time
+		spec.Programs["replacement"].AttachTarget = target
+		replacement, err := ebpf.NewProgram(spec.Programs["replacement"])
+		testutils.SkipIfNotSupported(t, err)
+		if err != nil {
+			t.Fatal("Can't create replacement program:", err)
+		}
+		defer replacement.Close()
+
+		freplace, err := AttachFreplace(nil, "", replacement)
+		if err != nil {
+			t.Fatal("Can't create freplace:", err)
+		}
+
+		testLink(t, freplace, testLinkOptions{
+			prog: replacement,
+			loadPinned: func(s string, opts *ebpf.LoadPinOptions) (Link, error) {
+				return LoadPinnedFreplace(s, opts)
+			},
+		})
+	})
+}
+
+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:        "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,
+		},
+	}
+
+	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: "MIT",
+			})
+			if err != nil {
+				t.Fatal(err)
+			}
+			defer prog.Close()
+
+			link, err := AttachTracing(TracingOptions{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 LoadPinnedRawTracepoint(s, opts)
+				},
+			})
+		})
+	}
+}
+
+func TestLSM(t *testing.T) {
+	testutils.SkipOnOldKernel(t, "5.11", "BPF_LINK_TYPE_TRACING")
+
+	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
+		Type:       ebpf.LSM,
+		AttachType: ebpf.AttachLSMMac,
+		AttachTo:   "file_mprotect",
+		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 := AttachLSM(LSMOptions{Program: prog})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	testLink(t, link, testLinkOptions{
+		prog: prog,
+		loadPinned: func(s string, opts *ebpf.LoadPinOptions) (Link, error) {
+			return LoadPinnedTrace(s, opts)
+		},
+	})
+}