From d978138670230eb577830e75eaecce05b6b2d003 Mon Sep 17 00:00:00 2001 From: Simone Magnani Date: Thu, 8 Aug 2024 11:52:14 +0200 Subject: [PATCH] added example for interacting with static/global vars Signed-off-by: Simone Magnani --- examples/README.md | 1 + examples/globals/bpf_bpfeb.go | 150 ++++++++++++++++++++++++++++++++++ examples/globals/bpf_bpfeb.o | Bin 0 -> 3168 bytes examples/globals/bpf_bpfel.go | 150 ++++++++++++++++++++++++++++++++++ examples/globals/bpf_bpfel.o | Bin 0 -> 3168 bytes examples/globals/main.go | 112 +++++++++++++++++++++++++ examples/globals/xdp.c | 26 ++++++ 7 files changed, 439 insertions(+) create mode 100644 examples/globals/bpf_bpfeb.go create mode 100644 examples/globals/bpf_bpfeb.o create mode 100644 examples/globals/bpf_bpfel.go create mode 100644 examples/globals/bpf_bpfel.o create mode 100644 examples/globals/main.go create mode 100644 examples/globals/xdp.c diff --git a/examples/README.md b/examples/README.md index a8309ef03..749fe0ac6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -23,6 +23,7 @@ Please see our [guide on what makes a good example](https://ebpf-go.dev/contribu * [tcx](./tcx/) - Print packet counts for ingress and egress. * XDP - Attach a program to a network interface to process incoming packets. * [xdp](xdp/) - Print packet counts by IPv4 source address. + * [globals](globals/) - Read and manipulate global variables from an XDP program while processing packets. ## How to run diff --git a/examples/globals/bpf_bpfeb.go b/examples/globals/bpf_bpfeb.go new file mode 100644 index 000000000..962de9526 --- /dev/null +++ b/examples/globals/bpf_bpfeb.go @@ -0,0 +1,150 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build mips || mips64 || ppc64 || s390x + +package main + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +// loadBpf returns the embedded CollectionSpec for bpf. +func loadBpf() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_BpfBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load bpf: %w", err) + } + + return spec, err +} + +// loadBpfObjects loads bpf and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *bpfObjects +// *bpfPrograms +// *bpfMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadBpf() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// bpfSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfSpecs struct { + bpfProgramSpecs + bpfMapSpecs + bpfVariableSpecs +} + +// bpfSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfProgramSpecs struct { + XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { +} + +// bpfObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfObjects struct { + bpfPrograms + bpfMaps + bpfVariables +} + +func (o *bpfObjects) Close() error { + return _BpfClose( + &o.bpfPrograms, + &o.bpfMaps, + &o.bpfVariables, + ) +} + +// bpfMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfMaps struct { +} + +func (m *bpfMaps) Close() error { + return _BpfClose() +} + +// bpfVariableSpecs contains variables before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfVariableSpecs struct { + ConstMsg *ebpf.VariableSpec `ebpf:"const_msg"` + PktCount *ebpf.VariableSpec `ebpf:"pkt_count"` + Random *ebpf.VariableSpec `ebpf:"random"` + VarMsg *ebpf.VariableSpec `ebpf:"var_msg"` + XdpProgFuncFmt *ebpf.VariableSpec `ebpf:"xdp_prog_func.___fmt"` +} + +// bpfVariables contains all variables after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfVariables struct { + ConstMsg *ebpf.Variable `ebpf:"const_msg"` + PktCount *ebpf.Variable `ebpf:"pkt_count"` + Random *ebpf.Variable `ebpf:"random"` + VarMsg *ebpf.Variable `ebpf:"var_msg"` + XdpProgFuncFmt *ebpf.Variable `ebpf:"xdp_prog_func.___fmt"` +} + +func (m *bpfVariables) Close() error { + return _BpfClose( + m.ConstMsg, + m.PktCount, + m.Random, + m.VarMsg, + m.XdpProgFuncFmt, + ) +} + +// bpfPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfPrograms struct { + XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.XdpProgFunc, + ) +} + +func _BpfClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed bpf_bpfeb.o +var _BpfBytes []byte diff --git a/examples/globals/bpf_bpfeb.o b/examples/globals/bpf_bpfeb.o new file mode 100644 index 0000000000000000000000000000000000000000..3782fc60dffa753d45abbe1ea8e1ae84805c6eb4 GIT binary patch literal 3168 zcmb_eO>Z1U5basxYz!D16Tl}lL|7!UH8BaGD8^!q;v|+#w8TEd5mI}+J6=}Von6n& z21h=`65>PRfO9~I10v?Yos+q7&5z*V3sNL5Ie_4{d9SByJRaE~BueV8dR5g`z1>wk zdvAX6N~z?ij2!hB&rZBC4Pab#J8sNQ#8d7aKeDp={d;q~<8}*EN|i92UE-2ef;mqqbpo7zCou*v zNW&PEU5uBkTmg5jyb6xfFh1Mh?^^i*_;m&XA+%aHyGUi1N(82gIeiSuf%t&Cz|Ch< z{UF$wwK?D>tZWc|#7|=|TWYkR4v&=ki_KaMxBOU?}arY<(y9-l;>68QQ}9 z3EGu+e+Gm2KEklJzOY#AI|cjCDQ5V()FR}g3B8nSr_ARStmZyR+#3mXX`Dq*D71lQ zDaLsS<0%wA-b;l7r@ucJW%R zJ8kXP>k(d(T;uVvI(S69IEfN}O?mO%w&AvQq><8HVaNXu-9wmSJ=)0SKdta%A(h-a z)szJn>$gIOBS`LzCf@D8t$$dE!CDexudy~r6*FhkiG3@w9{BB%-zn=yp=)ADez8ME za>Os+C&pmR>2v{1{EF*Y*Ces0=rH~U%H#6OdinOr`uzEQm0MqU!yNVr=~~LNS@Kb_ zi;>5f6Vyox27b#aDdzKk2Yk%=^IxZ-Rm?np@sso5`SaaACjTJ61*DynWph41-wlad z$vliumUXLu%rE~B^7&Uf{Y7(c+i*Bc<;NBm-_87fKZj4@+$GAOtS9Fh*|+2$e*yccXmBH z8yxu%ONfWW0q1}a2Sg$y?wrDnYkmX=UyzWv_yB_2@_pUav*U>qgebYX>#M5ns;Tbk z^_}^}OMQica#T=%sUC7#rS1*(7S5{YS0Jj1Q2*eot1#GN*$peOOMYp zG%T`*`FW8`m_3o#Fi(rTgZUki?_s_Q1`VPVYQ>1EK-sf0V-6#x3c!$6X!XvgP_z2u zPhlRX1Kcjp914=gz^avAjbIuN}Qw7 zn*lo3K(4Qec{k(pENmp^!gJ-!c;969KL$)7l%nFj=bv^m-N!y z^74#ApT^})+fW8XAzfgbr%JdvxgjhhP%!|Z3>8Rg)fkSrQ z#5A7S=b@PS?PGk=g!t{G_rYUo!1M;Oi*woNomWp^GJ00Z5_^hN1&^gwlO8b^JFv#A z>9pvGna!09=B{fQ%;>BsiT->HexU$jT<-Tr2Agd8c!Ox?{u6?^mixb)!PH&I`14IH zf=OJ@txI5%!%G&XcrAxpIlPm@!{Z~P{mM_mE!^HXto!e-T=EIh#(UCD#{#|p(j>j+ zaP8vP>$e&@s8nLSB$>wJW7T^^{B{y2!MgI>w;N`b)^Uwwvy#OB=X3|_S?4_cDOc!+ zoI#E=)e@6?H`kgi9f*42!nPBrHtanF1W-T!eeo?DQ|U&FvXAGe*$$?^4DBq`S{<$_ptg;%l%p7W+2v|e(w8S{rQ`HSo|CE!0*DI zrF?vbIV}F}A@Lb2AAc3P{>5$+(+)VLeOd2*XPhCQ>t`_JYfs;OS-39?xsJKJ_!FZ4 E2YgsRPyhe` literal 0 HcmV?d00001 diff --git a/examples/globals/main.go b/examples/globals/main.go new file mode 100644 index 000000000..23f025ee5 --- /dev/null +++ b/examples/globals/main.go @@ -0,0 +1,112 @@ +// This program demonstrates interacting with global variables and constants defined +// in an eBPF program from the userspace. For the example, the program is attached +// to a network interface with XDP (eXpress Data Path). +// The program declares three different types of variables: +// +// 1. `__u64 pkt_count = 0`: Initialized to zero -> .bss +// 2. `__u32 another_pkt_count = 0`: Initialized to zero -> .bss +// 3. `__u32 random = 1`: Initialized to != 0 -> .data +// 4. `char var_msg[] = "I can change :)"`: Initialized to != 0 -> .data +// 5. `const char const_msg[] = "I'm constant :)"`: Constant variable -> .rodata +// +// The userspace program (Go code in this file) prints the contents of all the +// variables, while also changing the value of the `random` and `var_msg` variables. +// This example depends on bpf_link, available in Linux kernel version 5.7 or newer. +package main + +import ( + "fmt" + "log" + "math/rand" + "net" + "os" + "strings" + "time" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" +) + +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go bpf xdp.c -- -I../headers + +func main() { + if len(os.Args) < 2 { + log.Fatalf("Please specify a network interface") + } + + // Look up the network interface by name. + ifaceName := os.Args[1] + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + log.Fatalf("lookup network iface %q: %s", ifaceName, err) + } + + // Load pre-compiled programs into the kernel. + objs := bpfObjects{} + if err := loadBpfObjects(&objs, nil); err != nil { + log.Fatalf("loading objects: %s", err) + } + defer objs.Close() + + // Attach the program. + l, err := link.AttachXDP(link.XDPOptions{ + Program: objs.XdpProgFunc, + Interface: iface.Index, + }) + if err != nil { + log.Fatalf("could not attach XDP program: %s", err) + } + defer l.Close() + + log.Printf("Attached XDP program to iface %q (index %d)", iface.Name, iface.Index) + log.Printf("Press Ctrl-C to exit and remove the program") + + // Initialize variables that we want to use from userspace + for _, v := range []*ebpf.Variable{objs.PktCount, objs.Random, objs.ConstMsg, objs.VarMsg} { + v.Mmap() + } + + var ( + sb strings.Builder + vPkt uint64 + vRandom, newRandomVal uint32 + vMsgConst []byte = make([]byte, objs.ConstMsg.Size()) + vMsgVar, newMsgVar []byte = make([]byte, objs.VarMsg.Size()), make([]byte, objs.VarMsg.Size()) + ) + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for range ticker.C { + if err = objs.PktCount.Load(&vPkt); err != nil { + log.Fatal(err) + } + if err = objs.Random.Load(&vRandom); err != nil { + log.Fatal(err) + } + if err = objs.ConstMsg.Load(&vMsgConst); err != nil { + log.Fatal(err) + } + if err = objs.VarMsg.Load(&vMsgVar); err != nil { + log.Fatal(err) + } + + newRandomVal = rand.Uint32() + if err = objs.Random.Store(newRandomVal); err != nil { + log.Fatal(err) + } + + copy(newMsgVar, vMsgVar) + newMsgVar[len(newMsgVar)-2] = (newMsgVar[len(newMsgVar)-2]+1)%2 + 40 + if err = objs.VarMsg.Store(newMsgVar); err != nil { + log.Fatal(err) + } + if err = objs.PktCount.Load(&vPkt); err != nil { + log.Fatal(err) + } + + sb.Reset() + sb.WriteString(fmt.Sprintf("--> PktCount: %20v, ConstMsg: %21s\n", vPkt, vMsgConst)) + sb.WriteString(fmt.Sprintf("--> Random: %20v, VarMsg: %21s\n", vRandom, vMsgVar)) + log.Printf("Variables Status:\n%s", sb.String()) + } +} diff --git a/examples/globals/xdp.c b/examples/globals/xdp.c new file mode 100644 index 000000000..ab6b851dd --- /dev/null +++ b/examples/globals/xdp.c @@ -0,0 +1,26 @@ +//go:build ignore + +#include "common.h" + +char __license[] SEC("license") = "Dual MIT/GPL"; + +#define MAX_MAP_ENTRIES 16 + +// Initialized to zero -> .bss +__u64 pkt_count = 0; + +// Initialized to != 0 -> .data +__u32 random = 1; +char var_msg[] = "I can change :)"; + +// Constant variable -> .rodata +const char const_msg[] = "I'm constant :)"; + +SEC("xdp") +int xdp_prog_func(struct xdp_md *ctx) { + pkt_count++; + + bpf_printk("pkt_count=%20llu, random=%10u, const_msg=%s, var_msg=%s", pkt_count, random, const_msg, var_msg); + + return XDP_PASS; +}