Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugins): Windows eBPF Plugin for retina #1147

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions pkg/plugin/ebpfwindows/dropreasons_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package ebpfwindows

import (
"fmt"
)

// DropMin numbers less than this are non-drop reason codes
var DropMin uint8 = 130

// DropInvalid is the Invalid packet reason.
var DropInvalid uint8 = 2

// These values are shared with bpf/lib/common.h and api/v1/flow/flow.proto.
var dropErrors = map[uint8]string{
0: "Success",
2: "Invalid packet",
3: "Plain Text",
4: "Interface Decrypted",
5: "LB: No backend slot entry found",
6: "LB: No backend entry found",
7: "LB: Reverse entry update failed",
8: "LB: Reverse entry stale",
9: "Fragmented packet",
10: "Fragmented packet entry update failed",
11: "Missed tail call to custom program",
}

// Keep in sync with __id_for_file in bpf/lib/source_info.h.
var files = map[uint8]string{

// source files from bpf/
1: "bpf_host.c",
2: "bpf_lxc.c",
3: "bpf_overlay.c",
4: "bpf_xdp.c",
5: "bpf_sock.c",
6: "bpf_network.c",

// header files from bpf/lib/
101: "arp.h",
102: "drop.h",
103: "srv6.h",
104: "icmp6.h",
105: "nodeport.h",
106: "lb.h",
107: "mcast.h",
108: "ipv4.h",
109: "conntrack.h",
110: "l3.h",
111: "trace.h",
112: "encap.h",
113: "encrypt.h",
}

// BPFFileName returns the file name for the given BPF file id.
func BPFFileName(id uint8) string {
if name, ok := files[id]; ok {
return name
}
return fmt.Sprintf("unknown(%d)", id)
}

func extendedReason(extError int8) string {
if extError == int8(0) {
return ""
}
return fmt.Sprintf("%d", extError)

Check failure on line 67 in pkg/plugin/ebpfwindows/dropreasons_windows.go

View workflow job for this annotation

GitHub Actions / Lint (windows, amd64)

fmt.Sprintf can be replaced with faster strconv.Itoa (perfsprint)

Check failure on line 67 in pkg/plugin/ebpfwindows/dropreasons_windows.go

View workflow job for this annotation

GitHub Actions / Lint (windows, arm64)

fmt.Sprintf can be replaced with faster strconv.Itoa (perfsprint)
}

func DropReasonExt(reason uint8, extError int8) string {
if err, ok := dropErrors[reason]; ok {
if ext := extendedReason(extError); ext == "" {
return err
} else {

Check failure on line 74 in pkg/plugin/ebpfwindows/dropreasons_windows.go

View workflow job for this annotation

GitHub Actions / Lint (windows, amd64)

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)

Check failure on line 74 in pkg/plugin/ebpfwindows/dropreasons_windows.go

View workflow job for this annotation

GitHub Actions / Lint (windows, arm64)

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)
return err + ", " + ext
}
}
return fmt.Sprintf("%d, %d", reason, extError)
}

// DropReason prints the drop reason in a human readable string
func DropReason(reason uint8) string {
return DropReasonExt(reason, int8(0))
}
185 changes: 185 additions & 0 deletions pkg/plugin/ebpfwindows/ebpf_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package ebpfwindows

import (
"context"
"errors"
"time"
"unsafe"

v1 "github.com/cilium/cilium/pkg/hubble/api/v1"
kcfg "github.com/microsoft/retina/pkg/config"
"github.com/microsoft/retina/pkg/log"
"github.com/microsoft/retina/pkg/plugin/registry"
"go.uber.org/zap"
)

const (
// name of the ebpfwindows plugin
name = "windowseBPF"
)

var (
ErrInvalidEventData = errors.New("The Cilium Event Data is invalid")
)

// Plugin is the ebpfwindows plugin
type Plugin struct {
l *log.ZapLogger
cfg *kcfg.Config
}

func New(cfg *kcfg.Config) registry.Plugin {
return &Plugin{
l: log.Logger().Named(name),
cfg: cfg,
}
}

