Repository: cilium/ebpf
Pull request #158 (2020-11-11 11:46:12)

link: add AttachSkLookup
Add a wrapper for SkLookup programs that allows retrieving information about the link.

jsitnicki: Looks like nothing in `link/sk_lookup.go` is specific to BPF sk_lookup. BPF flow_dissector would be attached and loaded from pinned link the same way. Have you considered orienting it around BPF link type (`BPF_LINK_TYPE_NETNS`) instead of BPF program type?
lmb: Good point, I'll do that.
lmb: Fixed, PTAL.

Patch:
diff --git a/internal/syscall.go b/internal/syscall.go
index efbf403..9c5e0b3 100644
--- a/internal/syscall.go
+++ b/internal/syscall.go
@@ -137,3 +137,30 @@ func BPFObjGet(fileName string) (*FD, error) {
 	}
 	return NewFD(uint32(ptr)), nil
 }
+
+type bpfObjGetInfoByFDAttr struct {
+	fd      uint32
+	infoLen uint32
+	info    Pointer
+}
+
+// BPFObjGetInfoByFD wraps BPF_OBJ_GET_INFO_BY_FD.
+//
+// Available from 4.13.
+func BPFObjGetInfoByFD(fd *FD, info unsafe.Pointer, size uintptr) error {
+	value, err := fd.Value()
+	if err != nil {
+		return err
+	}
+
+	attr := bpfObjGetInfoByFDAttr{
+		fd:      value,
+		infoLen: uint32(size),
+		info:    NewPointer(info),
+	}
+	_, err = BPF(BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
+	if err != nil {
+		return fmt.Errorf("fd %v: %w", fd, err)
+	}
+	return nil
+}
diff --git a/link/link.go b/link/link.go
index 1a1ba33..bfc0380 100644
--- a/link/link.go
+++ b/link/link.go
@@ -2,6 +2,7 @@ package link
 
 import (
 	"fmt"
+	"unsafe"
 
 	"github.com/cilium/ebpf"
 	"github.com/cilium/ebpf/internal"
@@ -32,6 +33,9 @@ type Link interface {
 	isLink()
 }
 
+// ID uniquely identifies a BPF link.
+type ID uint32
+
 // RawLinkOptions control the creation of a raw link.
 type RawLinkOptions struct {
 	// File descriptor to attach to. This differs for each attach type.
@@ -42,6 +46,13 @@ type RawLinkOptions struct {
 	Attach ebpf.AttachType
 }
 
+// RawLinkInfo contains metadata on a link.
+type RawLinkInfo struct {
+	Type    Type
+	ID      ID
+	Program ebpf.ProgramID
+}
+
 // RawLink is the low-level API to bpf_link.
 //
 // You should consider using the higher level interfaces in this
@@ -80,12 +91,32 @@ func AttachRawLink(opts RawLinkOptions) (*RawLink, error) {
 
 // LoadPinnedRawLink loads a persisted link from a bpffs.
 func LoadPinnedRawLink(fileName string) (*RawLink, error) {
+	return loadPinnedRawLink(fileName, UnspecifiedType)
+}
+
+func loadPinnedRawLink(fileName string, typ Type) (*RawLink, error) {
 	fd, err := internal.BPFObjGet(fileName)
 	if err != nil {
-		return nil, fmt.Errorf("can't load pinned link: %s", err)
+		return nil, fmt.Errorf("load pinned link: %s", err)
 	}
 
-	return &RawLink{fd}, nil
+	link := &RawLink{fd}
+	if typ == UnspecifiedType {
+		return link, nil
+	}
+
+	info, err := link.Info()
+	if err != nil {
+		link.Close()
+		return nil, fmt.Errorf("get pinned link info: %s", err)
+	}
+
+	if info.Type != typ {
+		link.Close()
+		return nil, fmt.Errorf("link type %v doesn't match %v", info.Type, typ)
+	}
+
+	return link, nil
 }
 
 func (l *RawLink) isLink() {}
@@ -159,3 +190,25 @@ func (l *RawLink) UpdateArgs(opts RawLinkUpdateOptions) error {
 	}
 	return bpfLinkUpdate(&attr)
 }
+
+// struct bpf_link_info
+type bpfLinkInfo struct {
+	typ     uint32
+	id      uint32
+	prog_id uint32
+}
+
+// Info returns metadata about the link.
+func (l *RawLink) Info() (*RawLinkInfo, error) {
+	var info bpfLinkInfo
+	err := internal.BPFObjGetInfoByFD(l.fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
+	if err != nil {
+		return nil, fmt.Errorf("link info: %s", err)
+	}
+
+	return &RawLinkInfo{
+		Type(info.typ),
+		ID(info.id),
+		ebpf.ProgramID(info.prog_id),
+	}, nil
+}
diff --git a/link/netns.go b/link/netns.go
new file mode 100644
index 0000000..3533ff0
--- /dev/null
+++ b/link/netns.go
@@ -0,0 +1,60 @@
+package link
+
+import (
+	"fmt"
+
+	"github.com/cilium/ebpf"
+)
+
+// NetNsInfo contains metadata about a network namespace link.
+type NetNsInfo struct {
+	RawLinkInfo
+}
+
+// NetNsLink is a program attached to a network namespace.
+type NetNsLink struct {
+	*RawLink
+}
+
+// AttachNetNs attaches a program to a network namespace.
+func AttachNetNs(ns int, prog *ebpf.Program) (*NetNsLink, error) {
+	var attach ebpf.AttachType
+	switch t := prog.Type(); t {
+	case ebpf.FlowDissector:
+		attach = ebpf.AttachFlowDissector
+	case ebpf.SkLookup:
+		attach = ebpf.AttachSkLookup
+	default:
+		return nil, fmt.Errorf("can't attach %v to network namespace", t)
+	}
+
+	link, err := AttachRawLink(RawLinkOptions{
+		Target:  ns,
+		Program: prog,
+		Attach:  attach,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return &NetNsLink{link}, nil
+}
+
+// LoadPinnedNetNs loads a network namespace link from bpffs.
+func LoadPinnedNetNs(fileName string) (*NetNsLink, error) {
+	link, err := loadPinnedRawLink(fileName, NetNsType)
+	if err != nil {
+		return nil, err
+	}
+
+	return &NetNsLink{link}, nil
+}
+
+// Info returns information about the link.
+func (nns *NetNsLink) Info() (*NetNsInfo, error) {
+	info, err := nns.RawLink.Info()
+	if err != nil {
+		return nil, err
+	}
+	return &NetNsInfo{*info}, nil
+}
diff --git a/link/syscalls.go b/link/syscalls.go
index b44ee81..56ca7fc 100644
--- a/link/syscalls.go
+++ b/link/syscalls.go
@@ -10,6 +10,22 @@ import (
 	"github.com/cilium/ebpf/internal/unix"
 )
 
+// Type is the kind of link.
+type Type uint32
+
+// Valid link types.
+//
+// Equivalent to enum bpf_link_type.
+const (
+	UnspecifiedType Type = iota
+	RawTracepointType
+	TracingType
+	CgroupType
+	IterType
+	NetNsType
+	XDPType
+)
+
 var haveProgAttach = internal.FeatureTest("BPF_PROG_ATTACH", "4.10", func() (bool, error) {
 	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
 		Type:       ebpf.CGroupSKB,
diff --git a/syscalls.go b/syscalls.go
index ab66edc..ff5c8e6 100644
--- a/syscalls.go
+++ b/syscalls.go
@@ -129,12 +129,6 @@ type bpfProgTestRunAttr struct {
 	duration    uint32
 }
 
-type bpfObjGetInfoByFDAttr struct {
-	fd      uint32
-	infoLen uint32
-	info    internal.Pointer // May be either bpfMapInfo or bpfProgInfo
-}
-
 type bpfGetFDByIDAttr struct {
 	id   uint32
 	next uint32
@@ -353,28 +347,9 @@ func bpfMapFreeze(m *internal.FD) error {
 	return err
 }
 
-func bpfGetObjectInfoByFD(fd *internal.FD, info unsafe.Pointer, size uintptr) error {
-	value, err := fd.Value()
-	if err != nil {
-		return err
-	}
-
-	// available from 4.13
-	attr := bpfObjGetInfoByFDAttr{
-		fd:      value,
-		infoLen: uint32(size),
-		info:    internal.NewPointer(info),
-	}
-	_, err = internal.BPF(internal.BPF_OBJ_GET_INFO_BY_FD, unsafe.Pointer(&attr), unsafe.Sizeof(attr))
-	if err != nil {
-		return fmt.Errorf("fd %v: %w", fd, err)
-	}
-	return nil
-}
-
 func bpfGetProgInfoByFD(fd *internal.FD) (*bpfProgInfo, error) {
 	var info bpfProgInfo
-	if err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil {
+	if err := internal.BPFObjGetInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)); err != nil {
 		return nil, fmt.Errorf("can't get program info: %w", err)
 	}
 	return &info, nil
@@ -382,7 +357,7 @@ func bpfGetProgInfoByFD(fd *internal.FD) (*bpfProgInfo, error) {
 
 func bpfGetMapInfoByFD(fd *internal.FD) (*bpfMapInfo, error) {
 	var info bpfMapInfo
-	err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
+	err := internal.BPFObjGetInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info))
 	if err != nil {
 		return nil, fmt.Errorf("can't get map info: %w", err)
 	}


Test patch:
diff --git a/link/link_test.go b/link/link_test.go
index cc0a587..2a71cf2 100644
--- a/link/link_test.go
+++ b/link/link_test.go
@@ -27,6 +27,18 @@ func TestRawLink(t *testing.T) {
 		t.Fatal("Can't create raw link:", err)
 	}
 
+	info, err := link.Info()
+	if err != nil {
+		t.Fatal("Can't get info:", err)
+	}
+	progID, err := prog.ID()
+	if err != nil {
+		t.Fatal("Can't get program ID:", err)
+	}
+	if info.Program != progID {
+		t.Error("Link program ID doesn't match program ID")
+	}
+
 	testLink(t, link, testLinkOptions{
 		prog: prog,
 		loadPinned: func(f string) (Link, error) {
diff --git a/link/netns_test.go b/link/netns_test.go
new file mode 100644
index 0000000..5004f05
--- /dev/null
+++ b/link/netns_test.go
@@ -0,0 +1,76 @@
+package link
+
+import (
+	"os"
+	"testing"
+
+	"github.com/cilium/ebpf"
+	"github.com/cilium/ebpf/asm"
+	"github.com/cilium/ebpf/internal/testutils"
+)
+
+func TestSkLookup(t *testing.T) {
+	testutils.SkipOnOldKernel(t, "5.8", "sk_lookup program")
+
+	prog := createSkLookupProgram()
+	defer prog.Close()
+
+	netns, err := os.Open("/proc/self/ns/net")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer netns.Close()
+
+	link, err := AttachNetNs(int(netns.Fd()), prog)
+	if err != nil {
+		t.Fatal("Can't attach link:", err)
+	}
+
+	_, err = link.Info()
+	if err != nil {
+		t.Fatal("Info returns an error:", err)
+	}
+
+	testLink(t, link, testLinkOptions{
+		prog: prog,
+		loadPinned: func(fileName string) (Link, error) {
+			return LoadPinnedNetNs(fileName)
+		},
+	})
+}
+
+func createSkLookupProgram() *ebpf.Program {
+	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
+		Type:       ebpf.SkLookup,
+		AttachType: ebpf.AttachSkLookup,
+		License:    "MIT",
+		Instructions: asm.Instructions{
+			asm.Mov.Imm(asm.R0, 0),
+			asm.Return(),
+		},
+	})
+	if err != nil {
+		panic(err)
+	}
+	return prog
+}
+
+func ExampleAttachNetNs() {
+	prog := createSkLookupProgram()
+	defer prog.Close()
+
+	// This can be a path to another netns as well.
+	netns, err := os.Open("/proc/self/ns/net")
+	if err != nil {
+		panic(err)
+	}
+	defer netns.Close()
+
+	link, err := AttachNetNs(int(netns.Fd()), prog)
+	if err != nil {
+		panic(err)
+	}
+
+	// The socket lookup program is now active until Close().
+	link.Close()
+}