diff --git a/pkg/plugin/ebpfwindows/dropreasons_windows.go b/pkg/plugin/ebpfwindows/dropreasons_windows.go
new file mode 100644
index 0000000000..9dae9a36be
--- /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 dropErrors = map[uint8]string{
+ 0: "Success",
+ 2: "Invalid packet",
+ 3: "Plain Text",
+ 4: "Interface Decrypted",
+ 5: "LB: No backend slot entry found",
+ 6: "LB: No backend entry found",
+ 7: "LB: Reverse entry update failed",
+ 8: "LB: Reverse entry stale",
+ 9: "Fragmented packet",
+ 10: "Fragmented packet entry update failed",
+ 11: "Missed tail call to custom program",
+}
+
+// Keep in sync with __id_for_file in bpf/lib/source_info.h.
+var files = map[uint8]string{
+
+ // source files from bpf/
+ 1: "bpf_host.c",
+ 2: "bpf_lxc.c",
+ 3: "bpf_overlay.c",
+ 4: "bpf_xdp.c",
+ 5: "bpf_sock.c",
+ 6: "bpf_network.c",
+
+ // header files from bpf/lib/
+ 101: "arp.h",
+ 102: "drop.h",
+ 103: "srv6.h",
+ 104: "icmp6.h",
+ 105: "nodeport.h",
+ 106: "lb.h",
+ 107: "mcast.h",
+ 108: "ipv4.h",
+ 109: "conntrack.h",
+ 110: "l3.h",
+ 111: "trace.h",
+ 112: "encap.h",
+ 113: "encrypt.h",
+}
+
+// BPFFileName returns the file name for the given BPF file id.
+func BPFFileName(id uint8) string {
+ if name, ok := files[id]; ok {
+ return name
+ }
+ return fmt.Sprintf("unknown(%d)", id)
+}
+
+func extendedReason(extError int8) string {
+ if extError == int8(0) {
+ return ""
+ }
+ return fmt.Sprintf("%d", extError)
+}
+
+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)
+}
+
+// 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..b3798e7ddb
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/ebpf_windows.go
@@ -0,0 +1,240 @@
+package ebpfwindows
+
+import (
+ "context"
+ "errors"
+ "time"
+
+ //"fmt"
+ "unsafe"
+
+ v1 "github.com/cilium/cilium/pkg/hubble/api/v1"
+ observer "github.com/cilium/cilium/pkg/hubble/observer/types"
+ hp "github.com/cilium/cilium/pkg/hubble/parser"
+ kcfg "github.com/microsoft/retina/pkg/config"
+
+ //"github.com/google/uuid"
+ "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"
+)
+
+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
+ enricher *enricher.Enricher
+ externalChannel chan *v1.Event
+ parser *hp.Parser
+}
+
+func init() {
+ registry.Add(name, New)
+}
+
+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 {
+ 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
+}
+
+// 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.enricher = enricher.Instance()
+ p.pullCiliumMetricsAndEvents(ctx)
+ p.l.Info("Complete ebpfWindows plugin...")
+ 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.Debug("MetricsMapIterateCallback")
+ p.l.Debug("Key", zap.String("Key", key.String()))
+ p.l.Debug("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.Debug("EventsMapCallback")
+ p.l.Debug("Size", zap.Uint64("Size", size))
+ err := p.handleTraceEvent(data, size)
+ if err != nil {
+ p.l.Error("Error handling trace event", zap.Error(err))
+ return -1
+ }
+ return 0
+}
+
+// pullCiliumeBPFMetrics is the function that is called periodically by the timer.
+func (p *Plugin) pullCiliumMetricsAndEvents(ctx context.Context) {
+ eventsMap := NewEventsMap()
+ metricsMap := NewMetricsMap()
+ err := eventsMap.RegisterForCallback(p.eventsMapCallback)
+ if err != nil {
+ p.l.Error("Error registering for events map callback", zap.Error(err))
+ return
+ }
+ ticker := time.NewTicker(p.cfg.MetricsInterval)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ err := metricsMap.IterateWithCallback(p.metricsMapIterateCallback)
+ if err != nil {
+ p.l.Error("Error iterating metrics map", zap.Error(err))
+ }
+ case <-ctx.Done():
+ p.l.Error("ebpfwindows plugin canceling", zap.Error(ctx.Err()))
+ eventsMap.UnregisterForCallback()
+ 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
+}
+
+// Stop the plugin by cancelling the periodic timer.
+func (p *Plugin) Stop() error {
+ p.l.Info("Stop ebpfWindows plugin...")
+ return nil
+}
+
+// Compile is a no-op for the ebpfwindows plugin
+func (p *Plugin) Compile(context.Context) error {
+ return nil
+}
+
+// Generate is a no-op for the ebpfwindows plugin
+func (p *Plugin) Generate(context.Context) error {
+ return nil
+}
+
+func (p *Plugin) 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
+ }
+ e, err := p.parser.Decode(&observer.MonitorEvent{
+ Payload: &observer.PerfEvent{
+ Data: (*[unsafe.Sizeof(DropNotify{})]byte)(data)[:],
+ },
+ })
+ if err != nil {
+ p.l.Error("Could not convert event to flow", zap.Any("handleTraceEvent", data), zap.Error(err))
+ return ErrInvalidEventData
+ }
+ p.enricher.Write(e)
+ case CiliumNotifyTrace:
+ if uintptr(size) < unsafe.Sizeof(TraceNotify{}) {
+ p.l.Error("Invalid TraceNotify data size", zap.Uint64("size", size))
+ return ErrInvalidEventData
+ }
+ e, err := p.parser.Decode(&observer.MonitorEvent{
+ Payload: &observer.PerfEvent{
+ Data: (*[unsafe.Sizeof(TraceNotify{})]byte)(data)[:],
+ },
+ })
+ if err != nil {
+ p.l.Error("Could not convert event to flow", zap.Any("handleTraceEvent", data), zap.Error(err))
+ return ErrInvalidEventData
+ }
+
+ p.enricher.Write(e)
+ case CiliumNotifyTraceSock:
+ if uintptr(size) < unsafe.Sizeof(TraceSockNotify{}) {
+ p.l.Error("Invalid TraceSockNotify data size", zap.Uint64("size", size))
+ return ErrInvalidEventData
+ }
+ e, err := p.parser.Decode(&observer.MonitorEvent{
+ Payload: &observer.PerfEvent{
+ Data: (*[unsafe.Sizeof(TraceSockNotify{})]byte)(data)[:],
+ },
+ })
+ if err != nil {
+ p.l.Error("Could not convert event to flow", zap.Any("handleTraceEvent", data), zap.Error(err))
+ return ErrInvalidEventData
+ }
+ p.enricher.Write(e)
+ }
+ 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..6e596b6374
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/ebpf_windows_test.go
@@ -0,0 +1,347 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+// nolint
+
+package ebpfwindows
+
+import (
+ "context"
+ "encoding/binary"
+ "fmt"
+ "net"
+ "os/exec"
+ "testing"
+ "time"
+ "unsafe"
+
+ "github.com/cilium/cilium/api/v1/flow"
+ v1 "github.com/cilium/cilium/pkg/hubble/api/v1"
+ 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"
+ "golang.org/x/sys/windows"
+)
+
+type FiveTuple struct {
+ Proto uint8
+ SrcIP uint32
+ DstIP uint32
+ SrcPort uint16
+ DstPort uint16
+}
+
+type Filter struct {
+ Event uint8
+ SrcIP uint32
+ DstIP uint32
+ SrcPort uint16
+ DstPort uint16
+}
+
+var (
+ Event_WriterDLL = windows.NewLazyDLL("event_writer.dll")
+)
+
+func ParseIPToUInt(ipStr string) (uint32, error) {
+ ip := net.ParseIP(ipStr)
+ if ip == nil {
+ return 0, fmt.Errorf("ParseIPToUInt - invalid IP address")
+ }
+
+ ip = ip.To4()
+ if ip == nil {
+ return 0, fmt.Errorf("ParseIPToUInt- invalid IPV4 address")
+ }
+ return binary.BigEndian.Uint32(ip), nil
+}
+
+func GetRingData(l *log.ZapLogger, e *enricher.Enricher, ctx *context.Context, eventChannel chan int) {
+ evReader := e.ExportReader()
+ timeout := 180 * time.Second
+ timeoutChan := time.After(timeout)
+ getData := make(chan *v1.Event)
+ check_five_tuple_exists := Event_WriterDLL.NewProc("check_five_tuple_exists")
+
+ go func() {
+ ev := evReader.NextFollow(*ctx)
+ getData <- ev
+ }()
+
+ defer func() {
+ err := evReader.Close()
+ if err != nil {
+ l.Error("Error closing the event reader", zap.Error(err))
+ }
+ l.Info("Enricher reader closed")
+ }()
+
+ select {
+ case <-timeoutChan:
+ l.Info("Timeout reached")
+ eventChannel <- 1
+ return
+ case ev := <-getData:
+ if ev == nil {
+ l.Info("No more events, breaking loop")
+ eventChannel <- 1
+ return
+ }
+
+ switch ev.Event.(type) {
+ case *flow.Flow:
+ if flow := ev.GetFlow(); flow != nil {
+ if ip := flow.GetIP(); ip != nil {
+ if l4 := flow.GetL4(); l4 != nil {
+ srcIP := ip.Source
+ dstIP := ip.Destination
+ srcIPU32, err := ParseIPToUInt(srcIP)
+ if err != nil {
+ l.Error("Error", zap.Error(err), zap.String("IP", srcIP))
+ eventChannel <- 1
+ return
+ }
+ dstIPU32, err := ParseIPToUInt(dstIP)
+ if err != nil {
+ l.Error("Error", zap.Error(err), zap.String("IP", dstIP))
+ eventChannel <- 1
+ return
+ }
+ if tcp := l4.GetTCP(); tcp != nil {
+ srcPrt := uint16(tcp.GetSourcePort())
+ dstPrt := uint16(tcp.GetDestinationPort())
+
+ l.Info("TCP",
+ zap.String("FlowType", flow.GetType().String()),
+ zap.String("srcIP", srcIP),
+ zap.String("dstIP", dstIP),
+ zap.Uint16("srcP", srcPrt),
+ zap.Uint16("dstP", dstPrt),
+ )
+
+ fvt := &FiveTuple{
+ Proto: 6,
+ SrcIP: srcIPU32,
+ DstIP: dstIPU32,
+ SrcPort: srcPrt,
+ DstPort: dstPrt,
+ }
+
+ ret, _, _ := check_five_tuple_exists.Call(uintptr(unsafe.Pointer(fvt)))
+ if ret == 0 {
+ l.Info("Match found!")
+ eventChannel <- 0
+ return
+ }
+ }
+
+ if udp := l4.GetUDP(); udp != nil {
+ srcPrt := uint16(udp.GetSourcePort())
+ dstPrt := uint16(udp.GetDestinationPort())
+
+ l.Info("UDP",
+ zap.String("FlowType", flow.GetType().String()),
+ zap.String("srcIP", srcIP),
+ zap.String("dstIP", dstIP),
+ zap.Uint16("srcP", srcPrt),
+ zap.Uint16("dstP", dstPrt),
+ )
+
+ fvt := &FiveTuple{
+ Proto: 17,
+ SrcIP: srcIPU32,
+ DstIP: dstIPU32,
+ SrcPort: srcPrt,
+ DstPort: dstPrt,
+ }
+ ret, _, _ := check_five_tuple_exists.Call(uintptr(unsafe.Pointer(fvt)))
+ if ret == 0 {
+ l.Info("Match found!")
+ eventChannel <- 0
+ return
+ }
+ }
+ }
+ }
+ }
+ default:
+ l.Info("Unknown event type", zap.Any("event", ev))
+ }
+ }
+
+ l.Error("Could not find expected flow object")
+ eventChannel <- 1
+}
+
+func GetAllInterfaces() ([]int, error) {
+ interfaces, err := net.Interfaces()
+ var ifaceList []int
+ if err != nil {
+ return nil, err
+ }
+
+ for _, iface := range interfaces {
+ ifaceList = append(ifaceList, iface.Index)
+ }
+
+ return ifaceList, nil
+}
+
+func SetupEventWriter(l *log.ZapLogger) (int, error) {
+ if Event_WriterDLL == nil {
+ return 1, fmt.Errorf("SetupEventWriter - cannot lookup Event_WriterDLL")
+ }
+
+ pin_maps_load_programs := Event_WriterDLL.NewProc("pin_maps_load_programs")
+ if ret, _, err := pin_maps_load_programs.Call(); ret != 0 {
+ return int(ret), fmt.Errorf("SetupEventWriter - %v", zap.Error(err))
+ }
+
+ attach_program_to_interface := Event_WriterDLL.NewProc("attach_program_to_interface")
+ int_attach_count := 0
+ if ifindexList, err := GetAllInterfaces(); err != nil {
+ return 1, fmt.Errorf("SetupEventWriter - no interfaces found")
+ } else {
+ for _, ifidx := range ifindexList {
+ //Continue when error
+ if ret, _, err := attach_program_to_interface.Call(uintptr(ifidx)); ret != 0 {
+ l.Error("SetupEventWriter - failed to attach event_writer", zap.Int("Interface", ifidx), zap.Error(err))
+ } else {
+ l.Info("Event_writer attached to interface", zap.Int("Ifindex", ifidx))
+ int_attach_count += 1
+ }
+ }
+ }
+
+ return 0, nil
+}
+
+func CloseEventWriter() (int, error) {
+ if Event_WriterDLL == nil {
+ return 1, fmt.Errorf("CloseEventWriter - cannot lookup Event_WriterDLL")
+ }
+
+ unload_programs_detach := Event_WriterDLL.NewProc("unload_programs_detach")
+ ret, _, err := unload_programs_detach.Call()
+ if ret != 0 {
+ return int(ret), fmt.Errorf("CloseEventWriter - %v", zap.Error(err))
+ }
+ return 0, nil
+}
+
+func Curl(url string) (int, error) {
+ cmd := exec.Command("curl", url)
+ _, err := cmd.Output()
+ if err != nil {
+ return 1, fmt.Errorf("Curl - %s", err)
+ }
+
+ return 0, nil
+}
+
+func TestMain(t *testing.T) {
+ log.SetupZapLogger(log.GetDefaultLogOpts())
+ l := log.Logger().Named("test-ebpf")
+ ctx := context.Background()
+
+ //Load and attach ebpf program
+ if ret, err := SetupEventWriter(l); ret != 0 {
+ l.Error("TestMain", zap.Error(err))
+ t.Fail()
+ return
+ }
+
+ defer func() {
+ ret, err := CloseEventWriter()
+ if ret != 0 {
+ l.Error("TestMain", zap.Error(err))
+ return
+ }
+ l.Info("Program successfully unloaded and detached")
+ }()
+
+ 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()
+
+ tt := New(cfg)
+ err := tt.Stop()
+ if err != nil {
+ l.Error("TestMain - failed to stop plugin", zap.Error(err))
+ return
+ }
+
+ ctxTimeout, cancel := context.WithTimeout(ctx, time.Second*10)
+ defer cancel()
+ err = tt.Generate(ctxTimeout)
+ if err != nil {
+ l.Error("TestMain - failed to generate plugin", zap.Error(err))
+ return
+ }
+
+ err = tt.Compile(ctxTimeout)
+ if err != nil {
+ l.Error("TestMain - failed to compile plugin", zap.Error(err))
+ return
+ }
+
+ err = tt.Init()
+ if err != nil {
+ l.Error("TestMain - failed to init plugin", zap.Error(err))
+ return
+ }
+
+ go tt.Start(ctx)
+ defer func() {
+ err = tt.Stop()
+ if err != nil {
+ l.Error("TestMain - failed to stop plugin", zap.Error(err))
+ }
+ }()
+
+ if ret, err := ValidateFlowObject(l, ctx, e, CiliumNotifyTrace); ret != 0 {
+ l.Error("TestTraceNotify", zap.Error(err))
+ t.Fail()
+ }
+ if ret, err := ValidateFlowObject(l, ctx, e, CiliumNotifyDrop); ret != 0 {
+ l.Error("TestDropNotify", zap.Error(err))
+ t.Fail()
+ }
+}
+
+func ValidateFlowObject(l *log.ZapLogger, ctx context.Context, e *enricher.Enricher, evt_type uint8) (int, error) {
+ eventChannel := make(chan int)
+ set_filter := Event_WriterDLL.NewProc("set_filter")
+ // Hardcoding IP addr for aka.ms - 23.213.38.151
+ flt := &Filter{
+ Event: evt_type,
+ SrcIP: 399845015,
+ DstIP: 0,
+ SrcPort: 0,
+ DstPort: 0,
+ }
+ ret, _, err := set_filter.Call(uintptr(unsafe.Pointer(flt)))
+ if ret != 0 {
+ return int(ret), fmt.Errorf("ValidateFlowObject - %v", zap.Error(err))
+ } else {
+ l.Debug("ValidateFlowObject", zap.String("Filter", "Updated successfully"))
+ }
+
+ go GetRingData(l, e, &ctx, eventChannel)
+ if ret, err := Curl("aka.ms"); ret != 0 {
+ return ret, fmt.Errorf("ValidateFlowObject - %v", zap.Error(err))
+ }
+ result := <-eventChannel
+ return result, nil
+}
diff --git a/pkg/plugin/ebpfwindows/event_writer/bpf_event_writer.c b/pkg/plugin/ebpfwindows/event_writer/bpf_event_writer.c
new file mode 100644
index 0000000000..ad00db02ff
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/event_writer/bpf_event_writer.c
@@ -0,0 +1,262 @@
+#include "bpf_helpers.h"
+#include "bpf_helper_defs.h"
+#include "bpf_endian.h"
+#include "xdp/ebpfhook.h"
+#include "event_writer.h"
+
+SEC (".maps")
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __type(key, struct five_tuple);
+ __type(value, uint8_t);
+ __uint(pinning, LIBBPF_PIN_BY_NAME);
+ __uint(max_entries, 512 * 4096);
+} five_tuple_map;
+
+SEC(".maps")
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __type(key, uint8_t);
+ __type(value, struct filter);
+ __uint(pinning, LIBBPF_PIN_BY_NAME);
+ __uint(max_entries, 1);
+} filter_map;
+
+SEC(".maps")
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __type(key, uint32_t);
+ __type(value, struct trace_notify);
+ __uint(max_entries, 1);
+} trc_buffer;
+
+SEC(".maps")
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
+ __type(key, uint32_t);
+ __type(value, struct drop_notify);
+ __uint(max_entries, 1);
+} drp_buffer;
+
+SEC(".maps")
+struct {
+ __uint(type, BPF_MAP_TYPE_RINGBUF);
+ __uint(pinning, LIBBPF_PIN_BY_NAME);
+ __uint(max_entries, 512 * 4096);
+} cilium_events;
+
+SEC(".maps")
+struct {
+ __uint(type, BPF_MAP_TYPE_PERCPU_HASH);
+ __type(key, struct metrics_key);
+ __type(value, struct metrics_value);
+ __uint(pinning, LIBBPF_PIN_BY_NAME);
+ __uint(max_entries, 512 * 4096);
+} cilium_metrics;
+
+void update_metrics(uint64_t bytes, uint8_t direction,
+ uint8_t reason, uint16_t line, uint8_t file)
+{
+ struct metrics_value *entry, new_entry = {};
+ struct metrics_key key = {};
+
+ key.reason = reason;
+ key.dir = direction;
+ key.line = line;
+ key.file = file;
+
+ entry = bpf_map_lookup_elem(&cilium_metrics, &key);
+ if (entry) {
+ entry->count += 1;
+ entry->bytes += bytes;
+ } else {
+ new_entry.count = 1;
+ new_entry.bytes = bytes;
+ bpf_map_update_elem(&cilium_metrics, &key, &new_entry, 0);
+ }
+}
+
+void create_trace_ntfy_event(struct trace_notify* trc_elm)
+{
+ memset(trc_elm, 0, sizeof(struct trace_notify));
+ trc_elm->type = CILIUM_NOTIFY_TRACE;
+ trc_elm->subtype = 0;
+ trc_elm->source = 0;
+ trc_elm->hash = 0;
+ trc_elm->len_orig = 128;
+ trc_elm->len_cap = 128;
+ trc_elm->version = 1;
+ trc_elm->src_label = 0;
+ trc_elm->dst_label = 0;
+ trc_elm->dst_id = 0;
+ trc_elm->reason = 0;
+ trc_elm->ifindex = 0;
+ trc_elm->ipv6 = 0;
+}
+
+void create_drop_event(struct drop_notify* drp_elm)
+{
+ memset(drp_elm, 0, sizeof(struct drop_notify));
+ drp_elm->type = CILIUM_NOTIFY_DROP;
+ drp_elm->subtype = 0;
+ drp_elm->source = 0;
+ drp_elm->hash = 0;
+ drp_elm->len_orig = 128;
+ drp_elm->len_cap = 128;
+ drp_elm->version = 1;
+ drp_elm->src_label = 0;
+ drp_elm->dst_label = 0;
+ drp_elm->dst_id = 0;
+ drp_elm->line = 0;
+ drp_elm->file = 0;
+ drp_elm->ext_error = 0;
+ drp_elm->ifindex = 0;
+}
+
+int extract_five_tuple_info(void* data, int bytes_to_copy, struct five_tuple* tup) {
+ struct ethhdr *eth;
+ uint8_t present = 1;
+
+ if (data == NULL || tup == NULL) {
+ return 1;
+ }
+
+ if (bytes_to_copy < sizeof(struct ethhdr)) {
+ return 1;
+ }
+
+ eth = data;
+ if (eth->ethertype != htons(0x0800)) {
+ return 1;
+ }
+
+ if (bytes_to_copy < sizeof(struct ethhdr) + sizeof(struct iphdr)) {
+ return 1;
+ }
+
+ struct iphdr *iph = data + sizeof(struct ethhdr);
+
+ // Only process TCP or UDP packets
+ if (iph->protocol != 6 && iph->protocol != 17) {
+ return 1;
+ }
+
+ tup->srcIP = htonl(iph->saddr);
+ tup->dstIP = htonl(iph->daddr);
+ tup->proto = iph->protocol;
+
+ if (tup->proto == 6) {
+ if (bytes_to_copy < sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct tcphdr)) {
+ return 1;
+ }
+
+ struct tcphdr *tcph = data + sizeof(struct ethhdr) + sizeof(struct iphdr);
+ tup->srcprt = htons(tcph->source);
+ tup->dstprt = htons(tcph->dest);
+ }
+ else if (tup->proto == 17) {
+ if (bytes_to_copy < sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr)) {
+ return 1;
+ }
+
+ struct udphdr *udph = data + sizeof(struct ethhdr) + sizeof(struct iphdr);
+ tup->srcprt = htons(udph->source);
+ tup->dstprt = htons(udph->dest);
+ }
+ return 0;
+}
+
+int
+check_filter(struct filter* flt, struct five_tuple* tup) {
+ if (flt == NULL || tup == NULL) {
+ return 1;
+ }
+
+ if (flt->srcIP != 0 && flt->srcIP != tup->srcIP) {
+ return 1;
+ }
+
+ if (flt->dstIP != 0 && flt->dstIP != tup->dstIP) {
+ return 1;
+ }
+
+ if (flt->srcprt != 0 && flt->srcprt != tup->srcprt) {
+ return 1;
+ }
+
+ if (flt->dstprt != 0 && flt->dstprt != tup->dstprt) {
+ return 1;
+ }
+
+ return 0;
+}
+
+SEC("xdp")
+int
+event_writer(xdp_md_t* ctx) {
+ uint8_t flt_key = 0;
+ uint32_t buf_key = 0;
+ struct filter* flt;
+ struct five_tuple tup;
+ uint32_t size_to_copy = 128;
+ uint8_t flt_evttype, present = 1;
+
+ if (ctx->data == NULL || ctx->data_end == NULL) {
+ return XDP_PASS;
+ }
+
+ update_metrics(10, METRIC_INGRESS, 0, 0, 0);
+ update_metrics(10, METRIC_EGRESS, 0, 0, 0);
+
+ if ((uintptr_t)ctx->data + size_to_copy > (uintptr_t)ctx->data_end) {
+ size_to_copy = (uintptr_t)ctx->data_end - (uintptr_t)ctx->data;
+ }
+
+ memset(&tup, 0, sizeof(tup));
+ if (extract_five_tuple_info(ctx->data, size_to_copy, &tup) != 0) {
+ return XDP_PASS;
+ }
+
+ flt = (struct filter*) bpf_map_lookup_elem(&filter_map, &flt_key);
+ if (flt == NULL) {
+ return XDP_PASS;
+ }
+
+ if (check_filter(flt, &tup) != 0) {
+ return XDP_PASS;
+ }
+
+ if (bpf_map_update_elem(&five_tuple_map, &tup, &present, BPF_ANY) != 0) {
+ return XDP_PASS;
+ }
+
+ flt_evttype = flt->event;
+ if (flt_evttype == CILIUM_NOTIFY_TRACE) {
+ struct trace_notify* trc_elm;
+
+ //Create a Mock Trace Event
+ trc_elm = (struct trace_notify *) bpf_map_lookup_elem(&trc_buffer, &buf_key);
+ if (trc_elm == NULL) {
+ return XDP_PASS;
+ }
+ create_trace_ntfy_event(trc_elm);
+ memset(trc_elm->data, 0, sizeof(trc_elm->data));
+ memcpy(trc_elm->data, ctx->data, size_to_copy);
+ bpf_ringbuf_output(&cilium_events, trc_elm, sizeof(struct trace_notify), 0);
+ }
+ if (flt_evttype == CILIUM_NOTIFY_DROP) {
+ struct drop_notify* drp_elm;
+
+ //Create a Mock Drop Event
+ drp_elm = (struct drop_notify *) bpf_map_lookup_elem(&drp_buffer, &buf_key);
+ if (drp_elm == NULL) {
+ return XDP_PASS;
+ }
+ create_drop_event(drp_elm);
+ memset(drp_elm->data, 0, sizeof(drp_elm->data));
+ memcpy(drp_elm->data, ctx->data, size_to_copy);
+ bpf_ringbuf_output(&cilium_events, drp_elm, sizeof(struct drop_notify), 0);
+ }
+
+ return XDP_PASS;
+}
\ No newline at end of file
diff --git a/pkg/plugin/ebpfwindows/event_writer/event_writer.h b/pkg/plugin/ebpfwindows/event_writer/event_writer.h
new file mode 100644
index 0000000000..30226d0e2d
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/event_writer/event_writer.h
@@ -0,0 +1,173 @@
+/* Copyright (c) Microsoft Corporation */
+/* SPDX-License-Identifier: MIT */
+
+#ifndef _EVENT_WRITER__
+#define _EVENT_WRITER__
+
+
+#define EVENTS_MAP_PIN_PATH \
+ "/ebpf/global/cilium_events"
+
+#define METRICS_MAP_PIN_PATH \
+ "/ebpf/global/cilium_metrics"
+
+#define FILTER_MAP_PIN_PATH \
+ "/ebpf/global/filter_map"
+
+#define FIVE_TUPLE_MAP_PIN_PATH \
+ "/ebpf/global/five_tuple_map"
+
+enum {
+ CILIUM_NOTIFY_UNSPEC = 0,
+ CILIUM_NOTIFY_DROP,
+ CILIUM_NOTIFY_DBG_MSG,
+ CILIUM_NOTIFY_DBG_CAPTURE,
+ CILIUM_NOTIFY_TRACE,
+ CILIUM_NOTIFY_POLICY_VERDICT,
+ CILIUM_NOTIFY_CAPTURE,
+ CILIUM_NOTIFY_TRACE_SOCK,
+};
+
+enum {
+ METRIC_INGRESS = 1,
+ METRIC_EGRESS,
+};
+
+struct ethhdr {
+ uint8_t dst_mac[6];
+ uint8_t src_mac[6];
+ uint16_t ethertype;
+};
+
+struct iphdr {
+ uint8_t ihl : 4,
+ version : 4;
+ uint8_t tos;
+ uint16_t tot_len;
+ uint16_t id;
+ uint16_t frag_off;
+ uint8_t ttl;
+ uint8_t protocol;
+ uint16_t check;
+ uint32_t saddr;
+ uint32_t daddr;
+};
+
+struct tcphdr {
+ uint16_t source; // Source port
+ uint16_t dest; // Destination port
+ uint32_t seq; // Sequence number
+ uint32_t ack_seq; // Acknowledgment number
+ uint8_t doff; // Data offset
+ uint8_t res1:4; // Reserved
+ uint8_t fin:1,
+ syn:1,
+ rst:1,
+ psh:1,
+ ack:1,
+ urg:1,
+ ece:1,
+ cwr:1,
+ ns:1;
+ uint16_t window; // Window size
+ uint16_t check; // Checksum
+ uint16_t urg_ptr; // Urgent pointer
+};
+
+struct udphdr {
+ uint16_t source; // Source port
+ uint16_t dest; // Destination port
+ uint16_t len; // Length of the UDP packet (header + data)
+ uint16_t check; // Checksum
+};
+
+union v6addr {
+ struct {
+ uint32_t p1;
+ uint32_t p2;
+ uint32_t p3;
+ uint32_t p4;
+ };
+ struct {
+ __u64 d1;
+ __u64 d2;
+ };
+ uint8_t addr[16];
+}__packed;
+
+struct five_tuple {
+ uint8_t proto;
+ uint32_t srcIP;
+ uint32_t dstIP;
+ uint16_t srcprt;
+ uint16_t dstprt;
+};
+
+struct filter {
+ uint8_t event;
+ uint32_t srcIP;
+ uint32_t dstIP;
+ uint16_t srcprt;
+ uint16_t dstprt;
+};
+
+struct trace_notify {
+ uint8_t type;
+ uint8_t subtype;
+ uint16_t source;
+ uint32_t hash;
+ uint32_t len_orig;
+ uint16_t len_cap;
+ uint16_t version;
+ uint32_t src_label;
+ uint32_t dst_label;
+ uint16_t dst_id;
+ uint8_t reason;
+ uint8_t ipv6:1;
+ uint8_t pad:7;
+ uint32_t ifindex;
+ union {
+ struct {
+ uint32_t orig_ip4;
+ uint32_t orig_pad1;
+ uint32_t orig_pad2;
+ uint32_t orig_pad3;
+ };
+ union v6addr orig_ip6;
+ };
+ uint8_t data[128];
+};
+
+struct drop_notify {
+ uint8_t type;
+ uint8_t subtype;
+ uint16_t source;
+ uint32_t hash;
+ uint32_t len_orig;
+ uint16_t len_cap;
+ uint16_t version;
+ uint32_t src_label;
+ uint32_t dst_label;
+ uint32_t dst_id; /* 0 for egress */
+ uint16_t line;
+ uint8_t file;
+ int8_t ext_error;
+ uint32_t ifindex;
+ uint8_t data[128];
+};
+
+struct metrics_key {
+ uint8_t reason; /* 0: forwarded, >0 dropped */
+ uint8_t dir:2, /* 1: ingress 2: egress */
+ pad:6;
+ uint16_t line; /* __MAGIC_LINE__ */
+ uint8_t file; /* __MAGIC_FILE__, needs to fit __source_file_name_to_id */
+ uint8_t reserved[3]; /* reserved for future extension */
+};
+
+struct metrics_value {
+ uint64_t count;
+ uint64_t bytes;
+};
+
+#endif /* _EVENT_WRITER__ */
\ No newline at end of file
diff --git a/pkg/plugin/ebpfwindows/event_writer/event_writer.sln b/pkg/plugin/ebpfwindows/event_writer/event_writer.sln
new file mode 100644
index 0000000000..d9405c5f7a
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/event_writer/event_writer.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.11.35431.28
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "event_writer", "event_writer.vcxproj", "{A12E2603-25A2-4A9C-9B9D-9156C9520789}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A12E2603-25A2-4A9C-9B9D-9156C9520789}.Debug|x64.ActiveCfg = Debug|x64
+ {A12E2603-25A2-4A9C-9B9D-9156C9520789}.Debug|x64.Build.0 = Debug|x64
+ {A12E2603-25A2-4A9C-9B9D-9156C9520789}.Debug|x86.ActiveCfg = Debug|Win32
+ {A12E2603-25A2-4A9C-9B9D-9156C9520789}.Debug|x86.Build.0 = Debug|Win32
+ {A12E2603-25A2-4A9C-9B9D-9156C9520789}.Release|x64.ActiveCfg = Release|x64
+ {A12E2603-25A2-4A9C-9B9D-9156C9520789}.Release|x64.Build.0 = Release|x64
+ {A12E2603-25A2-4A9C-9B9D-9156C9520789}.Release|x86.ActiveCfg = Release|Win32
+ {A12E2603-25A2-4A9C-9B9D-9156C9520789}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {1B17387E-FD19-4848-96B1-6A0F1322EE37}
+ EndGlobalSection
+EndGlobal
diff --git a/pkg/plugin/ebpfwindows/event_writer/event_writer.vcxproj b/pkg/plugin/ebpfwindows/event_writer/event_writer.vcxproj
new file mode 100644
index 0000000000..386c17b505
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/event_writer/event_writer.vcxproj
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+ 10.0.26100.2454
+
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 17.0
+ Win32Proj
+ {A12E2603-25A2-4A9C-9B9D-9156C9520789}
+ event_writer
+ 10.0.26100.0
+ $(SolutionDir)packages\eBPF-for-Windows.x64.0.20.0\build\native\
+ $(SolutionDir)packages\XDP-for-Windows.1.1.0\build\native\
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ event_writer
+
+
+ event_writer
+
+
+
+ Level3
+ true
+ _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ $(SolutionDir)packages\eBPF-for-Windows.x64.0.20.0\build\native\include;$(SolutionDir)packages\XDP-for-Windows.1.1.0\build\native\include;%(AdditionalIncludeDirectories)
+ stdcpp20
+ ProgramDatabase
+
+
+ Console
+ true
+ false
+ $(SolutionDir)packages\eBPF-for-Windows.x64.0.20.0\build\native\lib;$(SolutionDir)packages\XDP-for-Windows.1.1.0\build\native\lib;%(AdditionalLibraryDirectories)
+ ebpfapi.lib;%(AdditionalDependencies)
+
+
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ $(SolutionDir)packages\eBPF-for-Windows.x64.0.20.0\build\native\include;$(SolutionDir)packages\XDP-for-Windows.1.1.0\build\native\include;%(AdditionalIncludeDirectories)
+ stdcpp20
+
+
+ Console
+ true
+ true
+ true
+ false
+ $(SolutionDir)packages\eBPF-for-Windows.x64.0.20.0\build\native\lib;$(SolutionDir)packages\XDP-for-Windows.1.1.0\build\native\lib;%(AdditionalLibraryDirectories)
+ ebpfapi.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(Platform)\$(Configuration)\bpf_event_writer.sys
+
+ $(XdpPackagePath)bin\$(Platform)\xdpbpfexport.exe --clear
+ $(XdpPackagePath)bin\$(Platform)\xdpbpfexport.exe
+ clang -g -target bpf -O2 -Werror -I$(EbpfPackagePath)include -I$(XdpPackagePath)include -c bpf_event_writer.c -o $(Platform)\$(Configuration)\bpf_event_writer.o
+ pushd $(OutDir)
+ powershell -NonInteractive -ExecutionPolicy Unrestricted $(EbpfPackagePath)bin\Convert-BpfToNative.ps1 -FileName bpf_event_writer -IncludeDir $(EbpfPackagePath)include -Platform $(Platform) -Packages $(SolutionDir)packages -Configuration $(Configuration) -KernelMode $true
+ popd
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pkg/plugin/ebpfwindows/event_writer/event_writer.vcxproj.filters b/pkg/plugin/ebpfwindows/event_writer/event_writer.vcxproj.filters
new file mode 100644
index 0000000000..d6dfd78037
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/event_writer/event_writer.vcxproj.filters
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+ {75e6b9b2-c419-46ce-adb9-38f5a0454ea8}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pkg/plugin/ebpfwindows/event_writer/event_writer.vcxproj.user b/pkg/plugin/ebpfwindows/event_writer/event_writer.vcxproj.user
new file mode 100644
index 0000000000..88a550947e
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/event_writer/event_writer.vcxproj.user
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/pkg/plugin/ebpfwindows/event_writer/event_writer_api.cpp b/pkg/plugin/ebpfwindows/event_writer/event_writer_api.cpp
new file mode 100644
index 0000000000..303829c6d0
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/event_writer/event_writer_api.cpp
@@ -0,0 +1,155 @@
+#include
+#include
+#include
+#include
+#include "event_writer.h"
+#include "event_writer_util.h"
+
+std::vector> link_list;
+bpf_object* obj = NULL;
+
+extern "C" __declspec(dllexport) DWORD
+set_filter(struct filter* flt) {
+ uint8_t key = 0;
+ int map_flt_fd = 0;
+
+ // Attempt to open the pinned map
+ map_flt_fd = bpf_obj_get(FILTER_MAP_PIN_PATH);
+ if (map_flt_fd < 0) {
+ fprintf(stderr, "%s - failed to lookup filter_map\n", __FUNCTION__);
+ return 1;
+ }
+ if (bpf_map_update_elem(map_flt_fd, &key, flt, 0) != 0) {
+ fprintf(stderr, "%s - failed to update filter\n", __FUNCTION__);
+ return 1;
+ }
+ return 0;
+}
+
+extern "C" __declspec(dllexport) DWORD
+check_five_tuple_exists(struct five_tuple* fvt) {
+ int map_evt_req_fd;
+ int value = 0;
+
+ map_evt_req_fd = bpf_obj_get(FIVE_TUPLE_MAP_PIN_PATH);
+ if (map_evt_req_fd < 0) {
+ return 1;
+ }
+ if (bpf_map_lookup_elem(map_evt_req_fd, fvt, &value) != 0) {
+ return 1;
+ }
+
+ return 0;
+}
+
+extern "C" __declspec(dllexport) DWORD
+attach_program_to_interface(int ifindx) {
+ struct bpf_program* prg = bpf_object__find_program_by_name(obj, "event_writer");
+ bpf_link* link = NULL;
+ if (prg == NULL) {
+ fprintf(stderr, "%s - failed to find event_writer program", __FUNCTION__);
+ return 1;
+ }
+
+ link = bpf_program__attach_xdp(prg, ifindx);
+ if (link == NULL) {
+ fprintf(stderr, "%s - failed to attach to interface with ifindex %d\n", __FUNCTION__, ifindx);
+ return 1;
+ }
+
+ link_list.push_back(std::pair{ifindx, link});
+ return 0;
+}
+
+extern "C" __declspec(dllexport) DWORD
+pin_maps_load_programs(void) {
+ struct bpf_program* prg = NULL;
+ struct bpf_map *map_ev, *map_met, *map_fvt, *map_flt;
+ struct filter flt;
+ // Load the BPF object file
+ obj = bpf_object__open("bpf_event_writer.sys");
+ if (obj == NULL) {
+ fprintf(stderr, "%s - failed to open BPF sys file\n", __FUNCTION__);
+ return 1;
+ }
+
+ // Load cilium_events map and tcp_connect bpf program
+ if (bpf_object__load(obj) < 0) {
+ fprintf(stderr, "%s - failed to load BPF sys\n", __FUNCTION__);
+ bpf_object__close(obj);
+ return 1;
+ }
+
+ // Find the map by its name
+ map_ev = bpf_object__find_map_by_name(obj, "cilium_events");
+ if (map_ev == NULL) {
+ fprintf(stderr, "%s - failed to find cilium_events by name\n", __FUNCTION__);
+ bpf_object__close(obj);
+ return 1;
+ }
+ if (pin_map(EVENTS_MAP_PIN_PATH, map_ev) != 0) {
+ return 1;
+ }
+
+ // Find the map by its name
+ map_met = bpf_object__find_map_by_name(obj, "cilium_metrics");
+ if (map_met == NULL) {
+ fprintf(stderr, "%s - failed to find cilium_metrics by name\n", __FUNCTION__);
+ bpf_object__close(obj);
+ return 1;
+ }
+ if (pin_map(METRICS_MAP_PIN_PATH, map_ev) != 0) {
+ return 1;
+ }
+
+ // Find the map by its name
+ map_fvt = bpf_object__find_map_by_name(obj, "five_tuple_map");
+ if (map_fvt == NULL) {
+ fprintf(stderr, "%s - failed to find five_tuple_map by name\n", __FUNCTION__);
+ bpf_object__close(obj);
+ return 1;
+ }
+ if (pin_map(FIVE_TUPLE_MAP_PIN_PATH, map_fvt) != 0) {
+ return 1;
+ }
+
+ // Find the map by its name
+ map_flt = bpf_object__find_map_by_name(obj, "filter_map");
+ if (map_flt == NULL) {
+ fprintf(stderr, "%s - failed to lookup filter_map\n", __FUNCTION__);
+ return 1;
+ }
+ if (pin_map(FILTER_MAP_PIN_PATH, map_flt) != 0) {
+ return 1;
+ }
+
+ memset(&flt, 0, sizeof(flt));
+ flt.event = 4; // TRACE
+ if (set_filter(&flt) != 0) {
+ return 1;
+ }
+
+ return 0; // Return success
+}
+
+// Function to unload programs and detach
+extern "C" __declspec(dllexport) DWORD
+unload_programs_detach() {
+ for (auto it = link_list.begin(); it != link_list.end(); it ++) {
+ auto ifidx = it->first;
+ auto link = it->second;
+ auto link_fd = bpf_link__fd(link);
+ if (bpf_link_detach(link_fd) != 0) {
+ fprintf(stderr, "%s - failed to detach link %d\n", __FUNCTION__, ifidx);
+ }
+ if (bpf_link__destroy(link) != 0) {
+ fprintf(stderr, "%s - failed to destroy link %d", __FUNCTION__, ifidx);
+ }
+ }
+
+ if (obj != NULL) {
+ bpf_object__close(obj);
+ }
+
+ return 0;
+}
diff --git a/pkg/plugin/ebpfwindows/event_writer/event_writer_util.cpp b/pkg/plugin/ebpfwindows/event_writer/event_writer_util.cpp
new file mode 100644
index 0000000000..72a656efcb
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/event_writer/event_writer_util.cpp
@@ -0,0 +1,29 @@
+#include
+#include
+#include
+#include
+
+int pin_map(const char* pin_path, bpf_map* map) {
+ int map_fd = 0;
+ // Attempt to open the pinned map
+ map_fd = bpf_obj_get(pin_path);
+ if (map_fd < 0) {
+ // Get the file descriptor of the map
+ map_fd = bpf_map__fd(map);
+
+ if (map_fd < 0) {
+ fprintf(stderr, "%s - failed to get map file descriptor\n", __FUNCTION__);
+ return 1;
+ }
+
+ if (bpf_obj_pin(map_fd, pin_path) < 0) {
+ fprintf(stderr, "%s - failed to pin map to %s\n", pin_path, __FUNCTION__);
+ return 1;
+ }
+
+ printf("%s - map successfully pinned at %s\n", pin_path, __FUNCTION__);
+ } else {
+ printf("%s -pinned map found at %s\n", pin_path, __FUNCTION__);
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/pkg/plugin/ebpfwindows/event_writer/event_writer_util.h b/pkg/plugin/ebpfwindows/event_writer/event_writer_util.h
new file mode 100644
index 0000000000..973af0c5e3
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/event_writer/event_writer_util.h
@@ -0,0 +1,6 @@
+#include
+#include
+#include
+#include
+
+int pin_map(const char* pin_path, bpf_map* map);
\ No newline at end of file
diff --git a/pkg/plugin/ebpfwindows/eventsmap_types_windows.go b/pkg/plugin/ebpfwindows/eventsmap_types_windows.go
new file mode 100644
index 0000000000..6d2a5e5326
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/eventsmap_types_windows.go
@@ -0,0 +1,123 @@
+package ebpfwindows
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "net"
+)
+
+// IP represents an IPv4 or IPv4 or IPv6 address
+type IP struct {
+ Address uint32
+ Pad1 uint32
+ Pad2 uint32
+ Pad3 uint32
+}
+
+// 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
+ Data [128]byte
+}
+
+// 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
+ Data [128]byte
+}
+
+// TraceNotify is the notification for a packet trace
+type TraceNotify struct {
+ NotifyCaptureHdr
+ SrcLabel uint32
+ DstLabel uint32
+ DstID uint16
+ Reason uint8
+ IPv6 bool
+ Ifindex uint32
+ OrigIP IP
+ Data [128]byte
+}
+
+// Notification types
+const (
+ 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 {
+
+ 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 {
+ 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)
+}
diff --git a/pkg/plugin/ebpfwindows/eventsmap_windows.go b/pkg/plugin/ebpfwindows/eventsmap_windows.go
new file mode 100644
index 0000000000..0e5d1ad4ea
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/eventsmap_windows.go
@@ -0,0 +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 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 uint64) 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
new file mode 100644
index 0000000000..157bcc94d8
--- /dev/null
+++ b/pkg/plugin/ebpfwindows/metricsmap_windows.go
@@ -0,0 +1,190 @@
+package ebpfwindows
+
+import (
+ "fmt"
+ "reflect"
+ "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, 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, valueSize int) uintptr {
+
+ if enumCallBack != nil {
+ return uintptr(enumCallBack(key, value, valueSize))
+ }
+
+ 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, 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)
+ 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
+}
+
+// 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
+ }
+
+ 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())
+}