Repository: cilium/ebpf
Pull request #1295 (2024-01-05 14:25:48)

Add support to retrieve missed counts from probes
adding support to get info data from kprobe_multi/perf_events probes, using it in tetragon [1941](https://github.com/cilium/tetragon/pull/1941)

rgo3: I will spend some more time next week to work on the ideas Lorenz had to change how ebpf-go represents the `bpf_link_info` struct in Go.
olsajiri: > I will spend some more time next week to work on the ideas Lorenz had to change how ebpf-go represents the `bpf_link_info` struct in Go.

sounds great, please let me know if I can help in any way
olsajiri: @lmb @rgo3 hey guys, could you plz check on the new changes?
I rebased that on the latest and it works now with tetragon https://github.com/cilium/tetragon/pull/1941 thanks
lmb: @olsajiri good to go?

olsajiri: @lmb yes, I have tetragon changes on top of this and it looks good, so looks good to me

Patch:
diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go
index 5aebdf9..3fe7369 100644
--- a/internal/cmd/gentypes/main.go
+++ b/internal/cmd/gentypes/main.go
@@ -122,6 +122,7 @@ import (
 		{"RetCode", "bpf_ret_code"},
 		{"XdpAction", "xdp_action"},
 		{"TcxActionBase", "tcx_action_base"},
+		{"PerfEventType", "bpf_perf_event_type"},
 	}
 
 	sort.Slice(enums, func(i, j int) bool {
@@ -643,6 +644,33 @@ import (
 				replace(enumTypes["AttachType"], "attach_type"),
 			},
 		},
+		{"KprobeMultiLinkInfo",
+			[]patch{
+				choose(3, "kprobe_multi"),
+				flattenAnon,
+				replace(pointer, "addrs"),
+			},
+		},
+		{"PerfEventLinkInfo",
+			[]patch{
+				choose(3, "perf_event"),
+				flattenAnon,
+				renameNth(3, "perf_event_type"),
+				replace(enumTypes["PerfEventType"], "perf_event_type"),
+				truncateAfter("perf_event_type"),
+			},
+		},
+		{"KprobeLinkInfo",
+			[]patch{
+				choose(3, "perf_event"),
+				flattenAnon,
+				renameNth(3, "perf_event_type"),
+				replace(enumTypes["PerfEventType"], "perf_event_type"),
+				choose(4, "kprobe"),
+				flattenAnon,
+				replace(pointer, "func_name"),
+			},
+		},
 	}
 
 	sort.Slice(linkInfoExtraTypes, func(i, j int) bool {
@@ -885,6 +913,16 @@ func rename(from, to string) patch {
 	}
 }
 
+func renameNth(idx int, to string) patch {
+	return func(s *btf.Struct) error {
+		if idx >= len(s.Members) {
+			return fmt.Errorf("index %d is out of bounds", idx)
+		}
+		s.Members[idx].Name = to
+		return nil
+	}
+}
+
 func name(member int, name string) patch {
 	return modifyNth(func(m *btf.Member) error {
 		if m.Name != "" {
diff --git a/internal/sys/syscall.go b/internal/sys/syscall.go
index 2ef8a13..f6b6e93 100644
--- a/internal/sys/syscall.go
+++ b/internal/sys/syscall.go
@@ -99,12 +99,24 @@ func (i *NetkitLinkInfo) info() (unsafe.Pointer, uint32) {
 	return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
 }
 
+func (i *KprobeMultiLinkInfo) info() (unsafe.Pointer, uint32) {
+	return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
+}
+
+func (i *KprobeLinkInfo) info() (unsafe.Pointer, uint32) {
+	return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
+}
+
 var _ Info = (*BtfInfo)(nil)
 
 func (i *BtfInfo) info() (unsafe.Pointer, uint32) {
 	return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
 }
 
+func (i *PerfEventLinkInfo) info() (unsafe.Pointer, uint32) {
+	return unsafe.Pointer(i), uint32(unsafe.Sizeof(*i))
+}
+
 // ObjInfo retrieves information about a BPF Fd.
 //
 // info may be one of MapInfo, ProgInfo, LinkInfo and BtfInfo.
diff --git a/internal/sys/types.go b/internal/sys/types.go
index cb63219..d2ae942 100644
--- a/internal/sys/types.go
+++ b/internal/sys/types.go
@@ -402,6 +402,18 @@ const (
 	BPF_MAP_TYPE_CGRP_STORAGE                     MapType = 32
 )
 
+type PerfEventType uint32
+
+const (
+	BPF_PERF_EVENT_UNSPEC     PerfEventType = 0
+	BPF_PERF_EVENT_UPROBE     PerfEventType = 1
+	BPF_PERF_EVENT_URETPROBE  PerfEventType = 2
+	BPF_PERF_EVENT_KPROBE     PerfEventType = 3
+	BPF_PERF_EVENT_KRETPROBE  PerfEventType = 4
+	BPF_PERF_EVENT_TRACEPOINT PerfEventType = 5
+	BPF_PERF_EVENT_EVENT      PerfEventType = 6
+)
+
 type ProgType uint32
 
 const (
@@ -1263,6 +1275,32 @@ type IterLinkInfo struct {
 	TargetNameLen uint32
 }
 
+type KprobeLinkInfo struct {
+	Type          LinkType
+	Id            LinkID
+	ProgId        uint32
+	_             [4]byte
+	PerfEventType PerfEventType
+	_             [4]byte
+	FuncName      Pointer
+	NameLen       uint32
+	Offset        uint32
+	Addr          uint64
+	Missed        uint64
+}
+
+type KprobeMultiLinkInfo struct {
+	Type   LinkType
+	Id     LinkID
+	ProgId uint32
+	_      [4]byte
+	Addrs  Pointer
+	Count  uint32
+	Flags  uint32
+	Missed uint64
+	_      [16]byte
+}
+
 type NetNsLinkInfo struct {
 	Type       LinkType
 	Id         LinkID
@@ -1295,6 +1333,14 @@ type NetkitLinkInfo struct {
 	_          [32]byte
 }
 
+type PerfEventLinkInfo struct {
+	Type          LinkType
+	Id            LinkID
+	ProgId        uint32
+	_             [4]byte
+	PerfEventType PerfEventType
+}
+
 type RawTracepointLinkInfo struct {
 	Type      LinkType
 	Id        LinkID
diff --git a/link/link.go b/link/link.go
index 9ca5e08..8142856 100644
--- a/link/link.go
+++ b/link/link.go
@@ -194,6 +194,48 @@ type NetkitInfo struct {
 	AttachType sys.AttachType
 }
 
+type KprobeMultiInfo struct {
+	count  uint32
+	flags  uint32
+	missed uint64
+}
+
+// AddressCount is the number of addresses hooked by the kprobe.
+func (kpm *KprobeMultiInfo) AddressCount() (uint32, bool) {
+	return kpm.count, kpm.count > 0
+}
+
+func (kpm *KprobeMultiInfo) Flags() (uint32, bool) {
+	return kpm.flags, kpm.count > 0
+}
+
+func (kpm *KprobeMultiInfo) Missed() (uint64, bool) {
+	return kpm.missed, kpm.count > 0
+}
+
+type PerfEventInfo struct {
+	Type  sys.PerfEventType
+	extra interface{}
+}
+
+func (r *PerfEventInfo) Kprobe() *KprobeInfo {
+	e, _ := r.extra.(*KprobeInfo)
+	return e
+}
+
+type KprobeInfo struct {
+	address uint64
+	missed  uint64
+}
+
+func (kp *KprobeInfo) Address() (uint64, bool) {
+	return kp.address, kp.address > 0
+}
+
+func (kp *KprobeInfo) Missed() (uint64, bool) {
+	return kp.missed, kp.address > 0
+}
+
 // Tracing returns tracing type-specific link info.
 //
 // Returns nil if the type-specific link info isn't available.
@@ -250,6 +292,22 @@ func (r Info) Netkit() *NetkitInfo {
 	return e
 }
 
+// KprobeMulti returns kprobe-multi type-specific link info.
+//
+// Returns nil if the type-specific link info isn't available.
+func (r Info) KprobeMulti() *KprobeMultiInfo {
+	e, _ := r.extra.(*KprobeMultiInfo)
+	return e
+}
+
+// PerfEvent returns perf-event type-specific link info.
+//
+// Returns nil if the type-specific link info isn't available.
+func (r Info) PerfEvent() *PerfEventInfo {
+	e, _ := r.extra.(*PerfEventInfo)
+	return e
+}
+
 // RawLink is the low-level API to bpf_link.
 //
 // You should consider using the higher level interfaces in this
@@ -425,8 +483,7 @@ func (l *RawLink) Info() (*Info, error) {
 		extra = &XDPInfo{
 			Ifindex: xdpInfo.Ifindex,
 		}
-	case RawTracepointType, IterType,
-		PerfEventType, KprobeMultiType, UprobeMultiType:
+	case RawTracepointType, IterType, UprobeMultiType:
 		// Extra metadata not supported.
 	case TCXType:
 		var tcxInfo sys.TcxLinkInfo
@@ -457,6 +514,39 @@ func (l *RawLink) Info() (*Info, error) {
 			Ifindex:    netkitInfo.Ifindex,
 			AttachType: netkitInfo.AttachType,
 		}
+	case KprobeMultiType:
+		var kprobeMultiInfo sys.KprobeMultiLinkInfo
+		if err := sys.ObjInfo(l.fd, &kprobeMultiInfo); err != nil {
+			return nil, fmt.Errorf("kprobe multi link info: %s", err)
+		}
+		extra = &KprobeMultiInfo{
+			count:  kprobeMultiInfo.Count,
+			flags:  kprobeMultiInfo.Flags,
+			missed: kprobeMultiInfo.Missed,
+		}
+	case PerfEventType:
+		var perfEventInfo sys.PerfEventLinkInfo
+		if err := sys.ObjInfo(l.fd, &perfEventInfo); err != nil {
+			return nil, fmt.Errorf("perf event link info: %s", err)
+		}
+
+		var extra2 interface{}
+		switch perfEventInfo.PerfEventType {
+		case sys.BPF_PERF_EVENT_KPROBE, sys.BPF_PERF_EVENT_KRETPROBE:
+			var kprobeInfo sys.KprobeLinkInfo
+			if err := sys.ObjInfo(l.fd, &kprobeInfo); err != nil {
+				return nil, fmt.Errorf("kprobe multi link info: %s", err)
+			}
+			extra2 = &KprobeInfo{
+				address: kprobeInfo.Addr,
+				missed:  kprobeInfo.Missed,
+			}
+		}
+
+		extra = &PerfEventInfo{
+			Type:  perfEventInfo.PerfEventType,
+			extra: extra2,
+		}
 	default:
 		return nil, fmt.Errorf("unknown link info type: %d", info.Type)
 	}


Test patch:
diff --git a/link/link_test.go b/link/link_test.go
index c7f39c5..4f516e2 100644
--- a/link/link_test.go
+++ b/link/link_test.go
@@ -275,6 +275,32 @@ func testLink(t *testing.T, link Link, prog *ebpf.Program) {
 			if nf.Priority == 0 {
 				t.Fatalf("Failed to get link Netfilter extra info")
 			}
+		case sys.BPF_LINK_TYPE_KPROBE_MULTI:
+			// test default Info data
+			kmulti := info.KprobeMulti()
+			if count, ok := kmulti.AddressCount(); ok {
+				qt.Assert(t, qt.Not(qt.Equals(count, 0)))
+
+				_, ok = kmulti.Missed()
+				qt.Assert(t, qt.IsTrue(ok))
+				// NB: We don't check that missed is actually correct
+				// since it's not easy to trigger from tests.
+			}
+		case sys.BPF_LINK_TYPE_PERF_EVENT:
+			// test default Info data
+			pevent := info.PerfEvent()
+			switch pevent.Type {
+			case sys.BPF_PERF_EVENT_KPROBE, sys.BPF_PERF_EVENT_KRETPROBE:
+				kp := pevent.Kprobe()
+				if addr, ok := kp.Address(); ok {
+					qt.Assert(t, qt.Not(qt.Equals(addr, 0)))
+
+					_, ok := kp.Missed()
+					qt.Assert(t, qt.IsTrue(ok))
+					// NB: We don't check that missed is actually correct
+					// since it's not easy to trigger from tests.
+				}
+			}
 		}
 	})