summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format16
-rw-r--r--cmd/bpf/bpf_bpfel.go125
-rw-r--r--cmd/bpf/bpf_bpfel.obin0 -> 5312 bytes
-rw-r--r--cmd/bpf/bpf_usage.c73
-rw-r--r--cmd/bpf/gen.go3
-rw-r--r--cmd/bpf/main.go96
-rw-r--r--flake.nix15
-rw-r--r--go.mod2
-rw-r--r--go.sum14
9 files changed, 343 insertions, 1 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..f304ad3
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,16 @@
+BasedOnStyle: LLVM
+BreakBeforeBraces: Linux
+IndentCaseLabels: false
+
+AlignConsecutiveMacros: true
+AllowShortIfStatementsOnASingleLine: false
+
+ContinuationIndentWidth: 8
+IndentWidth: 8
+TabWidth: 8
+UseTab: Always
+
+ForEachMacros:
+ - 'LIST_FOREACH'
+ - 'CIRCLEQ_FOREACH'
+ - 'cJSON_ArrayForEach'
diff --git a/cmd/bpf/bpf_bpfel.go b/cmd/bpf/bpf_bpfel.go
new file mode 100644
index 0000000..1efd705
--- /dev/null
+++ b/cmd/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 main
+
+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/cmd/bpf/bpf_bpfel.o b/cmd/bpf/bpf_bpfel.o
new file mode 100644
index 0000000..789c00f
--- /dev/null
+++ b/cmd/bpf/bpf_bpfel.o
Binary files differ
diff --git a/cmd/bpf/bpf_usage.c b/cmd/bpf/bpf_usage.c
new file mode 100644
index 0000000..5e505da
--- /dev/null
+++ b/cmd/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/cmd/bpf/gen.go b/cmd/bpf/gen.go
new file mode 100644
index 0000000..c90c0a7
--- /dev/null
+++ b/cmd/bpf/gen.go
@@ -0,0 +1,3 @@
+package main
+
+//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel bpf bpf_usage.c
diff --git a/cmd/bpf/main.go b/cmd/bpf/main.go
new file mode 100644
index 0000000..5a5f842
--- /dev/null
+++ b/cmd/bpf/main.go
@@ -0,0 +1,96 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net"
+ "net/netip"
+ "os"
+ "time"
+
+ "github.com/cilium/ebpf"
+ "github.com/cilium/ebpf/link"
+)
+
+type Usage struct {
+ ingress uint64
+ egress uint64
+}
+
+func main() {
+ if len(os.Args) < 2 {
+ log.Fatalf("Please specify a network interface")
+ }
+
+ ifaceName := os.Args[1]
+ iface, err := net.InterfaceByName(ifaceName)
+ if err != nil {
+ log.Fatalf("lookup network iface %q: %s", ifaceName, err)
+ }
+
+ // Load pre-compiled programs into the kernel.
+ objs := bpfObjects{}
+ if err := loadBpfObjects(&objs, nil); err != nil {
+ log.Fatalf("loading objects: %s", err)
+ }
+ defer objs.Close()
+
+ // Attach the program.
+ 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()
+
+ // Attach the program to Egress TC.
+ 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()
+
+ ticker := time.NewTicker(1 * time.Second)
+ defer ticker.Stop()
+ for range ticker.C {
+ prettyPrint(objs.IngressIp4UsageMap, objs.EgressIp4UsageMap)
+ }
+}
+
+func prettyPrint(ingress *ebpf.Map, egress *ebpf.Map) {
+ ipUsage := make(map[netip.Addr]Usage)
+ var key netip.Addr
+ var value uint64
+
+ iter := ingress.Iterate()
+ for iter.Next(&key, &value) {
+ ipUsage[key] = Usage {
+ ingress: value,
+ }
+ }
+
+ iter = egress.Iterate()
+ for iter.Next(&key, &value) {
+ usage, ok := ipUsage[key]
+ if (ok) {
+ usage.egress = value
+ } else {
+ usage = Usage { egress: value }
+ }
+
+ ipUsage[key] = usage
+ }
+
+ fmt.Print("\033[H\033[2J")
+ fmt.Printf("%15s\t%16s\t%16s\n", "ip", "down", "up")
+ for ip4, usage := range ipUsage {
+ fmt.Printf("%15s\t%16d\t%16d\n", ip4, usage.ingress, usage.egress)
+ }
+}
diff --git a/flake.nix b/flake.nix
index c7517df..115172a 100644
--- a/flake.nix
+++ b/flake.nix
@@ -16,9 +16,22 @@
default = pkgs.mkShell {
name = "dev";
- buildInputs = with pkgs; [ go_1_22 gopls jq sqlite ];
+ buildInputs = with pkgs; [
+ go
+ gopls
+
+ jq
+ sqlite
+
+ libbpf
+ ccls
+ clang
+ libllvm
+ ];
shellHook = ''
export PS1="\033[0;36m[ ]\033[0m $PS1"
+ # stop littering eBPF C programs with go:build ignore
+ export CGO_ENABLED=0
'';
};
});
diff --git a/go.mod b/go.mod
index e856572..9d0ee8c 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module sinanmohd.com/redq
go 1.22.0
require (
+ github.com/cilium/ebpf v0.15.0
github.com/go-playground/validator/v10 v10.19.0
github.com/mattn/go-sqlite3 v1.14.22
lukechampine.com/blake3 v1.2.1
@@ -15,6 +16,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
+ golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
diff --git a/go.sum b/go.sum
index 62ae8ea..ca6b136 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
+github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
@@ -10,18 +12,30 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
+github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=
+golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=