diff options
Diffstat (limited to 'bpf')
-rw-r--r-- | bpf/bpf_bpfel.go | 125 | ||||
-rw-r--r-- | bpf/bpf_bpfel.o | bin | 0 -> 5312 bytes | |||
-rw-r--r-- | bpf/bpf_usage.c | 73 | ||||
-rw-r--r-- | bpf/gen.go | 3 | ||||
-rw-r--r-- | bpf/main.go | 124 |
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 Binary files differnew file mode 100644 index 0000000..789c00f --- /dev/null +++ b/bpf/bpf_bpfel.o 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 + } + } +} |