// Init is a no-op for the ebpfwindows plugin
func (p *Plugin) Init() error {
return nil
}

// Name returns the name of the ebpfwindows plugin
func (p *Plugin) Name() string {
return name
}

// Start the plugin by starting a periodic timer.
func (p *Plugin) Start(ctx context.Context) error {

p.l.Info("Start ebpfWindows plugin...")
p.pullCiliumMetricsAndEvents(ctx)
return nil
}

// metricsMapIterateCallback is the callback function that is called for each key-value pair in the metrics map.
func (p *Plugin) metricsMapIterateCallback(key *MetricsKey, value *MetricsValues) {
p.l.Info("MetricsMapIterateCallback")
p.l.Info("Key", zap.String("Key", key.String()))
p.l.Info("Value", zap.String("Value", value.String()))
}

// eventsMapCallback is the callback function that is called for each value in the events map.
func (p *Plugin) eventsMapCallback(data unsafe.Pointer, size uint64) int {
p.l.Info("EventsMapCallback")
p.l.Info("Size", zap.Uint64("Size", size))
err := p.handleTraceEvent(data, size)

if err != nil {
p.l.Error("Error handling trace event", zap.Error(err))
return -1
}

return 0
}

// pullCiliumeBPFMetrics is the function that is called periodically by the timer.
func (p *Plugin) pullCiliumMetricsAndEvents(ctx context.Context) {

eventsMap := NewEventsMap()
metricsMap := NewMetricsMap()

err := eventsMap.RegisterForCallback(p.eventsMapCallback)

if err != nil {
p.l.Error("Error registering for events map callback", zap.Error(err))
return
}

ticker := time.NewTicker(p.cfg.MetricsInterval)
defer ticker.Stop()

for {
select {
case <-ticker.C:
err := metricsMap.IterateWithCallback(p.metricsMapIterateCallback)
if err != nil {
p.l.Error("Error iterating metrics map", zap.Error(err))
}
case <-ctx.Done():
p.l.Error("ebpfwindows plugin canceling", zap.Error(ctx.Err()))
eventsMap.UnregisterForCallback()

Check failure on line 102 in pkg/plugin/ebpfwindows/ebpf_windows.go

View workflow job for this annotation

GitHub Actions / Lint (windows, amd64)

Error return value of `eventsMap.UnregisterForCallback` is not checked (errcheck)

Check failure on line 102 in pkg/plugin/ebpfwindows/ebpf_windows.go

View workflow job for this annotation

GitHub Actions / Lint (windows, arm64)

Error return value of `eventsMap.UnregisterForCallback` is not checked (errcheck)
return
}
}
}

// SetupChannel is a no-op for the ebpfwindows plugin
func (p *Plugin) SetupChannel(ch chan *v1.Event) error {

Check failure on line 109 in pkg/plugin/ebpfwindows/ebpf_windows.go

View workflow job for this annotation

GitHub Actions / Lint (windows, amd64)

unused-parameter: parameter 'ch' seems to be unused, consider removing or renaming it as _ (revive)

Check failure on line 109 in pkg/plugin/ebpfwindows/ebpf_windows.go

View workflow job for this annotation

GitHub Actions / Lint (windows, arm64)

unused-parameter: parameter 'ch' seems to be unused, consider removing or renaming it as _ (revive)
return nil
}

// Stop the plugin by cancelling the periodic timer.
func (p *Plugin) Stop() error {

p.l.Info("Stop ebpfWindows plugin...")
return nil
}

// Compile is a no-op for the ebpfwindows plugin
func (p *Plugin) Compile(context.Context) error {
return nil
}

// Generate is a no-op for the ebpfwindows plugin
func (p *Plugin) Generate(context.Context) error {
return nil
}

func (p *Plugin) handleDropNotify(dropNotify *DropNotify) {
p.l.Info("DropNotify", zap.String("DropNotify", dropNotify.String()))
}

func (p *Plugin) handleTraceNotify(traceNotify *TraceNotify) {
p.l.Info("TraceNotify", zap.String("TraceNotify", traceNotify.String()))
}

