Files
cunicu/pkg/wg/tracer/tracer.go
Steffen Vogel dad8d5996b update Go module path
Signed-off-by: Steffen Vogel <post@steffenvogel.de>
2022-09-08 17:42:12 +02:00

136 lines
3.3 KiB
Go

//go:build tracer
// Package tracer uses Linux Kprobes to gather ephemeral keys from handshakes of local WireGuard interfaces
//
// Tested with Linux 5.15.0
package tracer
import (
"errors"
"fmt"
"strings"
"syscall"
"time"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/ringbuf"
"github.com/cilium/ebpf/rlimit"
"github.com/stv0g/cunicu/pkg/wg/tracer/kernel"
)
//go:generate make -C kernel config.h
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -tags tracer -cc clang -type handshake -target $GOARCH bpf kprobe_wg_index_hashtable_insert.c -- -I include
var bootTime time.Time
type HandshakeTracer struct {
Handshakes chan *Handshake
Errors chan error
reader *ringbuf.Reader
kprobe link.Link
}
func NewHandshakeTracer() (*HandshakeTracer, error) {
var err error
// Allow the current process to lock memory for eBPF resources.
if err := rlimit.RemoveMemlock(); err != nil {
return nil, fmt.Errorf("failed to remove memlock: %w", err)
}
ht := &HandshakeTracer{
Errors: make(chan error),
Handshakes: make(chan *Handshake),
}
if err := ht.Check(); err != nil {
return nil, err
}
// Load pre-compiled programs and maps into the kernel.
objs := bpfObjects{}
if err := loadBpfObjects(&objs, nil); err != nil {
return nil, fmt.Errorf("failed loading objects: %w", err)
}
defer objs.Close()
// Open a Kprobe at the entry point of the kernel function and attach the
// pre-compiled program. Each time the kernel function enters, the program
// will emit an event containing pid and command of the execved task.
if ht.kprobe, err = link.Kprobe("wg_index_hashtable_insert", objs.KprobeWgIndexHashtableInsert, nil); err != nil {
return nil, fmt.Errorf("failed opening kprobe: %w", err)
}
// Open a ringbuf reader from userspace RINGBUF map described in the
// eBPF C program.
ht.reader, err = ringbuf.NewReader(objs.Handshakes)
if err != nil {
return nil, fmt.Errorf("failed opening ringbuf reader: %w", err)
}
go ht.run()
return ht, nil
}
func charsToString(is []int8) string {
bs := []byte{}
for i := 0; is[i] != 0; i++ {
bs = append(bs, byte(is[i]))
}
return string(bs)
}
func (ht *HandshakeTracer) Check() error {
uts := &syscall.Utsname{}
if err := syscall.Uname(uts); err != nil {
return fmt.Errorf("failed to get utsname: %w", err)
}
machine := charsToString(uts.Machine[:])
release := charsToString(uts.Release[:])
if machine != kernel.TargetMachine {
return fmt.Errorf("machine mismatch: %s != %s", machine, kernel.TargetMachine)
}
if !strings.HasPrefix(release, kernel.TargetRelease) {
return fmt.Errorf("release mismatch: %s != %s", release, kernel.TargetRelease)
}
return nil
}
func (ht *HandshakeTracer) Close() error {
if err := ht.reader.Close(); err != nil {
return fmt.Errorf("failed closing ringbuf reader: %w", err)
}
if err := ht.kprobe.Close(); err != nil {
return fmt.Errorf("failed to close kprobe: %w", err)
}
return nil
}
func (ht *HandshakeTracer) run() {
for {
record, err := ht.reader.Read()
if err != nil {
if errors.Is(err, ringbuf.ErrClosed) {
return
}
ht.Errors <- fmt.Errorf("failed to read from ringbuf: %w", err)
continue
}
if hs, err := HandshakeFromBPFRecord(record); err != nil {
ht.Errors <- fmt.Errorf("failed to parse handshake from record: %w", err)
} else {
ht.Handshakes <- hs
}
}
}