From 6d77a7957024262c1508a35a1f7b82a86109fcc6 Mon Sep 17 00:00:00 2001 From: Vinod K L Swamy Date: Mon, 16 Dec 2024 13:23:15 -0800 Subject: [PATCH 01/10] Draft Version of Retina Windows eBPF Plugin --- pkg/plugin/ebpfwindows/dropreasons_windows.go | 84 +++++++++ pkg/plugin/ebpfwindows/ebpf_windows.go | 98 ++++++++++ pkg/plugin/ebpfwindows/ebpf_windows_test.go | 71 +++++++ pkg/plugin/ebpfwindows/eventsmap_windows.go | 1 + pkg/plugin/ebpfwindows/metricsmap_windows.go | 173 ++++++++++++++++++ 5 files changed, 427 insertions(+) create mode 100644 pkg/plugin/ebpfwindows/dropreasons_windows.go create mode 100644 pkg/plugin/ebpfwindows/ebpf_windows.go create mode 100644 pkg/plugin/ebpfwindows/ebpf_windows_test.go create mode 100644 pkg/plugin/ebpfwindows/eventsmap_windows.go create mode 100644 pkg/plugin/ebpfwindows/metricsmap_windows.go diff --git a/pkg/plugin/ebpfwindows/dropreasons_windows.go b/pkg/plugin/ebpfwindows/dropreasons_windows.go new file mode 100644 index 0000000000..d3523685e6 --- /dev/null +++ b/pkg/plugin/ebpfwindows/dropreasons_windows.go @@ -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 errors = 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) +} + +func DropReasonExt(reason uint8, extError int8) string { + if err, ok := errors[reason]; ok { + if ext := extendedReason(extError); ext == "" { + return err + } else { + 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)) +} diff --git a/pkg/plugin/ebpfwindows/ebpf_windows.go b/pkg/plugin/ebpfwindows/ebpf_windows.go new file mode 100644 index 0000000000..b3ac424eb7 --- /dev/null +++ b/pkg/plugin/ebpfwindows/ebpf_windows.go @@ -0,0 +1,98 @@ +package ebpfwindows + +import ( + "context" + "time" + + 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" +) + +// 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.pullCiliumMetrics(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())) +} + +// pullCiliumeBPFMetrics is the function that is called periodically by the timer. +func (p *Plugin) pullCiliumMetrics(ctx context.Context) { + + ticker := time.NewTicker(p.cfg.MetricsInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + metricsMap := NewMetricsMap() + 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())) + return + } + } +} + +// SetupChannel is a no-op for the ebpfwindows plugin +func (p *Plugin) SetupChannel(ch chan *v1.Event) error { + 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 +} diff --git a/pkg/plugin/ebpfwindows/ebpf_windows_test.go b/pkg/plugin/ebpfwindows/ebpf_windows_test.go new file mode 100644 index 0000000000..fbf5ac3bca --- /dev/null +++ b/pkg/plugin/ebpfwindows/ebpf_windows_test.go @@ -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) { + log.SetupZapLogger(log.GetDefaultLogOpts()) + 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() { + } +} diff --git a/pkg/plugin/ebpfwindows/eventsmap_windows.go b/pkg/plugin/ebpfwindows/eventsmap_windows.go new file mode 100644 index 0000000000..51fa992a4a --- /dev/null +++ b/pkg/plugin/ebpfwindows/eventsmap_windows.go @@ -0,0 +1 @@ +package ebpfwindows diff --git a/pkg/plugin/ebpfwindows/metricsmap_windows.go b/pkg/plugin/ebpfwindows/metricsmap_windows.go new file mode 100644 index 0000000000..6f545e4b36 --- /dev/null +++ b/pkg/plugin/ebpfwindows/metricsmap_windows.go @@ -0,0 +1,173 @@ +package ebpfwindows + +import ( + "fmt" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + dirUnknown = 0 + dirIngress = 1 + dirEgress = 2 + dirService = 3 +) + +// direction is the metrics direction i.e ingress (to an endpoint), +// egress (from an endpoint) or service (NodePort service being accessed from +// outside or a ClusterIP service being accessed from inside the cluster). +// If it's none of the above, we return UNKNOWN direction. +var direction = map[uint8]string{ + dirUnknown: "UNKNOWN", + dirIngress: "INGRESS", + dirEgress: "EGRESS", + dirService: "SERVICE", +} + +// Value must be in sync with struct metrics_key in +type MetricsKey struct { + Reason uint8 `align:"reason"` + Dir uint8 `align:"dir"` + // Line contains the line number of the metrics statement. + Line uint16 `align:"line"` + // File is the number of the source file containing the metrics statement. + File uint8 `align:"file"` + Reserved [3]uint8 `align:"reserved"` +} + +// Value must be in sync with struct metrics_value in +type MetricsValue struct { + Count uint64 `align:"count"` + Bytes uint64 `align:"bytes"` +} + +// MetricsMapValues is a slice of MetricsMapValue +type MetricsValues []MetricsValue + +// IterateCallback represents the signature of the callback function expected by +// the IterateWithCallback method, which in turn is used to iterate all the +// keys/values of a metrics map. +type IterateCallback func(*MetricsKey, *MetricsValues) + +// MetricsMap interface represents a metrics map, and can be reused to implement +// mock maps for unit tests. +type MetricsMap interface { + IterateWithCallback(IterateCallback) error +} + +type metricsMap struct { +} + +var ( + // Load the retinaebpfapi.dll + retinaEbpfApi = windows.NewLazyDLL("retinaebpfapi.dll") + // Load the enumerate_cilium_metricsmap function + enumMetricsMap = retinaEbpfApi.NewProc("enumerate_cilium_metricsmap") +) + +// ringBufferEventCallback type definition in Go +type enumMetricsCallback = func(key unsafe.Pointer, value unsafe.Pointer) int + +// Callbacks in Go can only be passed as functions with specific signatures and often need to be wrapped in a syscall-compatible function. +var enumCallBack enumMetricsCallback = nil + +// This function will be passed to the Windows API +func enumMetricsSysCallCallback(key unsafe.Pointer, value unsafe.Pointer) uintptr { + + if enumCallBack != nil { + return uintptr(enumCallBack(key, value)) + } + + return 0 +} + +// NewMetricsMap creates a new metrics map +func NewMetricsMap() MetricsMap { + return &metricsMap{} +} + +// IterateWithCallback iterates through all the keys/values of a metrics map, +// passing each key/value pair to the cb callback +func (m metricsMap) IterateWithCallback(cb IterateCallback) error { + + // Define the callback function in Go + enumCallBack = func(key unsafe.Pointer, value unsafe.Pointer) int { + metricsKey := (*MetricsKey)(key) + metricsValues := (*MetricsValues)(value) + cb(metricsKey, metricsValues) + return 0 + } + + // Convert the Go function into a syscall-compatible function + callback := syscall.NewCallback(enumMetricsSysCallCallback) + + // Call the API + ret, _, err := enumMetricsMap.Call( + uintptr(callback), + ) + + if ret != 0 { + return err + } + + return nil +} + +// MetricDirection gets the direction in human readable string format +func MetricDirection(dir uint8) string { + if desc, ok := direction[dir]; ok { + return desc + } + return direction[dirUnknown] +} + +// Direction gets the direction in human readable string format +func (k *MetricsKey) Direction() string { + return MetricDirection(k.Dir) +} + +// String returns the key in human readable string format +func (k *MetricsKey) String() string { + return fmt.Sprintf("Direction: %s, Reason: %s, File: %s, Line: %d", k.Direction(), DropReason(k.Reason), BPFFileName(k.File), k.Line) +} + +// DropForwardReason gets the forwarded/dropped reason in human readable string format +func (k *MetricsKey) DropForwardReason() string { + return DropReason(k.Reason) +} + +// FileName returns the filename where the event occurred, in string format. +func (k *MetricsKey) FileName() string { + return BPFFileName(k.File) +} + +// IsDrop checks if the reason is drop or not. +func (k *MetricsKey) IsDrop() bool { + return k.Reason == DropInvalid || k.Reason >= DropMin +} + +// Count returns the sum of all the per-CPU count values +func (vs MetricsValues) Count() uint64 { + c := uint64(0) + for _, v := range vs { + c += v.Count + } + + return c +} + +// Bytes returns the sum of all the per-CPU bytes values +func (vs MetricsValues) Bytes() uint64 { + b := uint64(0) + for _, v := range vs { + b += v.Bytes + } + + return b +} + +func (vs MetricsValues) String() string { + return fmt.Sprintf("Count: %d, Bytes: %d", vs.Count(), vs.Bytes()) +} From 2bc6a55df98747d43876628a13f2fb1a8c9368be Mon Sep 17 00:00:00 2001 From: Vinod K L Swamy Date: Mon, 16 Dec 2024 23:18:10 -0800 Subject: [PATCH 02/10] Added suppoprt for cilium_events_map --- pkg/plugin/ebpfwindows/ebpf_windows.go | 24 ++++++- pkg/plugin/ebpfwindows/eventsmap_windows.go | 74 ++++++++++++++++++++ pkg/plugin/ebpfwindows/metricsmap_windows.go | 4 +- 3 files changed, 97 insertions(+), 5 deletions(-) diff --git a/pkg/plugin/ebpfwindows/ebpf_windows.go b/pkg/plugin/ebpfwindows/ebpf_windows.go index b3ac424eb7..08e50ca779 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows.go @@ -3,6 +3,7 @@ package ebpfwindows import ( "context" "time" + "unsafe" v1 "github.com/cilium/cilium/pkg/hubble/api/v1" kcfg "github.com/microsoft/retina/pkg/config" @@ -43,7 +44,7 @@ func (p *Plugin) Name() string { func (p *Plugin) Start(ctx context.Context) error { p.l.Info("Start ebpfWindows plugin...") - p.pullCiliumMetrics(ctx) + p.pullCiliumMetricsAndEvents(ctx) return nil } @@ -54,8 +55,25 @@ func (p *Plugin) metricsMapIterateCallback(key *MetricsKey, value *MetricsValues 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 uint32) int { + p.l.Info("EventsMapCallback") + p.l.Info("Size", zap.Uint32("Size", size)) + return 0 +} + // pullCiliumeBPFMetrics is the function that is called periodically by the timer. -func (p *Plugin) pullCiliumMetrics(ctx context.Context) { +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() @@ -63,13 +81,13 @@ func (p *Plugin) pullCiliumMetrics(ctx context.Context) { for { select { case <-ticker.C: - metricsMap := NewMetricsMap() 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() return } } diff --git a/pkg/plugin/ebpfwindows/eventsmap_windows.go b/pkg/plugin/ebpfwindows/eventsmap_windows.go index 51fa992a4a..2915ccf3ec 100644 --- a/pkg/plugin/ebpfwindows/eventsmap_windows.go +++ b/pkg/plugin/ebpfwindows/eventsmap_windows.go @@ -1 +1,75 @@ package ebpfwindows + +import ( + "syscall" + "unsafe" +) + +var ( + registerEventsMapCallback = retinaEbpfApi.NewProc("register_cilium_eventsmap_callback") + unregisterEventsMapCallback = retinaEbpfApi.NewProc("unregister_cilium_eventsmap_callback") +) + +type eventsMapCallback func(data unsafe.Pointer, size uint32) int + +// Callbacks in Go can only be passed as functions with specific signatures and often need to be wrapped in a syscall-compatible function. +var eventsCallback eventsMapCallback = nil + +// This function will be passed to the Windows API +func eventsMapSysCallCallback(data unsafe.Pointer, size uint32) uintptr { + + if eventsCallback != nil { + return uintptr(eventsCallback(data, size)) + } + + return 0 +} + +// EventsMap interface represents a events map +type EventsMap interface { + RegisterForCallback(eventsMapCallback) error + UnregisterForCallback() error +} + +type eventsMap struct { + ringBuffer uintptr +} + +// NewEventsMap creates a new metrics map +func NewEventsMap() EventsMap { + return &eventsMap{ringBuffer: 0} +} + +// RegisterForCallback registers a callback function to be called when a new event is added to the events map +func (e *eventsMap) RegisterForCallback(cb eventsMapCallback) error { + + eventsCallback = cb + + // Convert the Go function into a syscall-compatible function + callback := syscall.NewCallback(eventsMapSysCallCallback) + + // Call the API + ret, _, err := registerEventsMapCallback.Call( + uintptr(callback), + uintptr(unsafe.Pointer(&e.ringBuffer)), + ) + + if ret != 0 { + return err + } + + return nil +} + +// UnregisterForCallback unregisters the callback function +func (e *eventsMap) UnregisterForCallback() error { + + // Call the API + ret, _, err := unregisterEventsMapCallback.Call(e.ringBuffer) + + if ret != 0 { + return err + } + + return nil +} diff --git a/pkg/plugin/ebpfwindows/metricsmap_windows.go b/pkg/plugin/ebpfwindows/metricsmap_windows.go index 6f545e4b36..39ba04c6ff 100644 --- a/pkg/plugin/ebpfwindows/metricsmap_windows.go +++ b/pkg/plugin/ebpfwindows/metricsmap_windows.go @@ -68,13 +68,13 @@ var ( ) // ringBufferEventCallback type definition in Go -type enumMetricsCallback = func(key unsafe.Pointer, value unsafe.Pointer) int +type enumMetricsCallback = func(key, value unsafe.Pointer) int // Callbacks in Go can only be passed as functions with specific signatures and often need to be wrapped in a syscall-compatible function. var enumCallBack enumMetricsCallback = nil // This function will be passed to the Windows API -func enumMetricsSysCallCallback(key unsafe.Pointer, value unsafe.Pointer) uintptr { +func enumMetricsSysCallCallback(key, value unsafe.Pointer) uintptr { if enumCallBack != nil { return uintptr(enumCallBack(key, value)) From d5bd668d529799e01c093fcb4fa7d123b2346b32 Mon Sep 17 00:00:00 2001 From: Vinod K L Swamy Date: Tue, 17 Dec 2024 18:58:45 -0800 Subject: [PATCH 03/10] Added Support for Parsing CILIUM_NOTIFY_DROP,CILIUM_NOTIFY_TRACE,CILIUM_NOTIFY_TRACE_SOCK events --- pkg/plugin/ebpfwindows/dropreasons_windows.go | 4 +- pkg/plugin/ebpfwindows/ebpf_windows.go | 73 +++++++++++- .../ebpfwindows/event_map_types_windows.go | 110 ++++++++++++++++++ pkg/plugin/ebpfwindows/eventsmap_windows.go | 4 +- pkg/plugin/ebpfwindows/metricsmap_windows.go | 19 ++- 5 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 pkg/plugin/ebpfwindows/event_map_types_windows.go diff --git a/pkg/plugin/ebpfwindows/dropreasons_windows.go b/pkg/plugin/ebpfwindows/dropreasons_windows.go index d3523685e6..9dae9a36be 100644 --- a/pkg/plugin/ebpfwindows/dropreasons_windows.go +++ b/pkg/plugin/ebpfwindows/dropreasons_windows.go @@ -11,7 +11,7 @@ var DropMin uint8 = 130 var DropInvalid uint8 = 2 // These values are shared with bpf/lib/common.h and api/v1/flow/flow.proto. -var errors = map[uint8]string{ +var dropErrors = map[uint8]string{ 0: "Success", 2: "Invalid packet", 3: "Plain Text", @@ -68,7 +68,7 @@ func extendedReason(extError int8) string { } func DropReasonExt(reason uint8, extError int8) string { - if err, ok := errors[reason]; ok { + if err, ok := dropErrors[reason]; ok { if ext := extendedReason(extError); ext == "" { return err } else { diff --git a/pkg/plugin/ebpfwindows/ebpf_windows.go b/pkg/plugin/ebpfwindows/ebpf_windows.go index 08e50ca779..8d9b3b6afe 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows.go @@ -2,6 +2,7 @@ package ebpfwindows import ( "context" + "errors" "time" "unsafe" @@ -17,6 +18,10 @@ const ( name = "windowseBPF" ) +var ( + ErrInvalidEventData = errors.New("The Cilium Event Data is invalid") +) + // Plugin is the ebpfwindows plugin type Plugin struct { l *log.ZapLogger @@ -56,9 +61,16 @@ func (p *Plugin) metricsMapIterateCallback(key *MetricsKey, value *MetricsValues } // eventsMapCallback is the callback function that is called for each value in the events map. -func (p *Plugin) eventsMapCallback(data unsafe.Pointer, size uint32) int { +func (p *Plugin) eventsMapCallback(data unsafe.Pointer, size uint64) int { p.l.Info("EventsMapCallback") - p.l.Info("Size", zap.Uint32("Size", size)) + 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 } @@ -114,3 +126,60 @@ func (p *Plugin) Compile(context.Context) error { 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 CILIUM_NOTIFY_DROP: + + 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 CILIUM_NOTIFY_TRACE: + + 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 CILIUM_NOTIFY_TRACE_SOCK: + 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 +} diff --git a/pkg/plugin/ebpfwindows/event_map_types_windows.go b/pkg/plugin/ebpfwindows/event_map_types_windows.go new file mode 100644 index 0000000000..7a5fc5570c --- /dev/null +++ b/pkg/plugin/ebpfwindows/event_map_types_windows.go @@ -0,0 +1,110 @@ +package ebpfwindows + +import ( + "fmt" + "net" +) + +// IP represents an IPv4 or IPv4 or IPv6 address +type IP struct { + Address [16]byte +} + +// TraceSockNotify is the notification for a socket trace +type TraceSockNotify struct { + Type uint8 + XlatePoint uint8 + DstIP IP + DstPort uint16 + SockCookie uint64 + CgroupID uint64 + L4Proto uint8 + IPv6 bool + Pad uint8 +} + +// NotifyCommonHdr is the common header for all notifications +type NotifyCommonHdr struct { + Type uint8 + Subtype uint8 + Source uint16 + Hash uint32 +} + +// NotifyCaptureHdr is the common header for all capture notifications +type NotifyCaptureHdr struct { + NotifyCommonHdr + LenOrig uint32 // Length of original packet + LenCap uint16 // Length of captured bytes + Version uint16 // Capture header version +} + +// DropNotify is the notification for a packet drop +type DropNotify struct { + NotifyCaptureHdr + SrcLabel uint32 + DstLabel uint32 + DstID uint32 // 0 for egress + Line uint16 + File uint8 + ExtError int8 + Ifindex uint32 +} + +// TraceNotify is the notification for a packet trace +type TraceNotify struct { + NotifyCaptureHdr + SrcLabel uint32 + DstLabel uint32 + DstID uint16 + Reason uint8 + IPv6 bool + Pad uint8 + Ifindex uint32 + OrigIP IP +} + +// Notification types +const ( + CILIUM_NOTIFY_UNSPEC = 0 + CILIUM_NOTIFY_DROP = 1 + CILIUM_NOTIFY_DBG_MSG = 2 + CILIUM_NOTIFY_DBG_CAPTURE = 3 + CILIUM_NOTIFY_TRACE = 4 + CILIUM_NOTIFY_POLICY_VERDICT = 5 + CILIUM_NOTIFY_CAPTURE = 6 + CILIUM_NOTIFY_TRACE_SOCK = 7 +) + +// String returns a string representation of the DropNotify +func (k *DropNotify) String() string { + + return fmt.Sprintf("Ifindex: %d, SrcLabel:%d, DstLabel:%d, File: %s, Line: %d", k.Ifindex, k.SrcLabel, k.DstLabel, BPFFileName(k.File), k.Line) +} + +// String returns a string representation of the TraceNotify +func (k *TraceNotify) String() string { + var ipAddress string = "" + + if k.IPv6 { + ipAddress = net.IP(k.OrigIP.Address[:]).String() + } else { + ipAddress = net.IP(k.OrigIP.Address[:3]).String() + } + + return fmt.Sprintf("Ifindex: %d, SrcLabel:%d, DstLabel:%d, IpV6:%t, OrigIP:%s", k.Ifindex, k.SrcLabel, k.DstLabel, k.IPv6, ipAddress) +} + +// String returns a string representation of the TraceSockNotify +func (k *TraceSockNotify) String() string { + + var ipAddress string = "" + + if k.IPv6 { + ipAddress = net.IP(k.DstIP.Address[:]).String() + } else { + ipAddress = net.IP(k.DstIP.Address[:3]).String() + } + + return fmt.Sprintf("DstIP:%s, DstPort:%d, SockCookie:%d, CgroupID:%d, L4Proto:%d, IPv6:%t", ipAddress, k.DstPort, k.SockCookie, k.CgroupID, k.L4Proto, k.IPv6) +} diff --git a/pkg/plugin/ebpfwindows/eventsmap_windows.go b/pkg/plugin/ebpfwindows/eventsmap_windows.go index 2915ccf3ec..50b789be1f 100644 --- a/pkg/plugin/ebpfwindows/eventsmap_windows.go +++ b/pkg/plugin/ebpfwindows/eventsmap_windows.go @@ -10,13 +10,13 @@ var ( unregisterEventsMapCallback = retinaEbpfApi.NewProc("unregister_cilium_eventsmap_callback") ) -type eventsMapCallback func(data unsafe.Pointer, size uint32) int +type eventsMapCallback func(data unsafe.Pointer, size uint64) int // Callbacks in Go can only be passed as functions with specific signatures and often need to be wrapped in a syscall-compatible function. var eventsCallback eventsMapCallback = nil // This function will be passed to the Windows API -func eventsMapSysCallCallback(data unsafe.Pointer, size uint32) uintptr { +func eventsMapSysCallCallback(data unsafe.Pointer, size uint64) uintptr { if eventsCallback != nil { return uintptr(eventsCallback(data, size)) diff --git a/pkg/plugin/ebpfwindows/metricsmap_windows.go b/pkg/plugin/ebpfwindows/metricsmap_windows.go index 39ba04c6ff..79f69a1c60 100644 --- a/pkg/plugin/ebpfwindows/metricsmap_windows.go +++ b/pkg/plugin/ebpfwindows/metricsmap_windows.go @@ -2,6 +2,7 @@ package ebpfwindows import ( "fmt" + "reflect" "syscall" "unsafe" @@ -68,16 +69,16 @@ var ( ) // ringBufferEventCallback type definition in Go -type enumMetricsCallback = func(key, value unsafe.Pointer) int +type enumMetricsCallback = func(key, value unsafe.Pointer, valueSize int) int // Callbacks in Go can only be passed as functions with specific signatures and often need to be wrapped in a syscall-compatible function. var enumCallBack enumMetricsCallback = nil // This function will be passed to the Windows API -func enumMetricsSysCallCallback(key, value unsafe.Pointer) uintptr { +func enumMetricsSysCallCallback(key, value unsafe.Pointer, valueSize int) uintptr { if enumCallBack != nil { - return uintptr(enumCallBack(key, value)) + return uintptr(enumCallBack(key, value, valueSize)) } return 0 @@ -93,10 +94,16 @@ func NewMetricsMap() MetricsMap { func (m metricsMap) IterateWithCallback(cb IterateCallback) error { // Define the callback function in Go - enumCallBack = func(key unsafe.Pointer, value unsafe.Pointer) int { + enumCallBack = func(key unsafe.Pointer, value unsafe.Pointer, valueSize int) int { + + var metricsValues MetricsValues + sh := (*reflect.SliceHeader)(unsafe.Pointer(&metricsValues)) + sh.Data = uintptr(value) + sh.Len = valueSize + sh.Cap = valueSize + metricsKey := (*MetricsKey)(key) - metricsValues := (*MetricsValues)(value) - cb(metricsKey, metricsValues) + cb(metricsKey, &metricsValues) return 0 } From e4b54f5081cbce00651a2d71851d0fbd2a3bd7b3 Mon Sep 17 00:00:00 2001 From: Vinod K L Swamy Date: Wed, 18 Dec 2024 10:24:48 -0800 Subject: [PATCH 04/10] Some Bug Fixes --- pkg/plugin/ebpfwindows/dropreasons_windows.go | 100 ++++----- pkg/plugin/ebpfwindows/ebpf_windows.go | 190 +++++++++--------- pkg/plugin/ebpfwindows/ebpf_windows_test.go | 98 ++++----- ..._windows.go => eventsmap_types_windows.go} | 66 +++--- pkg/plugin/ebpfwindows/eventsmap_windows.go | 60 +++--- pkg/plugin/ebpfwindows/metricsmap_windows.go | 146 +++++++------- 6 files changed, 335 insertions(+), 325 deletions(-) rename pkg/plugin/ebpfwindows/{event_map_types_windows.go => eventsmap_types_windows.go} (72%) diff --git a/pkg/plugin/ebpfwindows/dropreasons_windows.go b/pkg/plugin/ebpfwindows/dropreasons_windows.go index 9dae9a36be..0dbf9a86c3 100644 --- a/pkg/plugin/ebpfwindows/dropreasons_windows.go +++ b/pkg/plugin/ebpfwindows/dropreasons_windows.go @@ -1,7 +1,7 @@ package ebpfwindows import ( - "fmt" + "fmt" ) // DropMin numbers less than this are non-drop reason codes @@ -12,73 +12,73 @@ 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", + 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", + // 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", + // 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) + 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) + if extError == int8(0) { + return "" + } + return fmt.Sprintf("%d", extError) } func DropReasonExt(reason uint8, extError int8) string { - if err, ok := dropErrors[reason]; ok { - if ext := extendedReason(extError); ext == "" { - return err - } else { - return err + ", " + ext - } - } - return fmt.Sprintf("%d, %d", reason, extError) + if err, ok := dropErrors[reason]; ok { + if ext := extendedReason(extError); ext == "" { + return err + } else { + 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)) + return DropReasonExt(reason, int8(0)) } diff --git a/pkg/plugin/ebpfwindows/ebpf_windows.go b/pkg/plugin/ebpfwindows/ebpf_windows.go index 8d9b3b6afe..745726eb5f 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows.go @@ -1,185 +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" + "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" + // name of the ebpfwindows plugin + name = "windowseBPF" ) var ( - ErrInvalidEventData = errors.New("The Cilium Event Data is invalid") + ErrInvalidEventData = errors.New("The Cilium Event Data is invalid") ) // Plugin is the ebpfwindows plugin type Plugin struct { - l *log.ZapLogger - cfg *kcfg.Config + l *log.ZapLogger + cfg *kcfg.Config } func New(cfg *kcfg.Config) registry.Plugin { - return &Plugin{ - l: log.Logger().Named(name), - cfg: cfg, - } + 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 + return nil } // Name returns the name of the ebpfwindows plugin func (p *Plugin) Name() string { - return name + 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 + 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())) + 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) + 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 - } + if err != nil { + p.l.Error("Error handling trace event", zap.Error(err)) + return -1 + } - return 0 + return 0 } // pullCiliumeBPFMetrics is the function that is called periodically by the timer. func (p *Plugin) pullCiliumMetricsAndEvents(ctx context.Context) { - eventsMap := NewEventsMap() - metricsMap := NewMetricsMap() + eventsMap := NewEventsMap() + metricsMap := NewMetricsMap() - err := eventsMap.RegisterForCallback(p.eventsMapCallback) + err := eventsMap.RegisterForCallback(p.eventsMapCallback) - if err != nil { - p.l.Error("Error registering for events map callback", zap.Error(err)) - return - } + 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() + 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() - return - } - } + 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() + return + } + } } // SetupChannel is a no-op for the ebpfwindows plugin func (p *Plugin) SetupChannel(ch chan *v1.Event) error { - return nil + return nil } // Stop the plugin by cancelling the periodic timer. func (p *Plugin) Stop() error { - p.l.Info("Stop ebpfWindows plugin...") - return nil + 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 + return nil } // Generate is a no-op for the ebpfwindows plugin func (p *Plugin) Generate(context.Context) error { - return nil + return nil } func (p *Plugin) handleDropNotify(dropNotify *DropNotify) { - p.l.Info("DropNotify", zap.String("DropNotify", dropNotify.String())) + p.l.Info("DropNotify", zap.String("DropNotify", dropNotify.String())) } func (p *Plugin) handleTraceNotify(traceNotify *TraceNotify) { - p.l.Info("TraceNotify", zap.String("TraceNotify", traceNotify.String())) + p.l.Info("TraceNotify", zap.String("TraceNotify", traceNotify.String())) } func (p *Plugin) handleTraceSockNotify(traceSockNotify *TraceSockNotify) { - p.l.Info("TraceSockNotify", zap.String("TraceSockNotify", traceSockNotify.String())) + 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 - } + if uintptr(size) < unsafe.Sizeof(uint8(0)) { + return ErrInvalidEventData + } - eventType := *(*uint8)(data) + eventType := *(*uint8)(data) - switch eventType { - case CILIUM_NOTIFY_DROP: + switch eventType { + case CiliumNotifyDrop: - if uintptr(size) < unsafe.Sizeof(DropNotify{}) { - p.l.Error("Invalid DropNotify data size", zap.Uint64("size", size)) - return ErrInvalidEventData - } + 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) + dropNotify := (*DropNotify)(data) + p.handleDropNotify(dropNotify) - case CILIUM_NOTIFY_TRACE: + case CiliumNotifyTrace: - if uintptr(size) < unsafe.Sizeof(TraceNotify{}) { - p.l.Error("Invalid TraceNotify data size", zap.Uint64("size", size)) - return ErrInvalidEventData - } + 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) + traceNotify := (*TraceNotify)(data) + p.handleTraceNotify(traceNotify) - case CILIUM_NOTIFY_TRACE_SOCK: - if uintptr(size) < unsafe.Sizeof(TraceSockNotify{}) { - p.l.Error("Invalid TraceSockNotify data size", zap.Uint64("size", size)) - return ErrInvalidEventData - } + 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) + traceSockNotify := (*TraceSockNotify)(data) + p.handleTraceSockNotify(traceSockNotify) - default: - p.l.Error("Unsupported event type", zap.Uint8("eventType", eventType)) - } + default: + p.l.Error("Unsupported event type", zap.Uint8("eventType", eventType)) + } - return nil + return nil } diff --git a/pkg/plugin/ebpfwindows/ebpf_windows_test.go b/pkg/plugin/ebpfwindows/ebpf_windows_test.go index fbf5ac3bca..50d436c200 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows_test.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows_test.go @@ -5,67 +5,67 @@ package ebpfwindows import ( - "context" - "testing" - "time" + "context" + "testing" + "time" - kcfg "github.com/microsoft/retina/pkg/config" - "github.com/microsoft/retina/pkg/log" - "go.uber.org/zap" + kcfg "github.com/microsoft/retina/pkg/config" + "github.com/microsoft/retina/pkg/log" + "go.uber.org/zap" ) func TestPlugin(t *testing.T) { - log.SetupZapLogger(log.GetDefaultLogOpts()) - l := log.Logger().Named("test-ebpf") + log.SetupZapLogger(log.GetDefaultLogOpts()) + l := log.Logger().Named("test-ebpf") - ctx := context.Background() + ctx := context.Background() - cfg := &kcfg.Config{ - MetricsInterval: 1 * time.Second, - EnablePodLevel: true, - } + cfg := &kcfg.Config{ + MetricsInterval: 1 * time.Second, + EnablePodLevel: true, + } - tt := New(cfg) + tt := New(cfg) - err := tt.Stop() - if err != nil { - l.Error("Failed to stop windows ebpf plugin", zap.Error(err)) - return - } + 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 - } + 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.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.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") + 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)) - } - }() + defer func() { + if err := tt.Stop(); err != nil { + l.Error("Failed to stop windows ebpf plugin", zap.Error(err)) + } + }() - for range ctx.Done() { - } + for range ctx.Done() { + } } diff --git a/pkg/plugin/ebpfwindows/event_map_types_windows.go b/pkg/plugin/ebpfwindows/eventsmap_types_windows.go similarity index 72% rename from pkg/plugin/ebpfwindows/event_map_types_windows.go rename to pkg/plugin/ebpfwindows/eventsmap_types_windows.go index 7a5fc5570c..6a9fcdeff0 100644 --- a/pkg/plugin/ebpfwindows/event_map_types_windows.go +++ b/pkg/plugin/ebpfwindows/eventsmap_types_windows.go @@ -1,13 +1,18 @@ package ebpfwindows import ( + "bytes" + "encoding/binary" "fmt" "net" ) // IP represents an IPv4 or IPv4 or IPv6 address type IP struct { - Address [16]byte + Address uint32 + Pad1 uint32 + Pad2 uint32 + Pad3 uint32 } // TraceSockNotify is the notification for a socket trace @@ -20,7 +25,6 @@ type TraceSockNotify struct { CgroupID uint64 L4Proto uint8 IPv6 bool - Pad uint8 } // NotifyCommonHdr is the common header for all notifications @@ -59,23 +63,44 @@ type TraceNotify struct { DstID uint16 Reason uint8 IPv6 bool - Pad uint8 Ifindex uint32 OrigIP IP } // Notification types const ( - CILIUM_NOTIFY_UNSPEC = 0 - CILIUM_NOTIFY_DROP = 1 - CILIUM_NOTIFY_DBG_MSG = 2 - CILIUM_NOTIFY_DBG_CAPTURE = 3 - CILIUM_NOTIFY_TRACE = 4 - CILIUM_NOTIFY_POLICY_VERDICT = 5 - CILIUM_NOTIFY_CAPTURE = 6 - CILIUM_NOTIFY_TRACE_SOCK = 7 + CiliumNotifyUnspec = 0 + CiliumNotifyDrop = 1 + CiliumNotifyDebugMessage = 2 + CiliumNotifyDebugCapture = 3 + CiliumNotifyTrace = 4 + CiliumNotifyPolicyVerdict = 5 + CiliumNotifyCapture = 6 + CiliumNotifyTraceSock = 7 ) +func (ip *IP) ConvertToString(IPv6 bool) string { + var ipAddress string + var buf bytes.Buffer + + err := binary.Write(&buf, binary.BigEndian, *ip) + + if err != nil { + return "" + } + + byteArray := buf.Bytes() + + if IPv6 { + ipAddress = net.IP(byteArray[:16]).String() + } else { + ipAddress = net.IP(byteArray[:4]).String() + } + + return ipAddress + +} + // String returns a string representation of the DropNotify func (k *DropNotify) String() string { @@ -84,27 +109,12 @@ func (k *DropNotify) String() string { // String returns a string representation of the TraceNotify func (k *TraceNotify) String() string { - var ipAddress string = "" - - if k.IPv6 { - ipAddress = net.IP(k.OrigIP.Address[:]).String() - } else { - ipAddress = net.IP(k.OrigIP.Address[:3]).String() - } - + ipAddress := k.OrigIP.ConvertToString(k.IPv6) return fmt.Sprintf("Ifindex: %d, SrcLabel:%d, DstLabel:%d, IpV6:%t, OrigIP:%s", k.Ifindex, k.SrcLabel, k.DstLabel, k.IPv6, ipAddress) } // String returns a string representation of the TraceSockNotify func (k *TraceSockNotify) String() string { - - var ipAddress string = "" - - if k.IPv6 { - ipAddress = net.IP(k.DstIP.Address[:]).String() - } else { - ipAddress = net.IP(k.DstIP.Address[:3]).String() - } - + ipAddress := k.DstIP.ConvertToString(k.IPv6) return fmt.Sprintf("DstIP:%s, DstPort:%d, SockCookie:%d, CgroupID:%d, L4Proto:%d, IPv6:%t", ipAddress, k.DstPort, k.SockCookie, k.CgroupID, k.L4Proto, k.IPv6) } diff --git a/pkg/plugin/ebpfwindows/eventsmap_windows.go b/pkg/plugin/ebpfwindows/eventsmap_windows.go index 50b789be1f..0e5d1ad4ea 100644 --- a/pkg/plugin/ebpfwindows/eventsmap_windows.go +++ b/pkg/plugin/ebpfwindows/eventsmap_windows.go @@ -1,13 +1,13 @@ package ebpfwindows import ( - "syscall" - "unsafe" + "syscall" + "unsafe" ) var ( - registerEventsMapCallback = retinaEbpfApi.NewProc("register_cilium_eventsmap_callback") - unregisterEventsMapCallback = retinaEbpfApi.NewProc("unregister_cilium_eventsmap_callback") + registerEventsMapCallback = retinaEbpfApi.NewProc("register_cilium_eventsmap_callback") + unregisterEventsMapCallback = retinaEbpfApi.NewProc("unregister_cilium_eventsmap_callback") ) type eventsMapCallback func(data unsafe.Pointer, size uint64) int @@ -18,58 +18,58 @@ var eventsCallback eventsMapCallback = nil // This function will be passed to the Windows API func eventsMapSysCallCallback(data unsafe.Pointer, size uint64) uintptr { - if eventsCallback != nil { - return uintptr(eventsCallback(data, size)) - } + if eventsCallback != nil { + return uintptr(eventsCallback(data, size)) + } - return 0 + return 0 } // EventsMap interface represents a events map type EventsMap interface { - RegisterForCallback(eventsMapCallback) error - UnregisterForCallback() error + RegisterForCallback(eventsMapCallback) error + UnregisterForCallback() error } type eventsMap struct { - ringBuffer uintptr + ringBuffer uintptr } // NewEventsMap creates a new metrics map func NewEventsMap() EventsMap { - return &eventsMap{ringBuffer: 0} + return &eventsMap{ringBuffer: 0} } // RegisterForCallback registers a callback function to be called when a new event is added to the events map func (e *eventsMap) RegisterForCallback(cb eventsMapCallback) error { - eventsCallback = cb + eventsCallback = cb - // Convert the Go function into a syscall-compatible function - callback := syscall.NewCallback(eventsMapSysCallCallback) + // Convert the Go function into a syscall-compatible function + callback := syscall.NewCallback(eventsMapSysCallCallback) - // Call the API - ret, _, err := registerEventsMapCallback.Call( - uintptr(callback), - uintptr(unsafe.Pointer(&e.ringBuffer)), - ) + // Call the API + ret, _, err := registerEventsMapCallback.Call( + uintptr(callback), + uintptr(unsafe.Pointer(&e.ringBuffer)), + ) - if ret != 0 { - return err - } + if ret != 0 { + return err + } - return nil + return nil } // UnregisterForCallback unregisters the callback function func (e *eventsMap) UnregisterForCallback() error { - // Call the API - ret, _, err := unregisterEventsMapCallback.Call(e.ringBuffer) + // Call the API + ret, _, err := unregisterEventsMapCallback.Call(e.ringBuffer) - if ret != 0 { - return err - } + if ret != 0 { + return err + } - return nil + return nil } diff --git a/pkg/plugin/ebpfwindows/metricsmap_windows.go b/pkg/plugin/ebpfwindows/metricsmap_windows.go index 79f69a1c60..79ebdcdf22 100644 --- a/pkg/plugin/ebpfwindows/metricsmap_windows.go +++ b/pkg/plugin/ebpfwindows/metricsmap_windows.go @@ -1,19 +1,19 @@ package ebpfwindows import ( - "fmt" - "reflect" - "syscall" - "unsafe" + "fmt" + "reflect" + "syscall" + "unsafe" - "golang.org/x/sys/windows" + "golang.org/x/sys/windows" ) const ( - dirUnknown = 0 - dirIngress = 1 - dirEgress = 2 - dirService = 3 + dirUnknown = 0 + dirIngress = 1 + dirEgress = 2 + dirService = 3 ) // direction is the metrics direction i.e ingress (to an endpoint), @@ -21,27 +21,27 @@ const ( // outside or a ClusterIP service being accessed from inside the cluster). // If it's none of the above, we return UNKNOWN direction. var direction = map[uint8]string{ - dirUnknown: "UNKNOWN", - dirIngress: "INGRESS", - dirEgress: "EGRESS", - dirService: "SERVICE", + dirUnknown: "UNKNOWN", + dirIngress: "INGRESS", + dirEgress: "EGRESS", + dirService: "SERVICE", } // Value must be in sync with struct metrics_key in type MetricsKey struct { - Reason uint8 `align:"reason"` - Dir uint8 `align:"dir"` - // Line contains the line number of the metrics statement. - Line uint16 `align:"line"` - // File is the number of the source file containing the metrics statement. - File uint8 `align:"file"` - Reserved [3]uint8 `align:"reserved"` + Reason uint8 `align:"reason"` + Dir uint8 `align:"dir"` + // Line contains the line number of the metrics statement. + Line uint16 `align:"line"` + // File is the number of the source file containing the metrics statement. + File uint8 `align:"file"` + Reserved [3]uint8 `align:"reserved"` } // Value must be in sync with struct metrics_value in type MetricsValue struct { - Count uint64 `align:"count"` - Bytes uint64 `align:"bytes"` + Count uint64 `align:"count"` + Bytes uint64 `align:"bytes"` } // MetricsMapValues is a slice of MetricsMapValue @@ -55,17 +55,17 @@ type IterateCallback func(*MetricsKey, *MetricsValues) // MetricsMap interface represents a metrics map, and can be reused to implement // mock maps for unit tests. type MetricsMap interface { - IterateWithCallback(IterateCallback) error + IterateWithCallback(IterateCallback) error } type metricsMap struct { } var ( - // Load the retinaebpfapi.dll - retinaEbpfApi = windows.NewLazyDLL("retinaebpfapi.dll") - // Load the enumerate_cilium_metricsmap function - enumMetricsMap = retinaEbpfApi.NewProc("enumerate_cilium_metricsmap") + // Load the retinaebpfapi.dll + retinaEbpfApi = windows.NewLazyDLL("retinaebpfapi.dll") + // Load the enumerate_cilium_metricsmap function + enumMetricsMap = retinaEbpfApi.NewProc("enumerate_cilium_metricsmap") ) // ringBufferEventCallback type definition in Go @@ -77,104 +77,104 @@ var enumCallBack enumMetricsCallback = nil // This function will be passed to the Windows API func enumMetricsSysCallCallback(key, value unsafe.Pointer, valueSize int) uintptr { - if enumCallBack != nil { - return uintptr(enumCallBack(key, value, valueSize)) - } + if enumCallBack != nil { + return uintptr(enumCallBack(key, value, valueSize)) + } - return 0 + return 0 } // NewMetricsMap creates a new metrics map func NewMetricsMap() MetricsMap { - return &metricsMap{} + return &metricsMap{} } // IterateWithCallback iterates through all the keys/values of a metrics map, // passing each key/value pair to the cb callback func (m metricsMap) IterateWithCallback(cb IterateCallback) error { - // Define the callback function in Go - enumCallBack = func(key unsafe.Pointer, value unsafe.Pointer, valueSize int) int { + // Define the callback function in Go + enumCallBack = func(key unsafe.Pointer, value unsafe.Pointer, valueSize int) int { - var metricsValues MetricsValues - sh := (*reflect.SliceHeader)(unsafe.Pointer(&metricsValues)) - sh.Data = uintptr(value) - sh.Len = valueSize - sh.Cap = valueSize + var metricsValues MetricsValues + sh := (*reflect.SliceHeader)(unsafe.Pointer(&metricsValues)) + sh.Data = uintptr(value) + sh.Len = valueSize + sh.Cap = valueSize - metricsKey := (*MetricsKey)(key) - cb(metricsKey, &metricsValues) - return 0 - } + metricsKey := (*MetricsKey)(key) + cb(metricsKey, &metricsValues) + return 0 + } - // Convert the Go function into a syscall-compatible function - callback := syscall.NewCallback(enumMetricsSysCallCallback) + // Convert the Go function into a syscall-compatible function + callback := syscall.NewCallback(enumMetricsSysCallCallback) - // Call the API - ret, _, err := enumMetricsMap.Call( - uintptr(callback), - ) + // Call the API + ret, _, err := enumMetricsMap.Call( + uintptr(callback), + ) - if ret != 0 { - return err - } + if ret != 0 { + return err + } - return nil + return nil } // MetricDirection gets the direction in human readable string format func MetricDirection(dir uint8) string { - if desc, ok := direction[dir]; ok { - return desc - } - return direction[dirUnknown] + if desc, ok := direction[dir]; ok { + return desc + } + return direction[dirUnknown] } // Direction gets the direction in human readable string format func (k *MetricsKey) Direction() string { - return MetricDirection(k.Dir) + return MetricDirection(k.Dir) } // String returns the key in human readable string format func (k *MetricsKey) String() string { - return fmt.Sprintf("Direction: %s, Reason: %s, File: %s, Line: %d", k.Direction(), DropReason(k.Reason), BPFFileName(k.File), k.Line) + return fmt.Sprintf("Direction: %s, Reason: %s, File: %s, Line: %d", k.Direction(), DropReason(k.Reason), BPFFileName(k.File), k.Line) } // DropForwardReason gets the forwarded/dropped reason in human readable string format func (k *MetricsKey) DropForwardReason() string { - return DropReason(k.Reason) + return DropReason(k.Reason) } // FileName returns the filename where the event occurred, in string format. func (k *MetricsKey) FileName() string { - return BPFFileName(k.File) + return BPFFileName(k.File) } // IsDrop checks if the reason is drop or not. func (k *MetricsKey) IsDrop() bool { - return k.Reason == DropInvalid || k.Reason >= DropMin + return k.Reason == DropInvalid || k.Reason >= DropMin } // Count returns the sum of all the per-CPU count values func (vs MetricsValues) Count() uint64 { - c := uint64(0) - for _, v := range vs { - c += v.Count - } + c := uint64(0) + for _, v := range vs { + c += v.Count + } - return c + return c } // Bytes returns the sum of all the per-CPU bytes values func (vs MetricsValues) Bytes() uint64 { - b := uint64(0) - for _, v := range vs { - b += v.Bytes - } + b := uint64(0) + for _, v := range vs { + b += v.Bytes + } - return b + return b } func (vs MetricsValues) String() string { - return fmt.Sprintf("Count: %d, Bytes: %d", vs.Count(), vs.Bytes()) + return fmt.Sprintf("Count: %d, Bytes: %d", vs.Count(), vs.Bytes()) } From 99b03070e52c3202cbc038ab5e6911bd04d241b0 Mon Sep 17 00:00:00 2001 From: Vinod K L Swamy Date: Fri, 20 Dec 2024 16:24:51 -0800 Subject: [PATCH 05/10] Added code to instantiate the hubble parser, enricher and construct a dummy flow object to send it to enricher --- pkg/plugin/ebpfwindows/ebpf_windows.go | 91 +++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/pkg/plugin/ebpfwindows/ebpf_windows.go b/pkg/plugin/ebpfwindows/ebpf_windows.go index 745726eb5f..a67fa7e625 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows.go @@ -3,13 +3,20 @@ package ebpfwindows import ( "context" "errors" + "net" "time" "unsafe" + "github.com/cilium/cilium/api/v1/flow" v1 "github.com/cilium/cilium/pkg/hubble/api/v1" + hp "github.com/cilium/cilium/pkg/hubble/parser" kcfg "github.com/microsoft/retina/pkg/config" + "github.com/microsoft/retina/pkg/enricher" "github.com/microsoft/retina/pkg/log" + "github.com/microsoft/retina/pkg/metrics" "github.com/microsoft/retina/pkg/plugin/registry" + "github.com/microsoft/retina/pkg/utils" + "github.com/sirupsen/logrus" "go.uber.org/zap" ) @@ -20,12 +27,20 @@ const ( var ( ErrInvalidEventData = errors.New("The Cilium Event Data is invalid") + ErrNilEnricher = errors.New("enricher is nil") ) // Plugin is the ebpfwindows plugin type Plugin struct { - l *log.ZapLogger - cfg *kcfg.Config + l *log.ZapLogger + cfg *kcfg.Config + enricher enricher.EnricherInterface + externalChannel chan *v1.Event + parser *hp.Parser +} + +func init() { + registry.Add(name, New) } func New(cfg *kcfg.Config) registry.Plugin { @@ -37,6 +52,23 @@ func New(cfg *kcfg.Config) registry.Plugin { // Init is a no-op for the ebpfwindows plugin func (p *Plugin) Init() error { + + parser, err := hp.New(logrus.WithField("cilium", "parser"), + nil, + nil, + nil, + nil, + nil, + nil, + nil, + ) + + if err != nil { + p.l.Fatal("Failed to create parser", zap.Error(err)) + return err + } + + p.parser = parser return nil } @@ -49,6 +81,12 @@ func (p *Plugin) Name() string { func (p *Plugin) Start(ctx context.Context) error { p.l.Info("Start ebpfWindows plugin...") + p.enricher = enricher.Instance() + + if p.enricher == nil { + return ErrNilEnricher + } + p.pullCiliumMetricsAndEvents(ctx) return nil } @@ -105,8 +143,9 @@ func (p *Plugin) pullCiliumMetricsAndEvents(ctx context.Context) { } } -// SetupChannel is a no-op for the ebpfwindows plugin +// SetupChannel saves the external channel to which the plugin will send events. func (p *Plugin) SetupChannel(ch chan *v1.Event) error { + p.externalChannel = ch return nil } @@ -181,5 +220,51 @@ func (p *Plugin) handleTraceEvent(data unsafe.Pointer, size uint64) error { p.l.Error("Unsupported event type", zap.Uint8("eventType", eventType)) } + t1 := time.Now().UnixNano() + + // Hardcoded values for flow object. These values will be replaced by the actual values from the event. + fl := utils.ToFlow( + p.l, + t1, + net.ParseIP("192.168.0.1").To4(), // Src IP + net.ParseIP("192.168.0.2").To4(), // Dst IP + 80, // Src Port + 1024, // Dst Port + 6, // Protocol + 2, + flow.Verdict_DROPPED, + ) + + if fl == nil { + p.l.Warn("Could not convert event to flow", zap.Any("handleTraceEvent", data)) + return ErrInvalidEventData + } + + if fl != nil { + + ev := &v1.Event{ + Event: fl, + Timestamp: fl.GetTime(), + } + + if p.enricher != nil { + p.enricher.Write(ev) + } else { + p.l.Error("enricher is nil when writing event") + } + + // Write the event to the external channel. + if p.externalChannel != nil { + select { + case p.externalChannel <- ev: + default: + // Channel is full, drop the event. + // We shouldn't slow down the reader. + metrics.LostEventsCounter.WithLabelValues(utils.ExternalChannel, name).Inc() + } + } + + } + return nil } From 2703bfff2da9a369ff6cf88e29870fc3617b3bd4 Mon Sep 17 00:00:00 2001 From: Vinod K L Swamy Date: Mon, 23 Dec 2024 11:58:12 -0800 Subject: [PATCH 06/10] Added the enricher loop to the test code --- pkg/plugin/ebpfwindows/ebpf_windows.go | 40 ++++++++++----------- pkg/plugin/ebpfwindows/ebpf_windows_test.go | 8 +++++ 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/pkg/plugin/ebpfwindows/ebpf_windows.go b/pkg/plugin/ebpfwindows/ebpf_windows.go index a67fa7e625..f8d16398be 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows.go @@ -34,7 +34,7 @@ var ( type Plugin struct { l *log.ZapLogger cfg *kcfg.Config - enricher enricher.EnricherInterface + enricher *enricher.Enricher externalChannel chan *v1.Event parser *hp.Parser } @@ -240,30 +240,26 @@ func (p *Plugin) handleTraceEvent(data unsafe.Pointer, size uint64) error { return ErrInvalidEventData } - if fl != nil { - - ev := &v1.Event{ - Event: fl, - Timestamp: fl.GetTime(), - } + ev := &v1.Event{ + Event: fl, + Timestamp: fl.GetTime(), + } - if p.enricher != nil { - p.enricher.Write(ev) - } else { - p.l.Error("enricher is nil when writing event") - } + if p.enricher != nil { + p.enricher.Write(ev) + } else { + p.l.Error("enricher is nil when writing event") + } - // Write the event to the external channel. - if p.externalChannel != nil { - select { - case p.externalChannel <- ev: - default: - // Channel is full, drop the event. - // We shouldn't slow down the reader. - metrics.LostEventsCounter.WithLabelValues(utils.ExternalChannel, name).Inc() - } + // Write the event to the external channel. + if p.externalChannel != nil { + select { + case p.externalChannel <- ev: + default: + // Channel is full, drop the event. + // We shouldn't slow down the reader. + metrics.LostEventsCounter.WithLabelValues(utils.ExternalChannel, name).Inc() } - } return nil diff --git a/pkg/plugin/ebpfwindows/ebpf_windows_test.go b/pkg/plugin/ebpfwindows/ebpf_windows_test.go index 50d436c200..b5357ba0d1 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows_test.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows_test.go @@ -10,7 +10,10 @@ import ( "time" kcfg "github.com/microsoft/retina/pkg/config" + "github.com/microsoft/retina/pkg/controllers/cache" + "github.com/microsoft/retina/pkg/enricher" "github.com/microsoft/retina/pkg/log" + "github.com/microsoft/retina/pkg/pubsub" "go.uber.org/zap" ) @@ -25,6 +28,11 @@ func TestPlugin(t *testing.T) { EnablePodLevel: true, } + c := cache.New(pubsub.New()) + e := enricher.New(ctx, c) + e.Run() + defer e.Reader.Close() + tt := New(cfg) err := tt.Stop() From 193a92158380cd433942858739a354ccc60ff53a Mon Sep 17 00:00:00 2001 From: Vinod K L Swamy Date: Mon, 23 Dec 2024 20:31:20 -0800 Subject: [PATCH 07/10] Added basic packet metrics from metrics map --- pkg/plugin/ebpfwindows/ebpf_windows.go | 34 +++- pkg/plugin/ebpfwindows/metricsmap_windows.go | 156 ++++++++++--------- 2 files changed, 116 insertions(+), 74 deletions(-) diff --git a/pkg/plugin/ebpfwindows/ebpf_windows.go b/pkg/plugin/ebpfwindows/ebpf_windows.go index f8d16398be..f7e35a5618 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows.go @@ -22,7 +22,17 @@ import ( const ( // name of the ebpfwindows plugin - name = "windowseBPF" + name string = "windowseBPF" + // name of the metrics + packetsReceived string = "win_packets_recv_count" + packetsSent string = "win_packets_sent_count" + bytesSent string = "win_bytes_sent_count" + bytesReceived string = "win_bytes_recv_count" + droppedPacketsIncoming string = "win_packets_recv_drop_count" + droppedPacketsOutgoing string = "win_packets_sent_drop_count" + // metrics direction + ingressLabel = "ingress" + egressLabel = "egress" ) var ( @@ -96,6 +106,28 @@ 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())) + + if key.IsDrop() { + if key.IsEgress() { + metrics.DropPacketsGauge.WithLabelValues(egressLabel).Set(float64(value.Count())) + } else if key.IsIngress() { + metrics.DropPacketsGauge.WithLabelValues(ingressLabel).Set(float64(value.Count())) + } + + } else { + + if key.IsEgress() { + metrics.ForwardBytesGauge.WithLabelValues(egressLabel).Set(float64(value.Bytes())) + p.l.Debug("emitting bytes sent count metric", zap.Uint64(bytesSent, value.Bytes())) + metrics.WindowsGauge.WithLabelValues(packetsSent).Set(float64(value.Count())) + p.l.Debug("emitting packets sent count metric", zap.Uint64(packetsSent, value.Count())) + } else if key.IsIngress() { + metrics.ForwardPacketsGauge.WithLabelValues(ingressLabel).Set(float64(value.Count())) + p.l.Debug("emitting packets received count metric", zap.Uint64(packetsReceived, value.Count())) + metrics.ForwardBytesGauge.WithLabelValues(ingressLabel).Set(float64(value.Bytes())) + p.l.Debug("emitting bytes received count metric", zap.Uint64(bytesReceived, value.Bytes())) + } + } } // eventsMapCallback is the callback function that is called for each value in the events map. diff --git a/pkg/plugin/ebpfwindows/metricsmap_windows.go b/pkg/plugin/ebpfwindows/metricsmap_windows.go index 79ebdcdf22..157bcc94d8 100644 --- a/pkg/plugin/ebpfwindows/metricsmap_windows.go +++ b/pkg/plugin/ebpfwindows/metricsmap_windows.go @@ -1,19 +1,19 @@ package ebpfwindows import ( - "fmt" - "reflect" - "syscall" - "unsafe" + "fmt" + "reflect" + "syscall" + "unsafe" - "golang.org/x/sys/windows" + "golang.org/x/sys/windows" ) const ( - dirUnknown = 0 - dirIngress = 1 - dirEgress = 2 - dirService = 3 + dirUnknown = 0 + dirIngress = 1 + dirEgress = 2 + dirService = 3 ) // direction is the metrics direction i.e ingress (to an endpoint), @@ -21,27 +21,27 @@ const ( // outside or a ClusterIP service being accessed from inside the cluster). // If it's none of the above, we return UNKNOWN direction. var direction = map[uint8]string{ - dirUnknown: "UNKNOWN", - dirIngress: "INGRESS", - dirEgress: "EGRESS", - dirService: "SERVICE", + dirUnknown: "UNKNOWN", + dirIngress: "INGRESS", + dirEgress: "EGRESS", + dirService: "SERVICE", } // Value must be in sync with struct metrics_key in type MetricsKey struct { - Reason uint8 `align:"reason"` - Dir uint8 `align:"dir"` - // Line contains the line number of the metrics statement. - Line uint16 `align:"line"` - // File is the number of the source file containing the metrics statement. - File uint8 `align:"file"` - Reserved [3]uint8 `align:"reserved"` + Reason uint8 `align:"reason"` + Dir uint8 `align:"dir"` + // Line contains the line number of the metrics statement. + Line uint16 `align:"line"` + // File is the number of the source file containing the metrics statement. + File uint8 `align:"file"` + Reserved [3]uint8 `align:"reserved"` } // Value must be in sync with struct metrics_value in type MetricsValue struct { - Count uint64 `align:"count"` - Bytes uint64 `align:"bytes"` + Count uint64 `align:"count"` + Bytes uint64 `align:"bytes"` } // MetricsMapValues is a slice of MetricsMapValue @@ -55,17 +55,17 @@ type IterateCallback func(*MetricsKey, *MetricsValues) // MetricsMap interface represents a metrics map, and can be reused to implement // mock maps for unit tests. type MetricsMap interface { - IterateWithCallback(IterateCallback) error + IterateWithCallback(IterateCallback) error } type metricsMap struct { } var ( - // Load the retinaebpfapi.dll - retinaEbpfApi = windows.NewLazyDLL("retinaebpfapi.dll") - // Load the enumerate_cilium_metricsmap function - enumMetricsMap = retinaEbpfApi.NewProc("enumerate_cilium_metricsmap") + // Load the retinaebpfapi.dll + retinaEbpfApi = windows.NewLazyDLL("retinaebpfapi.dll") + // Load the enumerate_cilium_metricsmap function + enumMetricsMap = retinaEbpfApi.NewProc("enumerate_cilium_metricsmap") ) // ringBufferEventCallback type definition in Go @@ -77,104 +77,114 @@ var enumCallBack enumMetricsCallback = nil // This function will be passed to the Windows API func enumMetricsSysCallCallback(key, value unsafe.Pointer, valueSize int) uintptr { - if enumCallBack != nil { - return uintptr(enumCallBack(key, value, valueSize)) - } + if enumCallBack != nil { + return uintptr(enumCallBack(key, value, valueSize)) + } - return 0 + return 0 } // NewMetricsMap creates a new metrics map func NewMetricsMap() MetricsMap { - return &metricsMap{} + return &metricsMap{} } // IterateWithCallback iterates through all the keys/values of a metrics map, // passing each key/value pair to the cb callback func (m metricsMap) IterateWithCallback(cb IterateCallback) error { - // Define the callback function in Go - enumCallBack = func(key unsafe.Pointer, value unsafe.Pointer, valueSize int) int { + // Define the callback function in Go + enumCallBack = func(key unsafe.Pointer, value unsafe.Pointer, valueSize int) int { - var metricsValues MetricsValues - sh := (*reflect.SliceHeader)(unsafe.Pointer(&metricsValues)) - sh.Data = uintptr(value) - sh.Len = valueSize - sh.Cap = valueSize + var metricsValues MetricsValues + sh := (*reflect.SliceHeader)(unsafe.Pointer(&metricsValues)) + sh.Data = uintptr(value) + sh.Len = valueSize + sh.Cap = valueSize - metricsKey := (*MetricsKey)(key) - cb(metricsKey, &metricsValues) - return 0 - } + metricsKey := (*MetricsKey)(key) + cb(metricsKey, &metricsValues) + return 0 + } - // Convert the Go function into a syscall-compatible function - callback := syscall.NewCallback(enumMetricsSysCallCallback) + // Convert the Go function into a syscall-compatible function + callback := syscall.NewCallback(enumMetricsSysCallCallback) - // Call the API - ret, _, err := enumMetricsMap.Call( - uintptr(callback), - ) + // Call the API + ret, _, err := enumMetricsMap.Call( + uintptr(callback), + ) - if ret != 0 { - return err - } + if ret != 0 { + return err + } - return nil + return nil } // MetricDirection gets the direction in human readable string format func MetricDirection(dir uint8) string { - if desc, ok := direction[dir]; ok { - return desc - } - return direction[dirUnknown] + if desc, ok := direction[dir]; ok { + return desc + } + return direction[dirUnknown] } // Direction gets the direction in human readable string format func (k *MetricsKey) Direction() string { - return MetricDirection(k.Dir) + return MetricDirection(k.Dir) } // String returns the key in human readable string format func (k *MetricsKey) String() string { - return fmt.Sprintf("Direction: %s, Reason: %s, File: %s, Line: %d", k.Direction(), DropReason(k.Reason), BPFFileName(k.File), k.Line) + return fmt.Sprintf("Direction: %s, Reason: %s, File: %s, Line: %d", k.Direction(), DropReason(k.Reason), BPFFileName(k.File), k.Line) } // DropForwardReason gets the forwarded/dropped reason in human readable string format func (k *MetricsKey) DropForwardReason() string { - return DropReason(k.Reason) + return DropReason(k.Reason) } // FileName returns the filename where the event occurred, in string format. func (k *MetricsKey) FileName() string { - return BPFFileName(k.File) + return BPFFileName(k.File) } // IsDrop checks if the reason is drop or not. func (k *MetricsKey) IsDrop() bool { - return k.Reason == DropInvalid || k.Reason >= DropMin + return k.Reason == DropInvalid || k.Reason >= DropMin +} + +// IsIngress checks if the direction is ingress or not. +func (k *MetricsKey) IsIngress() bool { + return k.Dir == dirIngress +} + +// IsEgress checks if the direction is egress or not. +func (k *MetricsKey) IsEgress() bool { + return k.Dir == dirEgress } // Count returns the sum of all the per-CPU count values func (vs MetricsValues) Count() uint64 { - c := uint64(0) - for _, v := range vs { - c += v.Count - } + c := uint64(0) + for _, v := range vs { + c += v.Count + } - return c + return c } // Bytes returns the sum of all the per-CPU bytes values func (vs MetricsValues) Bytes() uint64 { - b := uint64(0) - for _, v := range vs { - b += v.Bytes - } + b := uint64(0) + for _, v := range vs { + b += v.Bytes + } - return b + return b } func (vs MetricsValues) String() string { - return fmt.Sprintf("Count: %d, Bytes: %d", vs.Count(), vs.Bytes()) + return fmt.Sprintf("Count: %d, Bytes: %d", vs.Count(), vs.Bytes()) } From be9bd9a9bb9bb15e8c7dd84979768ff4a2bf547c Mon Sep 17 00:00:00 2001 From: Vinod K L Swamy Date: Thu, 26 Dec 2024 10:02:46 -0800 Subject: [PATCH 08/10] Initialized Metrics in the test code --- pkg/plugin/ebpfwindows/ebpf_windows_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/plugin/ebpfwindows/ebpf_windows_test.go b/pkg/plugin/ebpfwindows/ebpf_windows_test.go index b5357ba0d1..3563d0159d 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows_test.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows_test.go @@ -13,6 +13,7 @@ import ( "github.com/microsoft/retina/pkg/controllers/cache" "github.com/microsoft/retina/pkg/enricher" "github.com/microsoft/retina/pkg/log" + "github.com/microsoft/retina/pkg/metrics" "github.com/microsoft/retina/pkg/pubsub" "go.uber.org/zap" ) @@ -32,6 +33,7 @@ func TestPlugin(t *testing.T) { e := enricher.New(ctx, c) e.Run() defer e.Reader.Close() + metrics.InitializeMetrics() tt := New(cfg) From fcc625d6e246c981e59933746a73620210fa3f4b Mon Sep 17 00:00:00 2001 From: Vinod K L Swamy Date: Fri, 17 Jan 2025 13:00:14 -0800 Subject: [PATCH 09/10] Added the NoOpGetters for Hubble Parser --- pkg/plugin/ebpfwindows/ebpf_windows.go | 15 +- pkg/plugin/ebpfwindows/noop.go | 245 +++++++++++++++++++++++++ 2 files changed, 253 insertions(+), 7 deletions(-) create mode 100644 pkg/plugin/ebpfwindows/noop.go diff --git a/pkg/plugin/ebpfwindows/ebpf_windows.go b/pkg/plugin/ebpfwindows/ebpf_windows.go index f7e35a5618..dfdef73107 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows.go @@ -64,13 +64,14 @@ func New(cfg *kcfg.Config) registry.Plugin { func (p *Plugin) Init() error { parser, err := hp.New(logrus.WithField("cilium", "parser"), - nil, - nil, - nil, - nil, - nil, - nil, - nil, + // We use noop getters here since we will use our own custom parser in hubble + &NoopEndpointGetter, + &NoopIdentityGetter, + &NoopDNSGetter, + &NoopIPGetter, + &NoopServiceGetter, + &NoopLinkGetter, + &NoopPodMetadataGetter, ) if err != nil { diff --git a/pkg/plugin/ebpfwindows/noop.go b/pkg/plugin/ebpfwindows/noop.go new file mode 100644 index 0000000000..dca8c072ae --- /dev/null +++ b/pkg/plugin/ebpfwindows/noop.go @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Hubble + +// Copyright Authors of Cilium + +package ebpfwindows + +import ( + "net" + "net/netip" + "time" + + flowpb "github.com/cilium/cilium/api/v1/flow" + "github.com/cilium/cilium/api/v1/models" + "github.com/cilium/cilium/pkg/cgroups/manager" + v1 "github.com/cilium/cilium/pkg/hubble/api/v1" + "github.com/cilium/cilium/pkg/identity" + "github.com/cilium/cilium/pkg/ipcache" + slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" + "github.com/cilium/cilium/pkg/labels" + "github.com/cilium/cilium/pkg/policy" +) + +// FakeFQDNCache is used for unit tests that needs FQDNCache and/or DNSGetter. +type FakeFQDNCache struct { + OnInitializeFrom func(entries []*models.DNSLookup) + OnAddDNSLookup func(epID uint32, lookupTime time.Time, domainName string, ips []net.IP, ttl uint32) + OnGetNamesOf func(epID uint32, ip netip.Addr) []string +} + +// InitializeFrom implements FQDNCache.InitializeFrom. +func (f *FakeFQDNCache) InitializeFrom(entries []*models.DNSLookup) { + if f.OnInitializeFrom != nil { + f.OnInitializeFrom(entries) + return + } + panic("InitializeFrom([]*models.DNSLookup) should not have been called since it was not defined") +} + +// AddDNSLookup implements FQDNCache.AddDNSLookup. +func (f *FakeFQDNCache) AddDNSLookup(epID uint32, lookupTime time.Time, domainName string, ips []net.IP, ttl uint32) { + if f.OnAddDNSLookup != nil { + f.OnAddDNSLookup(epID, lookupTime, domainName, ips, ttl) + return + } + panic("AddDNSLookup(uint32, time.Time, string, []net.IP, uint32) should not have been called since it was not defined") +} + +// GetNamesOf implements FQDNCache.GetNameOf. +func (f *FakeFQDNCache) GetNamesOf(epID uint32, ip netip.Addr) []string { + if f.OnGetNamesOf != nil { + return f.OnGetNamesOf(epID, ip) + } + panic("GetNamesOf(uint32, netip.Addr) should not have been called since it was not defined") +} + +// NoopDNSGetter always returns an empty response. +var NoopDNSGetter = FakeFQDNCache{ + OnGetNamesOf: func(_ uint32, _ netip.Addr) (fqdns []string) { + return nil + }, +} + +// FakeEndpointGetter is used for unit tests that needs EndpointGetter. +type FakeEndpointGetter struct { + OnGetEndpointInfo func(ip netip.Addr) (endpoint v1.EndpointInfo, ok bool) + OnGetEndpointInfoByID func(id uint16) (endpoint v1.EndpointInfo, ok bool) +} + +// GetEndpointInfo implements EndpointGetter.GetEndpointInfo. +func (f *FakeEndpointGetter) GetEndpointInfo(ip netip.Addr) (endpoint v1.EndpointInfo, ok bool) { + if f.OnGetEndpointInfo != nil { + return f.OnGetEndpointInfo(ip) + } + panic("OnGetEndpointInfo not set") +} + +// GetEndpointInfoByID implements EndpointGetter.GetEndpointInfoByID. +func (f *FakeEndpointGetter) GetEndpointInfoByID(id uint16) (endpoint v1.EndpointInfo, ok bool) { + if f.OnGetEndpointInfoByID != nil { + return f.OnGetEndpointInfoByID(id) + } + panic("GetEndpointInfoByID not set") +} + +// NoopEndpointGetter always returns an empty response. +var NoopEndpointGetter = FakeEndpointGetter{ + OnGetEndpointInfo: func(_ netip.Addr) (endpoint v1.EndpointInfo, ok bool) { + return nil, false + }, + OnGetEndpointInfoByID: func(_ uint16) (endpoint v1.EndpointInfo, ok bool) { + return nil, false + }, +} + +type FakeLinkGetter struct{} + +func (e *FakeLinkGetter) Name(_ uint32) string { + return "lo" +} + +func (e *FakeLinkGetter) GetIfNameCached(ifindex int) (string, bool) { + return e.Name(uint32(ifindex)), true //nolint:gosec // this is a noop +} + +var NoopLinkGetter = FakeLinkGetter{} + +// FakeIPGetter is used for unit tests that needs IPGetter. +type FakeIPGetter struct { + OnGetK8sMetadata func(ip netip.Addr) *ipcache.K8sMetadata + OnLookupSecIDByIP func(ip netip.Addr) (ipcache.Identity, bool) +} + +// GetK8sMetadata implements FakeIPGetter.GetK8sMetadata. +func (f *FakeIPGetter) GetK8sMetadata(ip netip.Addr) *ipcache.K8sMetadata { + if f.OnGetK8sMetadata != nil { + return f.OnGetK8sMetadata(ip) + } + panic("OnGetK8sMetadata not set") +} + +// LookupSecIDByIP implements FakeIPGetter.LookupSecIDByIP. +func (f *FakeIPGetter) LookupSecIDByIP(ip netip.Addr) (ipcache.Identity, bool) { + if f.OnLookupSecIDByIP != nil { + return f.OnLookupSecIDByIP(ip) + } + panic("OnLookupByIP not set") +} + +// NoopIPGetter always returns an empty response. +var NoopIPGetter = FakeIPGetter{ + OnGetK8sMetadata: func(_ netip.Addr) *ipcache.K8sMetadata { + return nil + }, + OnLookupSecIDByIP: func(_ netip.Addr) (ipcache.Identity, bool) { + return ipcache.Identity{}, false + }, +} + +// FakeServiceGetter is used for unit tests that need ServiceGetter. +type FakeServiceGetter struct { + OnGetServiceByAddr func(ip netip.Addr, port uint16) *flowpb.Service +} + +// GetServiceByAddr implements FakeServiceGetter.GetServiceByAddr. +func (f *FakeServiceGetter) GetServiceByAddr(ip netip.Addr, port uint16) *flowpb.Service { + if f.OnGetServiceByAddr != nil { + return f.OnGetServiceByAddr(ip, port) + } + panic("OnGetServiceByAddr not set") +} + +// NoopServiceGetter always returns an empty response. +var NoopServiceGetter = FakeServiceGetter{ + OnGetServiceByAddr: func(_ netip.Addr, _ uint16) *flowpb.Service { + return nil + }, +} + +// FakeIdentityGetter is used for unit tests that need IdentityGetter. +type FakeIdentityGetter struct { + OnGetIdentity func(securityIdentity uint32) (*identity.Identity, error) +} + +// GetIdentity implements IdentityGetter.GetIPIdentity. +func (f *FakeIdentityGetter) GetIdentity(securityIdentity uint32) (*identity.Identity, error) { + if f.OnGetIdentity != nil { + return f.OnGetIdentity(securityIdentity) + } + panic("OnGetIdentity not set") +} + +// NoopIdentityGetter always returns an empty response. +var NoopIdentityGetter = FakeIdentityGetter{ + OnGetIdentity: func(_ uint32) (*identity.Identity, error) { + return &identity.Identity{}, nil + }, +} + +// FakeEndpointInfo implements v1.EndpointInfo for unit tests. All interface +// methods return values exposed in the fields. +type FakeEndpointInfo struct { + ContainerIDs []string + ID uint64 + Identity identity.NumericIdentity + IPv4 net.IP + IPv6 net.IP + PodName string + PodNamespace string + Labels []string + Pod *slim_corev1.Pod + + PolicyMap map[policy.Key]labels.LabelArrayList + PolicyRevision uint64 +} + +// GetID returns the ID of the endpoint. +func (e *FakeEndpointInfo) GetID() uint64 { + return e.ID +} + +// GetIdentity returns the numerical security identity of the endpoint. +func (e *FakeEndpointInfo) GetIdentity() identity.NumericIdentity { + return e.Identity +} + +// GetK8sPodName returns the pod name of the endpoint. +func (e *FakeEndpointInfo) GetK8sPodName() string { + return e.PodName +} + +// GetK8sNamespace returns the pod namespace of the endpoint. +func (e *FakeEndpointInfo) GetK8sNamespace() string { + return e.PodNamespace +} + +// GetLabels returns the labels of the endpoint. +func (e *FakeEndpointInfo) GetLabels() []string { + return e.Labels +} + +// GetPod return the pod object of the endpoint. +func (e *FakeEndpointInfo) GetPod() *slim_corev1.Pod { + return e.Pod +} + +func (e *FakeEndpointInfo) GetRealizedPolicyRuleLabelsForKey(key policy.Key) ( + derivedFrom labels.LabelArrayList, + revision uint64, + ok bool, +) { + derivedFrom, ok = e.PolicyMap[key] + return derivedFrom, e.PolicyRevision, ok +} + +// FakePodMetadataGetter is used for unit tests that need a PodMetadataGetter. +type FakePodMetadataGetter struct{} + +// GetPodMetadataForContainer implements getters.PodMetadataGetter. +func (f *FakePodMetadataGetter) GetPodMetadataForContainer(_ uint64) *manager.PodMetadata { + panic("unimplemented") +} + +// NoopPodMetadataGetter always returns an empty response. +var NoopPodMetadataGetter = FakePodMetadataGetter{} From e69de9c269c99978ba33be33c2746e225bcae066 Mon Sep 17 00:00:00 2001 From: Vinod K L Swamy Date: Mon, 20 Jan 2025 18:01:00 -0800 Subject: [PATCH 10/10] Fixed the golint errors --- pkg/plugin/ebpfwindows/dropreasons_windows.go | 103 ++-- pkg/plugin/ebpfwindows/ebpf_windows.go | 440 +++++++++--------- pkg/plugin/ebpfwindows/ebpf_windows_test.go | 118 ++--- .../ebpfwindows/eventsmap_types_windows.go | 16 +- pkg/plugin/ebpfwindows/eventsmap_windows.go | 62 +-- pkg/plugin/ebpfwindows/metricsmap_windows.go | 6 +- 6 files changed, 376 insertions(+), 369 deletions(-) diff --git a/pkg/plugin/ebpfwindows/dropreasons_windows.go b/pkg/plugin/ebpfwindows/dropreasons_windows.go index 0dbf9a86c3..1657677a3f 100644 --- a/pkg/plugin/ebpfwindows/dropreasons_windows.go +++ b/pkg/plugin/ebpfwindows/dropreasons_windows.go @@ -1,7 +1,8 @@ package ebpfwindows import ( - "fmt" + "fmt" + "strconv" ) // DropMin numbers less than this are non-drop reason codes @@ -12,73 +13,75 @@ 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", + 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", + // 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", + // 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) + 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) + if extError == 0 { + return "" + } + return strconv.Itoa(int(extError)) } func DropReasonExt(reason uint8, extError int8) string { - if err, ok := dropErrors[reason]; ok { - if ext := extendedReason(extError); ext == "" { - return err - } else { - return err + ", " + ext - } - } - return fmt.Sprintf("%d, %d", reason, extError) + var ext string + + if err, ok := dropErrors[reason]; ok { + if ext = extendedReason(extError); ext == "" { + return err + } + 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)) + return DropReasonExt(reason, int8(0)) } diff --git a/pkg/plugin/ebpfwindows/ebpf_windows.go b/pkg/plugin/ebpfwindows/ebpf_windows.go index dfdef73107..a11dc3b1cd 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows.go @@ -1,299 +1,303 @@ package ebpfwindows import ( - "context" - "errors" - "net" - "time" - "unsafe" - - "github.com/cilium/cilium/api/v1/flow" - v1 "github.com/cilium/cilium/pkg/hubble/api/v1" - hp "github.com/cilium/cilium/pkg/hubble/parser" - kcfg "github.com/microsoft/retina/pkg/config" - "github.com/microsoft/retina/pkg/enricher" - "github.com/microsoft/retina/pkg/log" - "github.com/microsoft/retina/pkg/metrics" - "github.com/microsoft/retina/pkg/plugin/registry" - "github.com/microsoft/retina/pkg/utils" - "github.com/sirupsen/logrus" - "go.uber.org/zap" + "context" + "errors" + "net" + "time" + "unsafe" + + "github.com/cilium/cilium/api/v1/flow" + v1 "github.com/cilium/cilium/pkg/hubble/api/v1" + hp "github.com/cilium/cilium/pkg/hubble/parser" + kcfg "github.com/microsoft/retina/pkg/config" + "github.com/microsoft/retina/pkg/enricher" + "github.com/microsoft/retina/pkg/log" + "github.com/microsoft/retina/pkg/metrics" + "github.com/microsoft/retina/pkg/plugin/registry" + "github.com/microsoft/retina/pkg/utils" + "github.com/sirupsen/logrus" + "go.uber.org/zap" ) const ( - // name of the ebpfwindows plugin - name string = "windowseBPF" - // name of the metrics - packetsReceived string = "win_packets_recv_count" - packetsSent string = "win_packets_sent_count" - bytesSent string = "win_bytes_sent_count" - bytesReceived string = "win_bytes_recv_count" - droppedPacketsIncoming string = "win_packets_recv_drop_count" - droppedPacketsOutgoing string = "win_packets_sent_drop_count" - // metrics direction - ingressLabel = "ingress" - egressLabel = "egress" + // name of the ebpfwindows plugin + name string = "windowseBPF" + // name of the metrics + packetsReceived string = "win_packets_recv_count" + packetsSent string = "win_packets_sent_count" + bytesSent string = "win_bytes_sent_count" + bytesReceived string = "win_bytes_recv_count" + droppedPacketsIncoming string = "win_packets_recv_drop_count" + droppedPacketsOutgoing string = "win_packets_sent_drop_count" + // metrics direction + ingressLabel = "ingress" + egressLabel = "egress" ) var ( - ErrInvalidEventData = errors.New("The Cilium Event Data is invalid") - ErrNilEnricher = errors.New("enricher is nil") + ErrInvalidEventData = errors.New("The Cilium Event Data is invalid") + ErrNilEnricher = errors.New("enricher is nil") ) // Plugin is the ebpfwindows plugin type Plugin struct { - l *log.ZapLogger - cfg *kcfg.Config - enricher *enricher.Enricher - externalChannel chan *v1.Event - parser *hp.Parser + l *log.ZapLogger + cfg *kcfg.Config + enricher *enricher.Enricher + externalChannel chan *v1.Event + parser *hp.Parser } func init() { - registry.Add(name, New) + registry.Add(name, New) } func New(cfg *kcfg.Config) registry.Plugin { - return &Plugin{ - l: log.Logger().Named(name), - cfg: cfg, - } + return &Plugin{ + l: log.Logger().Named(name), + cfg: cfg, + } } // Init is a no-op for the ebpfwindows plugin func (p *Plugin) Init() error { - parser, err := hp.New(logrus.WithField("cilium", "parser"), - // We use noop getters here since we will use our own custom parser in hubble - &NoopEndpointGetter, - &NoopIdentityGetter, - &NoopDNSGetter, - &NoopIPGetter, - &NoopServiceGetter, - &NoopLinkGetter, - &NoopPodMetadataGetter, - ) - - if err != nil { - p.l.Fatal("Failed to create parser", zap.Error(err)) - return err - } - - p.parser = parser - return nil + parser, err := hp.New(logrus.WithField("cilium", "parser"), + // We use noop getters here since we will use our own custom parser in hubble + &NoopEndpointGetter, + &NoopIdentityGetter, + &NoopDNSGetter, + &NoopIPGetter, + &NoopServiceGetter, + &NoopLinkGetter, + &NoopPodMetadataGetter, + ) + + if err != nil { + p.l.Fatal("Failed to create parser", zap.Error(err)) + return err + } + + p.parser = parser + return nil } // Name returns the name of the ebpfwindows plugin func (p *Plugin) Name() string { - return name + 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.enricher = enricher.Instance() + p.l.Info("Start ebpfWindows plugin...") + p.enricher = enricher.Instance() - if p.enricher == nil { - return ErrNilEnricher - } + if p.enricher == nil { + return ErrNilEnricher + } - p.pullCiliumMetricsAndEvents(ctx) - return nil + 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())) - - if key.IsDrop() { - if key.IsEgress() { - metrics.DropPacketsGauge.WithLabelValues(egressLabel).Set(float64(value.Count())) - } else if key.IsIngress() { - metrics.DropPacketsGauge.WithLabelValues(ingressLabel).Set(float64(value.Count())) - } - - } else { - - if key.IsEgress() { - metrics.ForwardBytesGauge.WithLabelValues(egressLabel).Set(float64(value.Bytes())) - p.l.Debug("emitting bytes sent count metric", zap.Uint64(bytesSent, value.Bytes())) - metrics.WindowsGauge.WithLabelValues(packetsSent).Set(float64(value.Count())) - p.l.Debug("emitting packets sent count metric", zap.Uint64(packetsSent, value.Count())) - } else if key.IsIngress() { - metrics.ForwardPacketsGauge.WithLabelValues(ingressLabel).Set(float64(value.Count())) - p.l.Debug("emitting packets received count metric", zap.Uint64(packetsReceived, value.Count())) - metrics.ForwardBytesGauge.WithLabelValues(ingressLabel).Set(float64(value.Bytes())) - p.l.Debug("emitting bytes received count metric", zap.Uint64(bytesReceived, value.Bytes())) - } - } + p.l.Info("MetricsMapIterateCallback") + p.l.Info("Key", zap.String("Key", key.String())) + p.l.Info("Value", zap.String("Value", value.String())) + + if key.IsDrop() { + if key.IsEgress() { + metrics.DropPacketsGauge.WithLabelValues(egressLabel).Set(float64(value.Count())) + } else if key.IsIngress() { + metrics.DropPacketsGauge.WithLabelValues(ingressLabel).Set(float64(value.Count())) + } + + } else { + + if key.IsEgress() { + metrics.ForwardBytesGauge.WithLabelValues(egressLabel).Set(float64(value.Bytes())) + p.l.Debug("emitting bytes sent count metric", zap.Uint64(bytesSent, value.Bytes())) + metrics.WindowsGauge.WithLabelValues(packetsSent).Set(float64(value.Count())) + p.l.Debug("emitting packets sent count metric", zap.Uint64(packetsSent, value.Count())) + } else if key.IsIngress() { + metrics.ForwardPacketsGauge.WithLabelValues(ingressLabel).Set(float64(value.Count())) + p.l.Debug("emitting packets received count metric", zap.Uint64(packetsReceived, value.Count())) + metrics.ForwardBytesGauge.WithLabelValues(ingressLabel).Set(float64(value.Bytes())) + p.l.Debug("emitting bytes received count metric", zap.Uint64(bytesReceived, value.Bytes())) + } + } } // 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) + 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 - } + if err != nil { + p.l.Error("Error handling trace event", zap.Error(err)) + return -1 + } - return 0 + 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() - return - } - } + 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())) + err := eventsMap.UnregisterForCallback() + + if err != nil { + p.l.Error("Error Unregistering Events Map callback", zap.Error(err)) + } + return + } + } } // SetupChannel saves the external channel to which the plugin will send events. func (p *Plugin) SetupChannel(ch chan *v1.Event) error { - p.externalChannel = ch - return nil + p.externalChannel = ch + return nil } // Stop the plugin by cancelling the periodic timer. func (p *Plugin) Stop() error { - p.l.Info("Stop ebpfWindows plugin...") - return nil + 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 + return nil } // Generate is a no-op for the ebpfwindows plugin func (p *Plugin) Generate(context.Context) error { - return nil + return nil } func (p *Plugin) handleDropNotify(dropNotify *DropNotify) { - p.l.Info("DropNotify", zap.String("DropNotify", dropNotify.String())) + p.l.Info("DropNotify", zap.String("DropNotify", dropNotify.String())) } func (p *Plugin) handleTraceNotify(traceNotify *TraceNotify) { - p.l.Info("TraceNotify", zap.String("TraceNotify", traceNotify.String())) + p.l.Info("TraceNotify", zap.String("TraceNotify", traceNotify.String())) } func (p *Plugin) handleTraceSockNotify(traceSockNotify *TraceSockNotify) { - p.l.Info("TraceSockNotify", zap.String("TraceSockNotify", traceSockNotify.String())) + 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)) - } - - t1 := time.Now().UnixNano() - - // Hardcoded values for flow object. These values will be replaced by the actual values from the event. - fl := utils.ToFlow( - p.l, - t1, - net.ParseIP("192.168.0.1").To4(), // Src IP - net.ParseIP("192.168.0.2").To4(), // Dst IP - 80, // Src Port - 1024, // Dst Port - 6, // Protocol - 2, - flow.Verdict_DROPPED, - ) - - if fl == nil { - p.l.Warn("Could not convert event to flow", zap.Any("handleTraceEvent", data)) - return ErrInvalidEventData - } - - ev := &v1.Event{ - Event: fl, - Timestamp: fl.GetTime(), - } - - if p.enricher != nil { - p.enricher.Write(ev) - } else { - p.l.Error("enricher is nil when writing event") - } - - // Write the event to the external channel. - if p.externalChannel != nil { - select { - case p.externalChannel <- ev: - default: - // Channel is full, drop the event. - // We shouldn't slow down the reader. - metrics.LostEventsCounter.WithLabelValues(utils.ExternalChannel, name).Inc() - } - } - - return nil + 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)) + } + + t1 := time.Now().UnixNano() + + // Hardcoded values for flow object. These values will be replaced by the actual values from the event. + fl := utils.ToFlow( + p.l, + t1, + net.ParseIP("192.168.0.1").To4(), // Src IP + net.ParseIP("192.168.0.2").To4(), // Dst IP + 80, // Src Port + 1024, // Dst Port + 6, // Protocol + 2, + flow.Verdict_DROPPED, + ) + + if fl == nil { + p.l.Warn("Could not convert event to flow", zap.Any("handleTraceEvent", data)) + return ErrInvalidEventData + } + + ev := &v1.Event{ + Event: fl, + Timestamp: fl.GetTime(), + } + + if p.enricher != nil { + p.enricher.Write(ev) + } else { + p.l.Error("enricher is nil when writing event") + } + + // Write the event to the external channel. + if p.externalChannel != nil { + select { + case p.externalChannel <- ev: + default: + // Channel is full, drop the event. + // We shouldn't slow down the reader. + metrics.LostEventsCounter.WithLabelValues(utils.ExternalChannel, name).Inc() + } + } + + return nil } diff --git a/pkg/plugin/ebpfwindows/ebpf_windows_test.go b/pkg/plugin/ebpfwindows/ebpf_windows_test.go index 3563d0159d..4a31547329 100644 --- a/pkg/plugin/ebpfwindows/ebpf_windows_test.go +++ b/pkg/plugin/ebpfwindows/ebpf_windows_test.go @@ -5,77 +5,77 @@ package ebpfwindows import ( - "context" - "testing" - "time" + "context" + "testing" + "time" - kcfg "github.com/microsoft/retina/pkg/config" - "github.com/microsoft/retina/pkg/controllers/cache" - "github.com/microsoft/retina/pkg/enricher" - "github.com/microsoft/retina/pkg/log" - "github.com/microsoft/retina/pkg/metrics" - "github.com/microsoft/retina/pkg/pubsub" - "go.uber.org/zap" + kcfg "github.com/microsoft/retina/pkg/config" + "github.com/microsoft/retina/pkg/controllers/cache" + "github.com/microsoft/retina/pkg/enricher" + "github.com/microsoft/retina/pkg/log" + "github.com/microsoft/retina/pkg/metrics" + "github.com/microsoft/retina/pkg/pubsub" + "go.uber.org/zap" ) -func TestPlugin(t *testing.T) { - log.SetupZapLogger(log.GetDefaultLogOpts()) - l := log.Logger().Named("test-ebpf") +func TestPlugin(_ *testing.T) { + log.SetupZapLogger(log.GetDefaultLogOpts()) + l := log.Logger().Named("test-ebpf") - ctx := context.Background() + ctx := context.Background() - cfg := &kcfg.Config{ - MetricsInterval: 1 * time.Second, - EnablePodLevel: true, - } + cfg := &kcfg.Config{ + MetricsInterval: 1 * time.Second, + EnablePodLevel: true, + } - c := cache.New(pubsub.New()) - e := enricher.New(ctx, c) - e.Run() - defer e.Reader.Close() - metrics.InitializeMetrics() + c := cache.New(pubsub.New()) + e := enricher.New(ctx, c) + e.Run() + defer e.Reader.Close() + metrics.InitializeMetrics() - tt := New(cfg) + tt := New(cfg) - err := tt.Stop() - if err != nil { - l.Error("Failed to stop windows ebpf plugin", zap.Error(err)) - return - } + 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 - } + 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.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.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") + 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)) - } - }() + defer func() { + if err := tt.Stop(); err != nil { + l.Error("Failed to stop windows ebpf plugin", zap.Error(err)) + } + }() - for range ctx.Done() { - } + for range ctx.Done() { + } } diff --git a/pkg/plugin/ebpfwindows/eventsmap_types_windows.go b/pkg/plugin/ebpfwindows/eventsmap_types_windows.go index 6a9fcdeff0..dffe70a7d5 100644 --- a/pkg/plugin/ebpfwindows/eventsmap_types_windows.go +++ b/pkg/plugin/ebpfwindows/eventsmap_types_windows.go @@ -24,7 +24,7 @@ type TraceSockNotify struct { SockCookie uint64 CgroupID uint64 L4Proto uint8 - IPv6 bool + Ipv6 bool } // NotifyCommonHdr is the common header for all notifications @@ -62,7 +62,7 @@ type TraceNotify struct { DstLabel uint32 DstID uint16 Reason uint8 - IPv6 bool + Ipv6 bool Ifindex uint32 OrigIP IP } @@ -79,7 +79,7 @@ const ( CiliumNotifyTraceSock = 7 ) -func (ip *IP) ConvertToString(IPv6 bool) string { +func (ip *IP) ConvertToString(Ipv6 bool) string { var ipAddress string var buf bytes.Buffer @@ -91,7 +91,7 @@ func (ip *IP) ConvertToString(IPv6 bool) string { byteArray := buf.Bytes() - if IPv6 { + if Ipv6 { ipAddress = net.IP(byteArray[:16]).String() } else { ipAddress = net.IP(byteArray[:4]).String() @@ -109,12 +109,12 @@ func (k *DropNotify) String() string { // String returns a string representation of the TraceNotify func (k *TraceNotify) String() string { - ipAddress := k.OrigIP.ConvertToString(k.IPv6) - return fmt.Sprintf("Ifindex: %d, SrcLabel:%d, DstLabel:%d, IpV6:%t, OrigIP:%s", k.Ifindex, k.SrcLabel, k.DstLabel, k.IPv6, ipAddress) + ipAddress := k.OrigIP.ConvertToString(k.Ipv6) + return fmt.Sprintf("Ifindex: %d, SrcLabel:%d, DstLabel:%d, IpV6:%t, OrigIP:%s", k.Ifindex, k.SrcLabel, k.DstLabel, k.Ipv6, ipAddress) } // String returns a string representation of the TraceSockNotify func (k *TraceSockNotify) String() string { - ipAddress := k.DstIP.ConvertToString(k.IPv6) - return fmt.Sprintf("DstIP:%s, DstPort:%d, SockCookie:%d, CgroupID:%d, L4Proto:%d, IPv6:%t", ipAddress, k.DstPort, k.SockCookie, k.CgroupID, k.L4Proto, k.IPv6) + ipAddress := k.DstIP.ConvertToString(k.Ipv6) + return fmt.Sprintf("DstIP:%s, DstPort:%d, SockCookie:%d, CgroupID:%d, L4Proto:%d, IPv6:%t", ipAddress, k.DstPort, k.SockCookie, k.CgroupID, k.L4Proto, k.Ipv6) } diff --git a/pkg/plugin/ebpfwindows/eventsmap_windows.go b/pkg/plugin/ebpfwindows/eventsmap_windows.go index 0e5d1ad4ea..b4f425f151 100644 --- a/pkg/plugin/ebpfwindows/eventsmap_windows.go +++ b/pkg/plugin/ebpfwindows/eventsmap_windows.go @@ -1,75 +1,75 @@ package ebpfwindows import ( - "syscall" - "unsafe" + "syscall" + "unsafe" ) var ( - registerEventsMapCallback = retinaEbpfApi.NewProc("register_cilium_eventsmap_callback") - unregisterEventsMapCallback = retinaEbpfApi.NewProc("unregister_cilium_eventsmap_callback") + registerEventsMapCallback = retinaEbpfAPI.NewProc("register_cilium_eventsmap_callback") + unregisterEventsMapCallback = retinaEbpfAPI.NewProc("unregister_cilium_eventsmap_callback") ) type eventsMapCallback func(data unsafe.Pointer, size uint64) int // Callbacks in Go can only be passed as functions with specific signatures and often need to be wrapped in a syscall-compatible function. -var eventsCallback eventsMapCallback = nil +var eventsCallback eventsMapCallback // This function will be passed to the Windows API func eventsMapSysCallCallback(data unsafe.Pointer, size uint64) uintptr { - if eventsCallback != nil { - return uintptr(eventsCallback(data, size)) - } + if eventsCallback != nil { + return uintptr(eventsCallback(data, size)) + } - return 0 + return 0 } // EventsMap interface represents a events map type EventsMap interface { - RegisterForCallback(eventsMapCallback) error - UnregisterForCallback() error + RegisterForCallback(eventsMapCallback) error + UnregisterForCallback() error } type eventsMap struct { - ringBuffer uintptr + ringBuffer uintptr } // NewEventsMap creates a new metrics map func NewEventsMap() EventsMap { - return &eventsMap{ringBuffer: 0} + return &eventsMap{ringBuffer: 0} } // RegisterForCallback registers a callback function to be called when a new event is added to the events map func (e *eventsMap) RegisterForCallback(cb eventsMapCallback) error { - eventsCallback = cb + eventsCallback = cb - // Convert the Go function into a syscall-compatible function - callback := syscall.NewCallback(eventsMapSysCallCallback) + // Convert the Go function into a syscall-compatible function + callback := syscall.NewCallback(eventsMapSysCallCallback) - // Call the API - ret, _, err := registerEventsMapCallback.Call( - uintptr(callback), - uintptr(unsafe.Pointer(&e.ringBuffer)), - ) + // Call the API + ret, _, err := registerEventsMapCallback.Call( + uintptr(callback), + uintptr(unsafe.Pointer(&e.ringBuffer)), + ) - if ret != 0 { - return err - } + if ret != 0 { + return err + } - return nil + return nil } // UnregisterForCallback unregisters the callback function func (e *eventsMap) UnregisterForCallback() error { - // Call the API - ret, _, err := unregisterEventsMapCallback.Call(e.ringBuffer) + // Call the API + ret, _, err := unregisterEventsMapCallback.Call(e.ringBuffer) - if ret != 0 { - return err - } + if ret != 0 { + return err + } - return nil + return nil } diff --git a/pkg/plugin/ebpfwindows/metricsmap_windows.go b/pkg/plugin/ebpfwindows/metricsmap_windows.go index 157bcc94d8..0e664b1911 100644 --- a/pkg/plugin/ebpfwindows/metricsmap_windows.go +++ b/pkg/plugin/ebpfwindows/metricsmap_windows.go @@ -63,16 +63,16 @@ type metricsMap struct { var ( // Load the retinaebpfapi.dll - retinaEbpfApi = windows.NewLazyDLL("retinaebpfapi.dll") + retinaEbpfAPI = windows.NewLazyDLL("retinaebpfapi.dll") // Load the enumerate_cilium_metricsmap function - enumMetricsMap = retinaEbpfApi.NewProc("enumerate_cilium_metricsmap") + enumMetricsMap = retinaEbpfAPI.NewProc("enumerate_cilium_metricsmap") ) // ringBufferEventCallback type definition in Go type enumMetricsCallback = func(key, value unsafe.Pointer, valueSize int) int // Callbacks in Go can only be passed as functions with specific signatures and often need to be wrapped in a syscall-compatible function. -var enumCallBack enumMetricsCallback = nil +var enumCallBack enumMetricsCallback // This function will be passed to the Windows API func enumMetricsSysCallCallback(key, value unsafe.Pointer, valueSize int) uintptr {