func (p *Plugin) handleTraceSockNotify(traceSockNotify *TraceSockNotify) {
p.l.Info("TraceSockNotify", zap.String("TraceSockNotify", traceSockNotify.String()))
}

func (p *Plugin) handleTraceEvent(data unsafe.Pointer, size uint64) error {

if uintptr(size) < unsafe.Sizeof(uint8(0)) {
return ErrInvalidEventData
}

eventType := *(*uint8)(data)

switch eventType {
case CiliumNotifyDrop:

if uintptr(size) < unsafe.Sizeof(DropNotify{}) {
p.l.Error("Invalid DropNotify data size", zap.Uint64("size", size))
return ErrInvalidEventData
}

dropNotify := (*DropNotify)(data)
p.handleDropNotify(dropNotify)

case CiliumNotifyTrace:

if uintptr(size) < unsafe.Sizeof(TraceNotify{}) {
p.l.Error("Invalid TraceNotify data size", zap.Uint64("size", size))
return ErrInvalidEventData
}

traceNotify := (*TraceNotify)(data)
p.handleTraceNotify(traceNotify)

case CiliumNotifyTraceSock:
if uintptr(size) < unsafe.Sizeof(TraceSockNotify{}) {
p.l.Error("Invalid TraceSockNotify data size", zap.Uint64("size", size))
return ErrInvalidEventData
}

traceSockNotify := (*TraceSockNotify)(data)
p.handleTraceSockNotify(traceSockNotify)

default:
p.l.Error("Unsupported event type", zap.Uint8("eventType", eventType))
}

return nil
}
71 changes: 71 additions & 0 deletions pkg/plugin/ebpfwindows/ebpf_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// nolint

package ebpfwindows

import (
"context"
"testing"
"time"

kcfg "github.com/microsoft/retina/pkg/config"
"github.com/microsoft/retina/pkg/log"
"go.uber.org/zap"
)

func TestPlugin(t *testing.T) {

Check failure on line 17 in pkg/plugin/ebpfwindows/ebpf_windows_test.go

View workflow job for this annotation

GitHub Actions / Lint (windows, amd64)

unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive)

Check failure on line 17 in pkg/plugin/ebpfwindows/ebpf_windows_test.go

View workflow job for this annotation

GitHub Actions / Lint (windows, arm64)

unused-parameter: parameter 't' seems to be unused, consider removing or renaming it as _ (revive)
log.SetupZapLogger(log.GetDefaultLogOpts())

Check failure on line 18 in pkg/plugin/ebpfwindows/ebpf_windows_test.go

View workflow job for this annotation

GitHub Actions / Lint (windows, amd64)

Error return value of `log.SetupZapLogger` is not checked (errcheck)

Check failure on line 18 in pkg/plugin/ebpfwindows/ebpf_windows_test.go

View workflow job for this annotation

GitHub Actions / Lint (windows, arm64)

Error return value of `log.SetupZapLogger` is not checked (errcheck)
l := log.Logger().Named("test-ebpf")

ctx := context.Background()

cfg := &kcfg.Config{
MetricsInterval: 1 * time.Second,
EnablePodLevel: true,
}

tt := New(cfg)

err := tt.Stop()
if err != nil {
l.Error("Failed to stop windows ebpf plugin", zap.Error(err))
return
}

ctxTimeout, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
err = tt.Generate(ctxTimeout)
if err != nil {
l.Error("Failed to generate the plugin specific header files", zap.Error(err))
return
}

err = tt.Compile(ctxTimeout)
if err != nil {
l.Error("Failed to compile the ebpf to generate bpf object", zap.Error(err))
return
}

err = tt.Init()
if err != nil {
l.Error("Failed to initialize plugin specific objects", zap.Error(err))
return
}

err = tt.Start(ctx)
if err != nil {
l.Error("Failed to start windows ebpf plugin", zap.Error(err))
return
}
l.Info("Started windows ebpf plugin")

defer func() {
if err := tt.Stop(); err != nil {
l.Error("Failed to stop windows ebpf plugin", zap.Error(err))
}
}()

for range ctx.Done() {
}
}
Loading
Loading