summaryrefslogtreecommitdiff
path: root/bpf
diff options
context:
space:
mode:
Diffstat (limited to 'bpf')
-rw-r--r--bpf/bpf_bpfel.go125
-rw-r--r--bpf/bpf_bpfel.obin0 -> 5312 bytes
-rw-r--r--bpf/bpf_usage.c73
-rw-r--r--bpf/gen.go3
-rw-r--r--bpf/main.go124
5 files changed, 325 insertions, 0 deletions
diff --git a/bpf/bpf_bpfel.go b/bpf/bpf_bpfel.go
new file mode 100644
index 0000000..f4ffb76
--- /dev/null
+++ b/bpf/bpf_bpfel.go
@@ -0,0 +1,125 @@
+// Code generated by bpf2go; DO NOT EDIT.
+//go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64
+
+package bpf
+
+import (
+ "bytes"
+ _ "embed"
+ "fmt"
+ "io"
+
+ "github.com/cilium/ebpf"
+)
+
+// loadBpf returns the embedded CollectionSpec for bpf.
+func loadBpf() (*ebpf.CollectionSpec, error) {
+ reader := bytes.NewReader(_BpfBytes)
+ spec, err := ebpf.LoadCollectionSpecFromReader(reader)
+ if err != nil {
+ return nil, fmt.Errorf("can't load bpf: %w", err)
+ }
+
+ return spec, err
+}
+
+// loadBpfObjects loads bpf and converts it into a struct.
+//
+// The following types are suitable as obj argument:
+//
+// *bpfObjects
+// *bpfPrograms
+// *bpfMaps
+//
+// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
+func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
+ spec, err := loadBpf()
+ if err != nil {
+ return err
+ }
+
+ return spec.LoadAndAssign(obj, opts)
+}
+
+// bpfSpecs contains maps and programs before they are loaded into the kernel.
+//
+// It can be passed ebpf.CollectionSpec.Assign.
+type bpfSpecs struct {
+ bpfProgramSpecs
+ bpfMapSpecs
+}
+
+// bpfSpecs contains programs before they are loaded into the kernel.
+//
+// It can be passed ebpf.CollectionSpec.Assign.
+type bpfProgramSpecs struct {
+ EgressFunc *ebpf.ProgramSpec `ebpf:"egress__func"`
+ IngressFunc *ebpf.ProgramSpec `ebpf:"ingress_func"`
+}
+
+// bpfMapSpecs contains maps before they are loaded into the kernel.
+//
+// It can be passed ebpf.CollectionSpec.Assign.
+type bpfMapSpecs struct {
+ EgressIp4UsageMap *ebpf.MapSpec `ebpf:"egress_ip4_usage_map"`
+ IngressIp4UsageMap *ebpf.MapSpec `ebpf:"ingress_ip4_usage_map"`
+}
+
+// bpfObjects contains all objects after they have been loaded into the kernel.
+//
+// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
+type bpfObjects struct {
+ bpfPrograms
+ bpfMaps
+}
+
+func (o *bpfObjects) Close() error {
+ return _BpfClose(
+ &o.bpfPrograms,
+ &o.bpfMaps,
+ )
+}
+
+// bpfMaps contains all maps after they have been loaded into the kernel.
+//
+// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
+type bpfMaps struct {
+ EgressIp4UsageMap *ebpf.Map `ebpf:"egress_ip4_usage_map"`
+ IngressIp4UsageMap *ebpf.Map `ebpf:"ingress_ip4_usage_map"`
+}
+
+func (m *bpfMaps) Close() error {
+ return _BpfClose(
+ m.EgressIp4UsageMap,
+ m.IngressIp4UsageMap,
+ )
+}
+
+// bpfPrograms contains all programs after they have been loaded into the kernel.
+//
+// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
+type bpfPrograms struct {
+ EgressFunc *ebpf.Program `ebpf:"egress__func"`
+ IngressFunc *ebpf.Program `ebpf:"ingress_func"`
+}
+
+func (p *bpfPrograms) Close() error {
+ return _BpfClose(
+ p.EgressFunc,
+ p.IngressFunc,
+ )
+}
+
+func _BpfClose(closers ...io.Closer) error {
+ for _, closer := range closers {
+ if err := closer.Close(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Do not access this directly.
+//
+//go:embed bpf_bpfel.o
+var _BpfBytes []byte
diff --git a/bpf/bpf_bpfel.o b/bpf/bpf_bpfel.o
new file mode 100644
index 0000000..789c00f
--- /dev/null
+++ b/bpf/bpf_bpfel.o
Binary files differ
diff --git a/bpf/bpf_usage.c b/bpf/bpf_usage.c
new file mode 100644
index 0000000..5e505da
--- /dev/null
+++ b/bpf/bpf_usage.c
@@ -0,0 +1,73 @@
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+
+#include <bpf/bpf_endian.h>
+#include <bpf/bpf_helpers.h>
+
+#define MAX_MAP_ENTRIES 4096
+
+char __license[] SEC("license") = "GPL";
+
+struct {
+ __uint(type, BPF_MAP_TYPE_LRU_HASH);
+ __uint(max_entries, MAX_MAP_ENTRIES);
+ __type(key, __u32); // source IPv4 address
+ __type(value, __u64); // no of bytes
+} ingress_ip4_usage_map SEC(".maps");
+
+struct {
+ __uint(type, BPF_MAP_TYPE_LRU_HASH);
+ __uint(max_entries, MAX_MAP_ENTRIES);
+ __type(key, __u32); // destination IPv4 address
+ __type(value, __u64); // no of bytes
+} egress_ip4_usage_map SEC(".maps");
+
+typedef enum {
+ UPDATE_USAGE_INGRESS,
+ UPDATE_USAGE_EGRESS,
+} update_usage_t;
+
+static __always_inline int update_usage(void *map, struct __sk_buff *skb,
+ update_usage_t traffic)
+{
+ __u32 ip4;
+ __u64 len, *usage;
+
+ void *data_end = (void *)(long)skb->data_end;
+ void *data = (void *)(long)skb->data;
+ struct iphdr *ip = data + sizeof(struct ethhdr);
+
+ if (skb->protocol != bpf_htons(ETH_P_IP))
+ return TCX_PASS;
+ else if ((void *)(ip + 1) > data_end)
+ return TCX_PASS;
+
+ if (traffic == UPDATE_USAGE_INGRESS)
+ ip4 = ip->saddr;
+ else
+ ip4 = ip->daddr;
+ len = skb->len - sizeof(struct ethhdr);
+
+ usage = bpf_map_lookup_elem(map, &ip4);
+ if (!usage) {
+ /* no entry in the map for this IP address yet. */
+ bpf_map_update_elem(map, &ip4, &len, BPF_ANY);
+ } else {
+ __sync_fetch_and_add(usage, len);
+ }
+
+ return TCX_PASS;
+}
+
+SEC("tc")
+int ingress_func(struct __sk_buff *skb)
+{
+ return update_usage(&ingress_ip4_usage_map, skb, UPDATE_USAGE_INGRESS);
+}
+
+SEC("tc")
+int egress__func(struct __sk_buff *skb)
+{
+ return update_usage(&egress_ip4_usage_map, skb, UPDATE_USAGE_EGRESS);
+}
diff --git a/bpf/gen.go b/bpf/gen.go
new file mode 100644
index 0000000..ff585db
--- /dev/null
+++ b/bpf/gen.go
@@ -0,0 +1,3 @@
+package bpf
+
+//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel bpf bpf_usage.c
diff --git a/bpf/main.go b/bpf/main.go
new file mode 100644
index 0000000..47fdc41
--- /dev/null
+++ b/bpf/main.go
@@ -0,0 +1,124 @@
+package bpf
+
+import (
+ "errors"
+ "fmt"
+ "log"
+ "net"
+ "time"
+
+ "github.com/cilium/ebpf"
+ "github.com/cilium/ebpf/link"
+)
+
+type UsageStat struct {
+ lastSeen time.Time
+ lastDbPush time.Time
+ ingress uint64
+ egress uint64
+}
+type UsageMap map[uint32]UsageStat
+
+func Run(iface *net.Interface) {
+ usageMap := make(UsageMap)
+
+ objs := bpfObjects{}
+ if err := loadBpfObjects(&objs, nil); err != nil {
+ log.Fatalf("loading objects: %s", err)
+ }
+ defer objs.Close()
+
+ ingressLink, err := link.AttachTCX(link.TCXOptions{
+ Interface: iface.Index,
+ Program: objs.IngressFunc,
+ Attach: ebpf.AttachTCXIngress,
+ })
+ if err != nil {
+ log.Fatalf("could not attach TCx program: %s", err)
+ }
+ defer ingressLink.Close()
+
+ egressLink, err := link.AttachTCX(link.TCXOptions{
+ Interface: iface.Index,
+ Program: objs.EgressFunc,
+ Attach: ebpf.AttachTCXEgress,
+ })
+ if err != nil {
+ log.Fatalf("could not attach TCx program: %s", err)
+ }
+ defer egressLink.Close()
+
+ bpfTicker := time.NewTicker(1 * time.Second)
+ defer bpfTicker.Stop()
+ dbTicker := time.NewTicker(60 * time.Second)
+ defer dbTicker.Stop()
+ for {
+ select {
+ case <-bpfTicker.C:
+ usageMap.update(objs.IngressIp4UsageMap, objs.EgressIp4UsageMap)
+ case <-dbTicker.C:
+ continue;
+ }
+ }
+}
+
+func (usageMap UsageMap) update(ingress *ebpf.Map, egress *ebpf.Map) {
+ timeStart := time.Now()
+ batchKeys := make([]uint32, 4096)
+ batchValues := make([]uint64, 4096)
+ var key uint32
+
+ cursor := ebpf.MapBatchCursor{}
+ for {
+ _, err := ingress.BatchLookupAndDelete(&cursor, batchKeys, batchValues, nil)
+ for i := range batchKeys {
+ key = batchKeys[i]
+ usage, ok := usageMap[key]
+ if ok {
+ usage.ingress += batchValues[i]
+ usage.lastSeen = timeStart
+ usageMap[key] = usage;
+ } else {
+ usageMap[key] = UsageStat {
+ ingress: batchValues[i],
+ lastDbPush: timeStart,
+ lastSeen: timeStart,
+ }
+ }
+ }
+
+ if (errors.Is(err, ebpf.ErrKeyNotExist)) {
+ break;
+ } else if err != nil{
+ fmt.Println(err)
+ break
+ }
+ }
+
+ cursor = ebpf.MapBatchCursor{}
+ for {
+ _, err := egress.BatchLookupAndDelete(&cursor, batchKeys, batchValues, nil)
+ for i := range batchKeys {
+ key = batchKeys[i]
+ usage, ok := usageMap[key]
+ if ok {
+ usage.egress += batchValues[i]
+ usage.lastSeen = timeStart
+ usageMap[key] = usage;
+ } else {
+ usageMap[key] = UsageStat {
+ egress: batchValues[i],
+ lastDbPush: timeStart,
+ lastSeen: timeStart,
+ }
+ }
+ }
+
+ if (errors.Is(err, ebpf.ErrKeyNotExist)) {
+ break;
+ } else if err != nil{
+ fmt.Println(err)
+ break
+ }
+ }
+}