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()) +}