Hello world gadget with Wasm
This guide explores the wasm support to implement complex logic in our gadget. This is a continuation of hello world gadget, be sure you are familiar with that guide before continuing with this.
Creating our first wasm program
Create a folder named go
next to the program.bpf.c
file. In there
we create a new file named program.go
. As a first step, let's define the
init
, start
and stop
functions and emit some log messages from them:
package main
import (
api "github.com/inspektor-gadget/inspektor-gadget/wasmapi/go"
)
//export gadgetInit
func gadgetInit() int {
api.Info("init: hello from wasm")
return 0
}
//export gadgetStart
func gadgetStart() int {
api.Info("start: hello from wasm")
return 0
}
//export gadgetStop
func gadgetStop() int {
api.Info("stop: hello from wasm")
return 0
}
// The main function is not used, but it's still required by the compiler
func main() {}
Run the following commands in the go
directory to initialize the Golang module:
$ cd go
$ go mod init mygadget
go: creating new go.mod: module mygadget
go: to add module requirements and sums:
go mod tidy
$ go mod tidy
go: finding module for package github.com/inspektor-gadget/inspektor-gadget/wasmapi/go
go: found github.com/inspektor-gadget/inspektor-gadget/wasmapi/go in github.com/inspektor-gadget/inspektor-gadget v0.31.0
We also need a build.yaml
file that indicates the gadget includes a Golang
program that needs to be compiled to a wasm module:
wasm: go/program.go
Build the gadget
$ sudo ig image build . -t mygadget:latest
and run it:
$ sudo ig run mygadget:latest --verify-image=false
WARN[0000] image signature verification is disabled due to using corresponding option
INFO[0000] init: hello from wasm
WARN[0000] image signature verification is disabled due to using corresponding option
INFO[0000] init: hello from wasm
RUNTIME.CONTAINERNAME MNTNS_ID PID COMM FILENAME
INFO[0001] start: hello from wasm
...
^CINFO[0009] stop: hello from wasm
You can see how the different messages coming from wasm are printed in the terminal.
Manipulating fields
Now let's do something more interesting. Let's suppose we want to redact the user's name in the path of the file. Manipulating strings in eBPF is usually complicated, leave aside using regular expressions.
Our goal is to look for strings like /home/<user-name>/...
and redact (replace
by ***
) the user name part. This can be done by using a regular expression.
Let's add it to the gadgetInit
function like this:
//export gadgetInit
func gadgetInit() int {
api.Info("init: hello from wasm")
// Get the "open" datasource (name used in the GADGET_TRACER macro)
ds, err := api.GetDataSource("open")
if err != nil {
api.Warnf("failed to get datasource: %s", err)
return 1
}
// Get the field we're interested in
filenameF, err := ds.GetField("filename")
if err != nil {
api.Warnf("failed to get field: %s", err)
return 1
}
pattern := regexp.MustCompile(`^(/home/)(.*?)/(.*)$`)
// Subscribe to all events from "open" so we manipulate the data in the callback
ds.Subscribe(func(source api.DataSource, data api.Data) {
fileName, err := filenameF.String(data)
if err != nil {
api.Warnf("failed to get field fileName: %s", err)
return 1
}
replaced := pattern.ReplaceAllString(fileName, "${1}***/${3}")
filenameF.SetString(data, replaced)
}, 0)
return 0
}
Build and run the gadget again:
$ sudo ig image build . -t mygadget:latest
...
$ sudo ig run mygadget:latest --verify-image=false
WARN[0000] image signature verification is disabled due to using corresponding option
INFO[0000] init: hello from wasm
WARN[0000] image signature verification is disabled due to using corresponding option
INFO[0000] init: hello from wasm
RUNTIME.CONTAINERNAME MNTNS_ID PID COMM FILENAME
INFO[0001] start: hello from wasm
Let's generate some events from a container:
$ docker run --name c3 --rm -it busybox sh
# inside the container:
$ mkdir /home/mvb
$ touch /home/mvb/xxx.txt
$ cat /home/mvb/xxx.txt
The gadget redacts the user name as expected:
RUNTIME.CONTAINERNAME MNTNS_ID PID COMM FILENAME
c3 4026534569 226136 cat /home/***/xxx.txt
Adding new fields
There are cases where we want to add new fields from wasm. For instance, let's add a field that contains a human readable representation of the event.
The gadgetInit
functions now looks like:
//export gadgetInit
func gadgetInit() int {
api.Info("init: hello from wasm")
// Get the "open" datasource (name used in the GADGET_TRACER macro)
ds, err := api.GetDataSource("open")
if err != nil {
api.Warnf("failed to get datasource: %s", err)
return 1
}
// Get the field we're interested in
filenameF, err := ds.GetField("filename")
if err != nil {
api.Warnf("failed to get field: %s", err)
return 1
}
pidF, err := ds.GetField("pid")
if err != nil {
api.Warnf("failed to get field: %s", err)
return 1
}
humanF, err := ds.AddField("human", api.Kind_String)
if err != nil {
api.Warnf("failed to add field: %s", err)
return 1
}
pattern := regexp.MustCompile(`^(/home/)(.*?)/(.*)$`)
// Subscribe to all events from "open" so we manipulate the data in the callback
ds.Subscribe(func(source api.DataSource, data api.Data) {
fileName, err:= filenameF.String(data)
if err != nil {
api.Warnf("failed to get field fileName: %s", err)
return
}
replaced := pattern.ReplaceAllString(fileName, "${1}***/${3}")
filenameF.SetString(data, replaced)
pid, err:= pidF.Uint32(data)
if err != nil {
api.Warnf("failed to get pid: %s", err)
return
}
human := fmt.Sprintf("file %q was opened by %d", fileName, pid)
humanF.SetString(data, human)
}, 0)
return 0
}
Build and run the gadget again. This time using -o json
to easily see the
output from it:
$ sudo ig image build . -t mygadget:latest
...
$ sudo ig run mygadget:latest --verify-image=false -o jsonpretty
{
"comm": "cat",
"filename": "/home/***/xxx.txt",
"human": "file '/home/mvb/xxx.txt' was opened by 121351",
"k8s": {
"container": "",
"hostnetwork": false,
"namespace": "",
"node": "",
"pod": ""
},
"mntns_id": 4026534661,
"pid": 121351,
"runtime": {
"containerId": "2de33de4d1c73be918916322bf488a32f8b7a6eea0903422278fa13766e36f8f",
"containerImageDigest": "",
"containerImageName": "busybox",
"containerName": "c3",
"runtimeName": "docker"
}
}
Notice how the human field is there when cat /home/mvb/xxx.txt
is executed in
the container.
Dropping events
TBD!