Repository: cilium/ebpf
Pull request #163 (2020-11-17 10:14:49)

Various doc link fixes
See individual commit messages for details.


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/map.go b/map.go
index bb87977..5c9028d 100644
--- a/map.go
+++ b/map.go
@@ -664,7 +664,7 @@ func (m *Map) Clone() (*Map, error) {
 
 // Pin persists the map past the lifetime of the process that created it.
 //
-// This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional
+// This requires bpffs to be mounted above fileName. See https://docs.cilium.io/en/k8s-doc/admin/#admin-mount-bpffs
 func (m *Map) Pin(fileName string) error {
 	return internal.BPFObjPin(fileName, m.fd)
 }
diff --git a/prog.go b/prog.go
index 95ee2c5..67cfff6 100644
--- a/prog.go
+++ b/prog.go
@@ -322,7 +322,7 @@ func (p *Program) Clone() (*Program, error) {
 
 // Pin persists the Program past the lifetime of the process that created it
 //
-// This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional
+// This requires bpffs to be mounted above fileName. See https://docs.cilium.io/en/k8s-doc/admin/#admin-mount-bpffs
 func (p *Program) Pin(fileName string) error {
 	if err := internal.BPFObjPin(fileName, p.fd); err != nil {
 		return fmt.Errorf("can't pin program: %w", err)
diff --git a/readme.md b/readme.md
index 0c6614b..298db57 100644
--- a/readme.md
+++ b/readme.md
@@ -23,6 +23,7 @@ right now**. Expect to update your code if you want to follow along.
 
 ## Useful resources
 
-* [Cilium eBPF documentation](https://cilium.readthedocs.io/en/latest/bpf/#bpf-guide) (recommended)
+* [eBPF.io](https://ebpf.io) (recommended)
+* [Cilium eBPF documentation](https://docs.cilium.io/en/latest/bpf/#bpf-guide) (recommended)
 * [Linux documentation on BPF](http://elixir.free-electrons.com/linux/latest/source/Documentation/networking/filter.txt)
 * [eBPF features by Linux version](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md)
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()
+}