Skip to content

Commit fb45005

Browse files
mpastyllmb
authored andcommitted
link: add Iterator
Add an iterator for links that loops over all links in the system. Signed-off-by: Charalampos (Babis) Stylianopoulos <[email protected]>
1 parent 73b877d commit fb45005

File tree

4 files changed

+188
-2
lines changed

4 files changed

+188
-2
lines changed

internal/cmd/gentypes/main.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,14 @@ import (
363363
truncateAfter("next_id"),
364364
},
365365
},
366+
{
367+
"LinkGetNextId", retError, "obj_next_id", "BPF_LINK_GET_NEXT_ID",
368+
[]patch{
369+
choose(0, "start_id"), rename("start_id", "id"),
370+
replace(linkID, "id", "next_id"),
371+
truncateAfter("next_id"),
372+
},
373+
},
366374
// These piggy back on the obj_next_id decl, but only support the
367375
// first field...
368376
{
@@ -377,6 +385,10 @@ import (
377385
"ProgGetFdById", retFd, "obj_next_id", "BPF_PROG_GET_FD_BY_ID",
378386
[]patch{choose(0, "start_id"), rename("start_id", "id"), truncateAfter("id")},
379387
},
388+
{
389+
"LinkGetFdById", retFd, "obj_next_id", "BPF_LINK_GET_FD_BY_ID",
390+
[]patch{choose(0, "start_id"), rename("start_id", "id"), replace(linkID, "id"), truncateAfter("id")},
391+
},
380392
{
381393
"ObjGetInfoByFd", retError, "info_by_fd", "BPF_OBJ_GET_INFO_BY_FD",
382394
[]patch{replace(pointer, "info")},

internal/sys/types.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,26 @@ func LinkCreateUprobeMulti(attr *LinkCreateUprobeMultiAttr) (*FD, error) {
837837
return NewFD(int(fd))
838838
}
839839

840+
type LinkGetFdByIdAttr struct{ Id LinkID }
841+
842+
func LinkGetFdById(attr *LinkGetFdByIdAttr) (*FD, error) {
843+
fd, err := BPF(BPF_LINK_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
844+
if err != nil {
845+
return nil, err
846+
}
847+
return NewFD(int(fd))
848+
}
849+
850+
type LinkGetNextIdAttr struct {
851+
Id LinkID
852+
NextId LinkID
853+
}
854+
855+
func LinkGetNextId(attr *LinkGetNextIdAttr) error {
856+
_, err := BPF(BPF_LINK_GET_NEXT_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
857+
return err
858+
}
859+
840860
type LinkUpdateAttr struct {
841861
LinkFd uint32
842862
NewProgFd uint32

link/link.go

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package link
22

33
import (
4+
"errors"
45
"fmt"
6+
"os"
57

68
"github.com/cilium/ebpf"
79
"github.com/cilium/ebpf/btf"
@@ -46,8 +48,15 @@ type Link interface {
4648

4749
// NewLinkFromFD creates a link from a raw fd.
4850
//
49-
// You should not use fd after calling this function.
51+
// Deprecated: use [NewFromFD] instead.
5052
func NewLinkFromFD(fd int) (Link, error) {
53+
return NewFromFD(fd)
54+
}
55+
56+
// NewFromFD creates a link from a raw fd.
57+
//
58+
// You should not use fd after calling this function.
59+
func NewFromFD(fd int) (Link, error) {
5160
sysFD, err := sys.NewFD(fd)
5261
if err != nil {
5362
return nil, err
@@ -56,6 +65,19 @@ func NewLinkFromFD(fd int) (Link, error) {
5665
return wrapRawLink(&RawLink{fd: sysFD})
5766
}
5867

68+
// NewFromID returns the link associated with the given id.
69+
//
70+
// Returns ErrNotExist if there is no link with the given id.
71+
func NewFromID(id ID) (Link, error) {
72+
getFdAttr := &sys.LinkGetFdByIdAttr{Id: id}
73+
fd, err := sys.LinkGetFdById(getFdAttr)
74+
if err != nil {
75+
return nil, fmt.Errorf("get link fd from ID %d: %w", id, err)
76+
}
77+
78+
return wrapRawLink(&RawLink{fd, ""})
79+
}
80+
5981
// LoadPinnedLink loads a link that was persisted into a bpffs.
6082
func LoadPinnedLink(fileName string, opts *ebpf.LoadPinOptions) (Link, error) {
6183
raw, err := loadPinnedRawLink(fileName, opts)
@@ -446,3 +468,74 @@ func (l *RawLink) Info() (*Info, error) {
446468
extra,
447469
}, nil
448470
}
471+
472+
// Iterator allows iterating over links attached into the kernel.
473+
type Iterator struct {
474+
// The ID of the current link. Only valid after a call to Next
475+
ID ID
476+
// The current link. Only valid until a call to Next.
477+
// See Take if you want to retain the link.
478+
Link Link
479+
err error
480+
}
481+
482+
// Next retrieves the next link.
483+
//
484+
// Returns true if another link was found. Call [Iterator.Err] after the function returns false.
485+
func (it *Iterator) Next() bool {
486+
id := it.ID
487+
for {
488+
getIdAttr := &sys.LinkGetNextIdAttr{Id: id}
489+
err := sys.LinkGetNextId(getIdAttr)
490+
if errors.Is(err, os.ErrNotExist) {
491+
// There are no more links.
492+
break
493+
} else if err != nil {
494+
it.err = fmt.Errorf("get next link ID: %w", err)
495+
break
496+
}
497+
498+
id = getIdAttr.NextId
499+
l, err := NewFromID(id)
500+
if errors.Is(err, os.ErrNotExist) {
501+
// Couldn't load the link fast enough. Try next ID.
502+
continue
503+
} else if err != nil {
504+
it.err = fmt.Errorf("get link for ID %d: %w", id, err)
505+
break
506+
}
507+
508+
if it.Link != nil {
509+
it.Link.Close()
510+
}
511+
it.ID, it.Link = id, l
512+
return true
513+
}
514+
515+
// No more links or we encountered an error.
516+
if it.Link != nil {
517+
it.Link.Close()
518+
}
519+
it.Link = nil
520+
return false
521+
}
522+
523+
// Take the ownership of the current link.
524+
//
525+
// It's the callers responsibility to close the link.
526+
func (it *Iterator) Take() Link {
527+
l := it.Link
528+
it.Link = nil
529+
return l
530+
}
531+
532+
// Err returns an error if iteration failed for some reason.
533+
func (it *Iterator) Err() error {
534+
return it.err
535+
}
536+
537+
func (it *Iterator) Close() {
538+
if it.Link != nil {
539+
it.Link.Close()
540+
}
541+
}

link/link_test.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,67 @@ func TestRawLinkLoadPinnedWithOptions(t *testing.T) {
8888
}
8989
}
9090

91+
func TestIterator(t *testing.T) {
92+
cgroup, prog := mustCgroupFixtures(t)
93+
94+
tLink, err := AttachRawLink(RawLinkOptions{
95+
Target: int(cgroup.Fd()),
96+
Program: prog,
97+
Attach: ebpf.AttachCGroupInetEgress,
98+
})
99+
testutils.SkipIfNotSupported(t, err)
100+
if err != nil {
101+
t.Fatal("Can't create original raw link:", err)
102+
}
103+
defer tLink.Close()
104+
tLinkInfo, err := tLink.Info()
105+
testutils.SkipIfNotSupported(t, err)
106+
if err != nil {
107+
t.Fatal("Can't get original link info:", err)
108+
}
109+
110+
it := new(Iterator)
111+
defer it.Close()
112+
113+
prev := it.ID
114+
var foundLink Link
115+
for it.Next() {
116+
// Iterate all loaded links.
117+
if it.Link == nil {
118+
t.Fatal("Next doesn't assign link")
119+
}
120+
if it.ID == prev {
121+
t.Fatal("Iterator doesn't advance ID")
122+
}
123+
prev = it.ID
124+
if it.ID == tLinkInfo.ID {
125+
foundLink = it.Take()
126+
}
127+
}
128+
if err := it.Err(); err != nil {
129+
t.Fatal("Iteration returned an error:", err)
130+
}
131+
if it.Link != nil {
132+
t.Fatal("Next doesn't clean up link on last iteration")
133+
}
134+
if prev != it.ID {
135+
t.Fatal("Next changes ID on last iteration")
136+
}
137+
if foundLink == nil {
138+
t.Fatal("Original link not found")
139+
}
140+
defer foundLink.Close()
141+
// Confirm that we found the original link.
142+
info, err := foundLink.Info()
143+
if err != nil {
144+
t.Fatal("Can't get link info:", err)
145+
}
146+
if info.ID != tLinkInfo.ID {
147+
t.Fatal("Found link has wrong ID")
148+
}
149+
150+
}
151+
91152
func newPinnedRawLink(t *testing.T, cgroup *os.File, prog *ebpf.Program) (*RawLink, string) {
92153
t.Helper()
93154

@@ -235,7 +296,7 @@ func testLink(t *testing.T, link Link, prog *ebpf.Program) {
235296
}
236297
defer unix.Close(dupFD)
237298

238-
newLink, err := NewLinkFromFD(dupFD)
299+
newLink, err := NewFromFD(dupFD)
239300
testutils.SkipIfNotSupported(t, err)
240301
if err != nil {
241302
t.Fatal("Can't create new link from dup link FD:", err)

0 commit comments

Comments
 (0)