1 package ebpf
2
3 import (
4 "bytes"
5 "encoding/binary"
6 "errors"
7 "fmt"
8 "math"
9 "os"
10 "path/filepath"
11 "runtime"
12 "strings"
13 "syscall"
14 "testing"
15 "time"
16
17 qt "github.com/frankban/quicktest"
18
19 "github.com/cilium/ebpf/asm"
20 "github.com/cilium/ebpf/btf"
21 "github.com/cilium/ebpf/internal"
22 "github.com/cilium/ebpf/internal/sys"
23 "github.com/cilium/ebpf/internal/testutils"
24 "github.com/cilium/ebpf/internal/unix"
25 )
26
27 func TestProgramRun(t *testing.T) {
28 testutils.SkipOnOldKernel(t, "4.8", "XDP program")
29
30 pat := []byte{0xDE, 0xAD, 0xBE, 0xEF}
31 buf := make([]byte, 14)
32
33
34
35 ins := asm.Instructions{
36
37 asm.LoadMem(asm.R2, asm.R1, 4, asm.Word),
38
39 asm.LoadMem(asm.R1, asm.R1, 0, asm.Word),
40
41 asm.Mov.Reg(asm.R3, asm.R1),
42
43 asm.Add.Imm(asm.R3, int32(len(buf))),
44
45 asm.JGT.Reg(asm.R3, asm.R2, "out"),
46 }
47 for i, b := range pat {
48 ins = append(ins, asm.StoreImm(asm.R1, int16(i), int64(b), asm.Byte))
49 }
50 ins = append(ins,
51
52 asm.LoadImm(asm.R0, 42, asm.DWord).WithSymbol("out"),
53 asm.Return(),
54 )
55
56 t.Log(ins)
57
58 prog, err := NewProgram(&ProgramSpec{
59 Name: "test",
60 Type: XDP,
61 Instructions: ins,
62 License: "MIT",
63 })
64 if err != nil {
65 t.Fatal(err)
66 }
67 defer prog.Close()
68
69 p2, err := prog.Clone()
70 if err != nil {
71 t.Fatal("Can't clone program")
72 }
73 defer p2.Close()
74
75 prog.Close()
76 prog = p2
77
78 ret, out, err := prog.Test(buf)
79 testutils.SkipIfNotSupported(t, err)
80 if err != nil {
81 t.Fatal(err)
82 }
83
84 if ret != 42 {
85 t.Error("Expected return value to be 42, got", ret)
86 }
87
88 if !bytes.Equal(out[:len(pat)], pat) {
89 t.Errorf("Expected %v, got %v", pat, out)
90 }
91 }
92
93 func TestProgramRunWithOptions(t *testing.T) {
94 testutils.SkipOnOldKernel(t, "5.15", "XDP ctx_in/ctx_out")
95
96 ins := asm.Instructions{
97
98 asm.LoadImm(asm.R0, 0, asm.DWord),
99 asm.Return(),
100 }
101
102 prog, err := NewProgram(&ProgramSpec{
103 Name: "test",
104 Type: XDP,
105 Instructions: ins,
106 License: "MIT",
107 })
108 if err != nil {
109 t.Fatal(err)
110 }
111 defer prog.Close()
112
113 buf := make([]byte, 14)
114 xdp := sys.XdpMd{
115 Data: 0,
116 DataEnd: 14,
117 }
118 xdpOut := sys.XdpMd{}
119 opts := RunOptions{
120 Data: buf,
121 Context: xdp,
122 ContextOut: &xdpOut,
123 }
124 ret, err := prog.Run(&opts)
125 testutils.SkipIfNotSupported(t, err)
126 if err != nil {
127 t.Fatal(err)
128 }
129
130 if ret != 0 {
131 t.Error("Expected return value to be 0, got", ret)
132 }
133
134 if xdp != xdpOut {
135 t.Errorf("Expect xdp (%+v) == xdpOut (%+v)", xdp, xdpOut)
136 }
137 }
138
139 func TestProgramRunEmptyData(t *testing.T) {
140 testutils.SkipOnOldKernel(t, "5.13", "sk_lookup BPF_PROG_RUN")
141
142 ins := asm.Instructions{
143
144 asm.LoadImm(asm.R0, 0, asm.DWord),
145 asm.Return(),
146 }
147
148 prog, err := NewProgram(&ProgramSpec{
149 Name: "test",
150 Type: SkLookup,
151 AttachType: AttachSkLookup,
152 Instructions: ins,
153 License: "MIT",
154 })
155 if err != nil {
156 t.Fatal(err)
157 }
158 defer prog.Close()
159
160 opts := RunOptions{
161 Context: sys.SkLookup{
162 Family: syscall.AF_INET,
163 },
164 }
165 ret, err := prog.Run(&opts)
166 testutils.SkipIfNotSupported(t, err)
167 if err != nil {
168 t.Fatal(err)
169 }
170
171 if ret != 0 {
172 t.Error("Expected return value to be 0, got", ret)
173 }
174 }
175
176 func TestProgramBenchmark(t *testing.T) {
177 prog := mustSocketFilter(t)
178
179 ret, duration, err := prog.Benchmark(make([]byte, 14), 1, nil)
180 testutils.SkipIfNotSupported(t, err)
181 if err != nil {
182 t.Fatal("Error from Benchmark:", err)
183 }
184
185 if ret != 2 {
186 t.Error("Expected return value 2, got", ret)
187 }
188
189 if duration == 0 {
190 t.Error("Expected non-zero duration")
191 }
192 }
193
194 func TestProgramTestRunInterrupt(t *testing.T) {
195 testutils.SkipOnOldKernel(t, "5.0", "EINTR from BPF_PROG_TEST_RUN")
196
197 prog := mustSocketFilter(t)
198
199 var (
200 tgid = unix.Getpid()
201 tidChan = make(chan int, 1)
202 exit = make(chan struct{})
203 errs = make(chan error, 1)
204 timeout = time.After(5 * time.Second)
205 )
206
207 defer close(exit)
208
209 go func() {
210 runtime.LockOSThread()
211 defer func() {
212
213
214 <-exit
215 runtime.UnlockOSThread()
216 }()
217
218 tidChan <- unix.Gettid()
219
220
221
222 opts := RunOptions{
223 Data: make([]byte, 14),
224 Repeat: math.MaxInt32,
225 Reset: func() {
226
227
228
229 close(errs)
230 runtime.Goexit()
231 },
232 }
233 _, _, err := prog.testRun(&opts)
234
235 errs <- err
236 }()
237
238 tid := <-tidChan
239 for {
240 err := unix.Tgkill(tgid, tid, syscall.SIGUSR1)
241 if err != nil {
242 t.Fatal("Can't send signal to goroutine thread:", err)
243 }
244
245 select {
246 case err, ok := <-errs:
247 if !ok {
248 return
249 }
250
251 testutils.SkipIfNotSupported(t, err)
252 if err == nil {
253 t.Fatal("testRun wasn't interrupted")
254 }
255
256 t.Fatal("testRun returned an error:", err)
257
258 case <-timeout:
259 t.Fatal("Timed out trying to interrupt the goroutine")
260
261 default:
262 }
263 }
264 }
265
266 func TestProgramClose(t *testing.T) {
267 prog := mustSocketFilter(t)
268
269 if err := prog.Close(); err != nil {
270 t.Fatal("Can't close program:", err)
271 }
272 }
273
274 func TestProgramPin(t *testing.T) {
275 prog := mustSocketFilter(t)
276 c := qt.New(t)
277
278 tmp := testutils.TempBPFFS(t)
279
280 path := filepath.Join(tmp, "program")
281 if err := prog.Pin(path); err != nil {
282 t.Fatal(err)
283 }
284
285 pinned := prog.IsPinned()
286 c.Assert(pinned, qt.IsTrue)
287
288 prog.Close()
289
290 prog, err := LoadPinnedProgram(path, nil)
291 testutils.SkipIfNotSupported(t, err)
292 if err != nil {
293 t.Fatal(err)
294 }
295 defer prog.Close()
296
297 if prog.Type() != SocketFilter {
298 t.Error("Expected pinned program to have type SocketFilter, but got", prog.Type())
299 }
300
301 if !prog.IsPinned() {
302 t.Error("Expected IsPinned to be true")
303 }
304 }
305
306 func TestProgramUnpin(t *testing.T) {
307 prog := mustSocketFilter(t)
308 c := qt.New(t)
309
310 tmp := testutils.TempBPFFS(t)
311
312 path := filepath.Join(tmp, "program")
313 if err := prog.Pin(path); err != nil {
314 t.Fatal(err)
315 }
316
317 pinned := prog.IsPinned()
318 c.Assert(pinned, qt.IsTrue)
319
320 if err := prog.Unpin(); err != nil {
321 t.Fatal("Failed to unpin program:", err)
322 }
323 if _, err := os.Stat(path); err == nil {
324 t.Fatal("Pinned program path still exists after unpinning:", err)
325 }
326 }
327
328 func TestProgramLoadPinnedWithFlags(t *testing.T) {
329
330 testutils.SkipOnOldKernel(t, "4.14", "file_flags in BPF_OBJ_GET")
331
332 prog := mustSocketFilter(t)
333
334 tmp := testutils.TempBPFFS(t)
335
336 path := filepath.Join(tmp, "program")
337 if err := prog.Pin(path); err != nil {
338 t.Fatal(err)
339 }
340
341 prog.Close()
342
343 _, err := LoadPinnedProgram(path, &LoadPinOptions{
344 Flags: math.MaxUint32,
345 })
346 testutils.SkipIfNotSupported(t, err)
347 if !errors.Is(err, unix.EINVAL) {
348 t.Fatal("Invalid flags don't trigger an error:", err)
349 }
350 }
351
352 func TestProgramVerifierOutputOnError(t *testing.T) {
353 _, err := NewProgram(&ProgramSpec{
354 Type: SocketFilter,
355 Instructions: asm.Instructions{
356 asm.Return(),
357 },
358 License: "MIT",
359 })
360 if err == nil {
361 t.Fatal("Expected program to be invalid")
362 }
363
364 var ve *VerifierError
365 if !errors.As(err, &ve) {
366 t.Fatal("Error does not contain a VerifierError")
367 }
368
369 if !strings.Contains(ve.Error(), "R0 !read_ok") {
370 t.Error("Unexpected verifier error contents:", ve)
371 }
372 }
373
374 func TestProgramKernelVersion(t *testing.T) {
375 testutils.SkipOnOldKernel(t, "4.20", "KernelVersion")
376 prog, err := NewProgram(&ProgramSpec{
377 Type: Kprobe,
378 Instructions: asm.Instructions{
379 asm.LoadImm(asm.R0, 0, asm.DWord),
380 asm.Return(),
381 },
382 KernelVersion: 42,
383 License: "MIT",
384 })
385 if err != nil {
386 t.Fatal("Could not load Kprobe program")
387 }
388 defer prog.Close()
389 }
390
391 func TestProgramVerifierOutput(t *testing.T) {
392 prog, err := NewProgramWithOptions(socketFilterSpec, ProgramOptions{
393 LogLevel: 2,
394 })
395 if err != nil {
396 t.Fatal(err)
397 }
398 defer prog.Close()
399
400 if prog.VerifierLog == "" {
401 t.Error("Expected VerifierLog to be present")
402 }
403
404
405 _, err = NewProgramWithOptions(&ProgramSpec{
406 Type: SocketFilter,
407 Instructions: asm.Instructions{
408 asm.Mov.Reg(asm.R0, asm.R1),
409 },
410 License: "MIT",
411 }, ProgramOptions{
412 LogLevel: 2,
413 })
414
415 if err == nil {
416 t.Fatal("Expected an error from invalid program")
417 }
418
419 var ve *internal.VerifierError
420 if !errors.As(err, &ve) {
421 t.Error("Error is not a VerifierError")
422 }
423 }
424
425 func TestProgramWithUnsatisfiedMap(t *testing.T) {
426 coll, err := LoadCollectionSpec("testdata/loader-el.elf")
427 if err != nil {
428 t.Fatal(err)
429 }
430
431
432 progSpec := coll.Programs["xdp_prog"]
433 progSpec.ByteOrder = nil
434
435 _, err = NewProgram(progSpec)
436 testutils.SkipIfNotSupported(t, err)
437 if !errors.Is(err, asm.ErrUnsatisfiedMapReference) {
438 t.Fatal("Expected an error wrapping asm.ErrUnsatisfiedMapReference, got", err)
439 }
440 t.Log(err)
441 }
442
443 func TestProgramName(t *testing.T) {
444 if err := haveObjName(); err != nil {
445 t.Skip(err)
446 }
447
448 prog := mustSocketFilter(t)
449
450 var info sys.ProgInfo
451 if err := sys.ObjInfo(prog.fd, &info); err != nil {
452 t.Fatal(err)
453 }
454
455 if name := unix.ByteSliceToString(info.Name[:]); name != "test" {
456 t.Errorf("Name is not test, got '%s'", name)
457 }
458 }
459
460 func TestSanitizeName(t *testing.T) {
461 for input, want := range map[string]string{
462 "test": "test",
463 "t-est": "test",
464 "t_est": "t_est",
465 "hörnchen": "hrnchen",
466 } {
467 if have := SanitizeName(input, -1); have != want {
468 t.Errorf("Wanted '%s' got '%s'", want, have)
469 }
470 }
471 }
472
473 func TestProgramCloneNil(t *testing.T) {
474 p, err := (*Program)(nil).Clone()
475 if err != nil {
476 t.Fatal(err)
477 }
478
479 if p != nil {
480 t.Fatal("Cloning a nil Program doesn't return nil")
481 }
482 }
483
484 func TestProgramMarshaling(t *testing.T) {
485 const idx = uint32(0)
486
487 arr := createProgramArray(t)
488 defer arr.Close()
489
490 prog := mustSocketFilter(t)
491
492 if err := arr.Put(idx, prog); err != nil {
493 t.Fatal("Can't put program:", err)
494 }
495
496 if err := arr.Lookup(idx, Program{}); err == nil {
497 t.Fatal("Lookup accepts non-pointer Program")
498 }
499
500 var prog2 *Program
501 defer prog2.Close()
502
503 if err := arr.Lookup(idx, prog2); err == nil {
504 t.Fatal("Get accepts *Program")
505 }
506
507 testutils.SkipOnOldKernel(t, "4.12", "lookup for ProgramArray")
508
509 if err := arr.Lookup(idx, &prog2); err != nil {
510 t.Fatal("Can't unmarshal program:", err)
511 }
512 defer prog2.Close()
513
514 if prog2 == nil {
515 t.Fatal("Unmarshalling set program to nil")
516 }
517 }
518
519 func TestProgramFromFD(t *testing.T) {
520 prog := mustSocketFilter(t)
521
522
523
524 prog2, err := NewProgramFromFD(prog.FD())
525 testutils.SkipIfNotSupported(t, err)
526 if err != nil {
527 t.Fatal(err)
528 }
529
530
531
532
533
534
535 prog2.fd.Forget()
536 }
537
538 func TestHaveProgTestRun(t *testing.T) {
539 testutils.CheckFeatureTest(t, haveProgTestRun)
540 }
541
542 func TestProgramGetNextID(t *testing.T) {
543 testutils.SkipOnOldKernel(t, "4.13", "bpf_prog_get_next_id")
544
545
546 _ = mustSocketFilter(t)
547
548
549
550 last := ProgramID(0)
551 for {
552 next, err := ProgramGetNextID(last)
553 if errors.Is(err, os.ErrNotExist) {
554 if last == 0 {
555 t.Fatal("Got ErrNotExist on the first iteration")
556 }
557 break
558 }
559 if err != nil {
560 t.Fatal("Unexpected error:", err)
561 }
562 if next <= last {
563 t.Fatalf("Expected next ID (%d) to be higher than the last ID (%d)", next, last)
564 }
565 last = next
566 }
567 }
568
569 func TestNewProgramFromID(t *testing.T) {
570 prog := mustSocketFilter(t)
571
572 info, err := prog.Info()
573 testutils.SkipIfNotSupported(t, err)
574 if err != nil {
575 t.Fatal("Could not get program info:", err)
576 }
577
578 id, ok := info.ID()
579 if !ok {
580 t.Skip("Program ID not supported")
581 }
582
583 prog2, err := NewProgramFromID(id)
584 if err != nil {
585 t.Fatalf("Can't get FD for program ID %d: %v", id, err)
586 }
587 prog2.Close()
588
589
590 _, err = NewProgramFromID(ProgramID(math.MaxUint32))
591 if !errors.Is(err, os.ErrNotExist) {
592 t.Fatal("Expected ErrNotExist, got:", err)
593 }
594 }
595
596 func TestProgramRejectIncorrectByteOrder(t *testing.T) {
597 spec := socketFilterSpec.Copy()
598
599 spec.ByteOrder = binary.BigEndian
600 if internal.NativeEndian == binary.BigEndian {
601 spec.ByteOrder = binary.LittleEndian
602 }
603
604 _, err := NewProgram(spec)
605 if err == nil {
606 t.Error("Incorrect ByteOrder should be rejected at load time")
607 }
608 }
609
610 func TestProgramSpecTag(t *testing.T) {
611 arr := createArray(t)
612 defer arr.Close()
613
614 spec := &ProgramSpec{
615 Type: SocketFilter,
616 Instructions: asm.Instructions{
617 asm.LoadImm(asm.R0, -1, asm.DWord),
618 asm.LoadMapPtr(asm.R1, arr.FD()),
619 asm.Mov.Imm32(asm.R0, 0),
620 asm.Return(),
621 },
622 License: "MIT",
623 }
624
625 prog, err := NewProgram(spec)
626 if err != nil {
627 t.Fatal(err)
628 }
629 defer prog.Close()
630
631 info, err := prog.Info()
632 testutils.SkipIfNotSupported(t, err)
633 if err != nil {
634 t.Fatal(err)
635 }
636
637 tag, err := spec.Tag()
638 if err != nil {
639 t.Fatal("Can't calculate tag:", err)
640 }
641
642 if tag != info.Tag {
643 t.Errorf("Calculated tag %s doesn't match kernel tag %s", tag, info.Tag)
644 }
645 }
646
647 func TestProgramTypeLSM(t *testing.T) {
648 lsmTests := []struct {
649 attachFn string
650 flags uint32
651 expectedErr bool
652 }{
653 {
654 attachFn: "task_getpgid",
655 },
656 {
657 attachFn: "task_setnice",
658 flags: unix.BPF_F_SLEEPABLE,
659 expectedErr: true,
660 },
661 {
662 attachFn: "file_open",
663 flags: unix.BPF_F_SLEEPABLE,
664 },
665 }
666 for _, tt := range lsmTests {
667 t.Run(tt.attachFn, func(t *testing.T) {
668 prog, err := NewProgram(&ProgramSpec{
669 AttachTo: tt.attachFn,
670 AttachType: AttachLSMMac,
671 Instructions: asm.Instructions{
672 asm.LoadImm(asm.R0, 0, asm.DWord),
673 asm.Return(),
674 },
675 License: "GPL",
676 Type: LSM,
677 Flags: tt.flags,
678 })
679 testutils.SkipIfNotSupported(t, err)
680
681 if tt.flags&unix.BPF_F_SLEEPABLE != 0 {
682 testutils.SkipOnOldKernel(t, "5.11", "BPF_F_SLEEPABLE for LSM progs")
683 }
684 if tt.expectedErr && err == nil {
685 t.Errorf("Test case '%s': expected error", tt.attachFn)
686 }
687 if !tt.expectedErr && err != nil {
688 t.Errorf("Test case '%s': expected success", tt.attachFn)
689 }
690 prog.Close()
691 })
692 }
693 }
694
695 func TestProgramKernelTypes(t *testing.T) {
696 if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) {
697 t.Skip("/sys/kernel/btf/vmlinux not present")
698 }
699
700 btfSpec, err := btf.LoadSpec("/sys/kernel/btf/vmlinux")
701 if err != nil {
702 t.Fatal(err)
703 }
704
705 prog, err := NewProgramWithOptions(&ProgramSpec{
706 Type: Tracing,
707 AttachType: AttachTraceIter,
708 AttachTo: "bpf_map",
709 Instructions: asm.Instructions{
710 asm.Mov.Imm(asm.R0, 0),
711 asm.Return(),
712 },
713 License: "MIT",
714 }, ProgramOptions{
715 KernelTypes: btfSpec,
716 })
717 testutils.SkipIfNotSupported(t, err)
718 if err != nil {
719 t.Fatal("NewProgram with Target:", err)
720 }
721 prog.Close()
722 }
723
724 func TestProgramBindMap(t *testing.T) {
725 testutils.SkipOnOldKernel(t, "5.10", "BPF_PROG_BIND_MAP")
726
727 arr, err := NewMap(&MapSpec{
728 Type: Array,
729 KeySize: 4,
730 ValueSize: 4,
731 MaxEntries: 1,
732 })
733 if err != nil {
734 t.Errorf("Failed to load map: %v", err)
735 }
736 defer arr.Close()
737
738 prog := mustSocketFilter(t)
739
740
741
742
743 if err := prog.BindMap(arr); err != nil {
744 t.Errorf("Failed to bind map to program: %v", err)
745 }
746 }
747
748 func TestProgramInstructions(t *testing.T) {
749 name := "test_prog"
750 spec := &ProgramSpec{
751 Type: SocketFilter,
752 Name: name,
753 Instructions: asm.Instructions{
754 asm.LoadImm(asm.R0, -1, asm.DWord).WithSymbol(name),
755 asm.Return(),
756 },
757 License: "MIT",
758 }
759
760 prog, err := NewProgram(spec)
761 if err != nil {
762 t.Fatal(err)
763 }
764 defer prog.Close()
765
766 pi, err := prog.Info()
767 testutils.SkipIfNotSupported(t, err)
768 if err != nil {
769 t.Fatal(err)
770 }
771
772 insns, err := pi.Instructions()
773 if err != nil {
774 t.Fatal(err)
775 }
776
777 tag, err := spec.Tag()
778 if err != nil {
779 t.Fatal(err)
780 }
781
782 tagXlated, err := insns.Tag(internal.NativeEndian)
783 if err != nil {
784 t.Fatal(err)
785 }
786
787 if tag != tagXlated {
788 t.Fatalf("tag %s differs from xlated instructions tag %s", tag, tagXlated)
789 }
790 }
791
792 func createProgramArray(t *testing.T) *Map {
793 t.Helper()
794
795 arr, err := NewMap(&MapSpec{
796 Type: ProgramArray,
797 KeySize: 4,
798 ValueSize: 4,
799 MaxEntries: 1,
800 })
801 if err != nil {
802 t.Fatal(err)
803 }
804 return arr
805 }
806
807 var socketFilterSpec = &ProgramSpec{
808 Name: "test",
809 Type: SocketFilter,
810 Instructions: asm.Instructions{
811 asm.LoadImm(asm.R0, 2, asm.DWord),
812 asm.Return(),
813 },
814 License: "MIT",
815 }
816
817 func mustSocketFilter(tb testing.TB) *Program {
818 tb.Helper()
819
820 prog, err := NewProgram(socketFilterSpec)
821 if err != nil {
822 tb.Fatal(err)
823 }
824 tb.Cleanup(func() { prog.Close() })
825
826 return prog
827 }
828
829
830 func ExampleProgram_verifierError() {
831 _, err := NewProgram(&ProgramSpec{
832 Type: SocketFilter,
833 Instructions: asm.Instructions{
834 asm.LoadImm(asm.R0, 0, asm.DWord),
835
836 },
837 License: "MIT",
838 })
839
840 var ve *VerifierError
841 if errors.As(err, &ve) {
842
843
844 fmt.Printf("Verifier error: %+v\n", ve)
845 }
846 }
847
848
849
850
851 func ExampleProgram_retrieveVerifierOutput() {
852 spec := &ProgramSpec{
853 Type: SocketFilter,
854 Instructions: asm.Instructions{
855 asm.LoadImm(asm.R0, 0, asm.DWord),
856 asm.Return(),
857 },
858 License: "MIT",
859 }
860
861 prog, err := NewProgramWithOptions(spec, ProgramOptions{
862 LogLevel: 2,
863 LogSize: 1024,
864 })
865 if err != nil {
866 panic(err)
867 }
868 defer prog.Close()
869
870 fmt.Println("The verifier output is:")
871 fmt.Println(prog.VerifierLog)
872 }
873
874
875 func ExampleProgram_unmarshalFromMap() {
876 progArray, err := LoadPinnedMap("/path/to/map", nil)
877 if err != nil {
878 panic(err)
879 }
880 defer progArray.Close()
881
882
883 var prog *Program
884 if err := progArray.Lookup(uint32(0), &prog); err != nil {
885 panic(err)
886 }
887 defer prog.Close()
888
889 fmt.Println("first prog:", prog)
890
891
892 var (
893 key uint32
894 entries = progArray.Iterate()
895 )
896
897 for entries.Next(&key, &prog) {
898 fmt.Println(key, "is", prog)
899 }
900
901 if err := entries.Err(); err != nil {
902 panic(err)
903 }
904 }
905
906 func ExampleProgramSpec_Tag() {
907 spec := &ProgramSpec{
908 Type: SocketFilter,
909 Instructions: asm.Instructions{
910 asm.LoadImm(asm.R0, 0, asm.DWord),
911 asm.Return(),
912 },
913 License: "MIT",
914 }
915
916 prog, _ := NewProgram(spec)
917 info, _ := prog.Info()
918 tag, _ := spec.Tag()
919
920 if info.Tag != tag {
921 fmt.Printf("The tags don't match: %s != %s\n", info.Tag, tag)
922 } else {
923 fmt.Println("The programs are identical, tag is", tag)
924 }
925 }
926
View as plain text