github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/link/link_test.go (about) 1 package link 2 3 import ( 4 "errors" 5 "math" 6 "os" 7 "path/filepath" 8 "reflect" 9 "testing" 10 11 "github.com/cilium/ebpf" 12 "github.com/cilium/ebpf/asm" 13 "github.com/cilium/ebpf/internal/sys" 14 "github.com/cilium/ebpf/internal/testutils" 15 "github.com/cilium/ebpf/internal/testutils/fdtrace" 16 "github.com/cilium/ebpf/internal/unix" 17 18 "github.com/go-quicktest/qt" 19 ) 20 21 func TestMain(m *testing.M) { 22 fdtrace.TestMain(m) 23 } 24 25 func TestRawLink(t *testing.T) { 26 cgroup, prog := mustCgroupFixtures(t) 27 28 link, err := AttachRawLink(RawLinkOptions{ 29 Target: int(cgroup.Fd()), 30 Program: prog, 31 Attach: ebpf.AttachCGroupInetEgress, 32 }) 33 testutils.SkipIfNotSupported(t, err) 34 if err != nil { 35 t.Fatal("Can't create raw link:", err) 36 } 37 38 info, err := link.Info() 39 if err != nil { 40 t.Fatal("Can't get link info:", err) 41 } 42 43 pi, err := prog.Info() 44 if err != nil { 45 t.Fatal("Can't get program info:", err) 46 } 47 48 progID, ok := pi.ID() 49 if !ok { 50 t.Fatal("Program ID not available in program info") 51 } 52 53 if info.Program != progID { 54 t.Error("Link program ID doesn't match program ID") 55 } 56 57 testLink(t, &linkCgroup{*link}, prog) 58 } 59 60 func TestUnpinRawLink(t *testing.T) { 61 cgroup, prog := mustCgroupFixtures(t) 62 link, _ := newPinnedRawLink(t, cgroup, prog) 63 defer link.Close() 64 65 qt.Assert(t, qt.IsTrue(link.IsPinned())) 66 67 if err := link.Unpin(); err != nil { 68 t.Fatal(err) 69 } 70 71 qt.Assert(t, qt.IsFalse(link.IsPinned())) 72 } 73 74 func TestRawLinkLoadPinnedWithOptions(t *testing.T) { 75 cgroup, prog := mustCgroupFixtures(t) 76 link, path := newPinnedRawLink(t, cgroup, prog) 77 defer link.Close() 78 79 qt.Assert(t, qt.IsTrue(link.IsPinned())) 80 81 // It seems like the kernel ignores BPF_F_RDONLY when updating a link, 82 // so we can't test this. 83 _, err := loadPinnedRawLink(path, &ebpf.LoadPinOptions{ 84 Flags: math.MaxUint32, 85 }) 86 if !errors.Is(err, unix.EINVAL) { 87 t.Fatal("Invalid flags don't trigger an error:", err) 88 } 89 } 90 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 152 func newPinnedRawLink(t *testing.T, cgroup *os.File, prog *ebpf.Program) (*RawLink, string) { 153 t.Helper() 154 155 link, err := AttachRawLink(RawLinkOptions{ 156 Target: int(cgroup.Fd()), 157 Program: prog, 158 Attach: ebpf.AttachCGroupInetEgress, 159 }) 160 testutils.SkipIfNotSupported(t, err) 161 if err != nil { 162 t.Fatal("Can't create raw link:", err) 163 } 164 165 path := filepath.Join(testutils.TempBPFFS(t), "link") 166 err = link.Pin(path) 167 testutils.SkipIfNotSupported(t, err) 168 if err != nil { 169 t.Fatal(err) 170 } 171 172 return link, path 173 } 174 175 func mustCgroupFixtures(t *testing.T) (*os.File, *ebpf.Program) { 176 t.Helper() 177 178 testutils.SkipIfNotSupported(t, haveProgAttach()) 179 180 return testutils.CreateCgroup(t), mustLoadProgram(t, ebpf.CGroupSKB, 0, "") 181 } 182 183 func testLink(t *testing.T, link Link, prog *ebpf.Program) { 184 t.Helper() 185 186 tmp, err := os.MkdirTemp("/sys/fs/bpf", "ebpf-test") 187 if err != nil { 188 t.Fatal(err) 189 } 190 defer os.RemoveAll(tmp) 191 192 t.Run("link/pinning", func(t *testing.T) { 193 path := filepath.Join(tmp, "link") 194 err = link.Pin(path) 195 testutils.SkipIfNotSupported(t, err) 196 if err != nil { 197 t.Fatalf("Can't pin %T: %s", link, err) 198 } 199 200 link2, err := LoadPinnedLink(path, nil) 201 if err != nil { 202 t.Fatalf("Can't load pinned %T: %s", link, err) 203 } 204 link2.Close() 205 206 if reflect.TypeOf(link) != reflect.TypeOf(link2) { 207 t.Errorf("Loading a pinned %T returns a %T", link, link2) 208 } 209 210 _, err = LoadPinnedLink(path, &ebpf.LoadPinOptions{ 211 Flags: math.MaxUint32, 212 }) 213 if !errors.Is(err, unix.EINVAL) { 214 t.Errorf("Loading a pinned %T doesn't respect flags", link) 215 } 216 }) 217 218 t.Run("link/update", func(t *testing.T) { 219 err := link.Update(prog) 220 testutils.SkipIfNotSupported(t, err) 221 if err != nil { 222 t.Fatal("Update returns an error:", err) 223 } 224 225 func() { 226 // Panicking is OK 227 defer func() { 228 _ = recover() 229 }() 230 231 if err := link.Update(nil); err == nil { 232 t.Fatalf("%T.Update accepts nil program", link) 233 } 234 }() 235 }) 236 237 t.Run("link/info", func(t *testing.T) { 238 info, err := link.Info() 239 testutils.SkipIfNotSupported(t, err) 240 if err != nil { 241 t.Fatal("Link info returns an error:", err) 242 } 243 244 if info.Type == 0 { 245 t.Fatal("Failed to get link info type") 246 } 247 248 switch info.Type { 249 case sys.BPF_LINK_TYPE_TRACING: 250 if info.Tracing() == nil { 251 t.Fatalf("Failed to get link tracing extra info") 252 } 253 case sys.BPF_LINK_TYPE_CGROUP: 254 cg := info.Cgroup() 255 if cg.CgroupId == 0 { 256 t.Fatalf("Failed to get link Cgroup extra info") 257 } 258 case sys.BPF_LINK_TYPE_NETNS: 259 netns := info.NetNs() 260 if netns.AttachType == 0 { 261 t.Fatalf("Failed to get link NetNs extra info") 262 } 263 case sys.BPF_LINK_TYPE_XDP: 264 xdp := info.XDP() 265 if xdp.Ifindex == 0 { 266 t.Fatalf("Failed to get link XDP extra info") 267 } 268 case sys.BPF_LINK_TYPE_TCX: 269 tcx := info.TCX() 270 if tcx.Ifindex == 0 { 271 t.Fatalf("Failed to get link TCX extra info") 272 } 273 case sys.BPF_LINK_TYPE_NETFILTER: 274 nf := info.Netfilter() 275 if nf.Priority == 0 { 276 t.Fatalf("Failed to get link Netfilter extra info") 277 } 278 case sys.BPF_LINK_TYPE_KPROBE_MULTI: 279 // test default Info data 280 kmulti := info.KprobeMulti() 281 if count, ok := kmulti.AddressCount(); ok { 282 qt.Assert(t, qt.Not(qt.Equals(count, 0))) 283 284 _, ok = kmulti.Missed() 285 qt.Assert(t, qt.IsTrue(ok)) 286 // NB: We don't check that missed is actually correct 287 // since it's not easy to trigger from tests. 288 } 289 case sys.BPF_LINK_TYPE_PERF_EVENT: 290 // test default Info data 291 pevent := info.PerfEvent() 292 switch pevent.Type { 293 case sys.BPF_PERF_EVENT_KPROBE, sys.BPF_PERF_EVENT_KRETPROBE: 294 kp := pevent.Kprobe() 295 if addr, ok := kp.Address(); ok { 296 qt.Assert(t, qt.Not(qt.Equals(addr, 0))) 297 298 _, ok := kp.Missed() 299 qt.Assert(t, qt.IsTrue(ok)) 300 // NB: We don't check that missed is actually correct 301 // since it's not easy to trigger from tests. 302 } 303 } 304 } 305 }) 306 307 type FDer interface { 308 FD() int 309 } 310 311 t.Run("from fd", func(t *testing.T) { 312 fder, ok := link.(FDer) 313 if !ok { 314 t.Skip("Link doesn't allow retrieving FD") 315 } 316 317 // We need to dup the FD since NewLinkFromFD takes 318 // ownership. 319 dupFD, err := unix.FcntlInt(uintptr(fder.FD()), unix.F_DUPFD_CLOEXEC, 1) 320 if err != nil { 321 t.Fatal("Can't dup link FD:", err) 322 } 323 defer unix.Close(dupFD) 324 325 newLink, err := NewFromFD(dupFD) 326 testutils.SkipIfNotSupported(t, err) 327 if err != nil { 328 t.Fatal("Can't create new link from dup link FD:", err) 329 } 330 defer newLink.Close() 331 332 if reflect.TypeOf(newLink) != reflect.TypeOf(link) { 333 t.Fatalf("Expected type %T, got %T", link, newLink) 334 } 335 }) 336 337 if err := link.Close(); err != nil { 338 t.Fatalf("%T.Close returns an error: %s", link, err) 339 } 340 } 341 342 func mustLoadProgram(tb testing.TB, typ ebpf.ProgramType, attachType ebpf.AttachType, attachTo string) *ebpf.Program { 343 tb.Helper() 344 345 license := "MIT" 346 switch typ { 347 case ebpf.RawTracepoint, ebpf.LSM: 348 license = "GPL" 349 } 350 351 prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ 352 Type: typ, 353 AttachType: attachType, 354 AttachTo: attachTo, 355 License: license, 356 Instructions: asm.Instructions{ 357 asm.Mov.Imm(asm.R0, 0), 358 asm.Return(), 359 }, 360 }) 361 if err != nil { 362 tb.Fatal(err) 363 } 364 365 tb.Cleanup(func() { 366 prog.Close() 367 }) 368 369 return prog 370 }