From e2f67996a608346ea3f3525ef2febf6ca5d2b78c Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Thu, 4 Jul 2024 13:13:23 +0530 Subject: refactor: drop http api, move to sqlc/postgresql --- api/exampleReq.go | 57 ----------------------- api/login.go | 61 ------------------------ api/main.go | 44 ------------------ api/utils.go | 31 ------------- bpf/bpf_bpfel.go | 125 +++++++++++++++++++++++++++++++++++++++++++++++++ bpf/bpf_bpfel.o | Bin 0 -> 5312 bytes bpf/bpf_usage.c | 73 +++++++++++++++++++++++++++++ bpf/gen.go | 3 ++ bpf/main.go | 124 +++++++++++++++++++++++++++++++++++++++++++++++++ cmd/bpf/bpf_bpfel.go | 125 ------------------------------------------------- cmd/bpf/bpf_bpfel.o | Bin 5312 -> 0 bytes cmd/bpf/bpf_usage.c | 73 ----------------------------- cmd/bpf/gen.go | 3 -- cmd/bpf/main.go | 96 -------------------------------------- cmd/redq/main.go | 14 +++--- cmd/redqctl/main.go | 74 ----------------------------- db/account.go | 129 --------------------------------------------------- db/bearer.go | 127 -------------------------------------------------- db/db.go | 32 +++++++++++++ db/main.go | 77 ------------------------------ db/models.go | 17 +++++++ db/query.sql | 6 +++ db/query.sql.go | 39 ++++++++++++++++ db/schema.sql | 7 +++ db/sqlc.yaml | 10 ++++ db/utils.go | 24 ---------- flake.nix | 2 +- go.mod | 14 ++---- go.sum | 39 +++++++--------- 29 files changed, 467 insertions(+), 959 deletions(-) delete mode 100644 api/exampleReq.go delete mode 100644 api/login.go delete mode 100644 api/main.go delete mode 100644 api/utils.go create mode 100644 bpf/bpf_bpfel.go create mode 100644 bpf/bpf_bpfel.o create mode 100644 bpf/bpf_usage.c create mode 100644 bpf/gen.go create mode 100644 bpf/main.go delete mode 100644 cmd/bpf/bpf_bpfel.go delete mode 100644 cmd/bpf/bpf_bpfel.o delete mode 100644 cmd/bpf/bpf_usage.c delete mode 100644 cmd/bpf/gen.go delete mode 100644 cmd/bpf/main.go delete mode 100644 cmd/redqctl/main.go delete mode 100644 db/account.go delete mode 100644 db/bearer.go create mode 100644 db/db.go delete mode 100644 db/main.go create mode 100644 db/models.go create mode 100644 db/query.sql create mode 100644 db/query.sql.go create mode 100644 db/schema.sql create mode 100644 db/sqlc.yaml delete mode 100644 db/utils.go diff --git a/api/exampleReq.go b/api/exampleReq.go deleted file mode 100644 index 54454a1..0000000 --- a/api/exampleReq.go +++ /dev/null @@ -1,57 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - "net/http" - - redqdb "sinanmohd.com/redq/db" -) - -type examplApiName struct { - db *redqdb.SafeDB - req *RequestApiName - resp *ResponseApiName -} - -type RequestApiName struct { - BearerToken string -} - -type ResponseApiName struct { - Bearer *redqdb.Bearer -} - -func newExamplApiName(db *redqdb.SafeDB) *examplApiName { - a := &examplApiName{} - a.db = db - - return a -} - -func (a *examplApiName) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - a.req = &RequestApiName{} - a.resp = &ResponseApiName{} - a.resp.Bearer = &redqdb.Bearer{} - - err := unmarshal(r.Body, a.req) - fmt.Println(a.req) - if err != nil { - handleError(err, rw, http.StatusUnprocessableEntity) - return - } - - err = a.resp.Bearer.VerifyAndUpdate(a.db, a.req.BearerToken) - if err != nil { - handleError(err, rw, http.StatusUnauthorized) - return - } - - json, err := json.Marshal(a.resp) - if err != nil { - handleError(err, rw, http.StatusInternalServerError) - return - } - - rw.Write(json) -} diff --git a/api/login.go b/api/login.go deleted file mode 100644 index ef43304..0000000 --- a/api/login.go +++ /dev/null @@ -1,61 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" - - "github.com/go-playground/validator/v10" - redqdb "sinanmohd.com/redq/db" -) - -type loginAPI struct { - db *redqdb.SafeDB - validate *validator.Validate - req *RequestLogin - resp *ResponseLogin -} - -type RequestLogin struct { - Account *redqdb.Account `validate:"required"` -} - -type ResponseLogin struct { - Account *redqdb.Account -} - -func newLogin(db *redqdb.SafeDB) *loginAPI { - a := &loginAPI{} - a.db = db - a.validate = validator.New(validator.WithRequiredStructEnabled()) - - return a -} - -func (a *loginAPI) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - a.req = &RequestLogin{} - a.resp = &ResponseLogin{} - - err := unmarshal(r.Body, a.req) - if err == nil { - err = a.validate.Struct(a.req) - } - if err != nil { - handleError(err, rw, http.StatusUnprocessableEntity) - return - } - - err = a.req.Account.Login(a.db) - if err != nil { - handleError(err, rw, http.StatusUnauthorized) - return - } - a.resp.Account = a.req.Account - - json, err := json.Marshal(a.resp) - if err != nil { - handleError(err, rw, http.StatusInternalServerError) - return - } - - rw.Write(json) -} diff --git a/api/main.go b/api/main.go deleted file mode 100644 index c4645b8..0000000 --- a/api/main.go +++ /dev/null @@ -1,44 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - - redqdb "sinanmohd.com/redq/db" -) - -func Run(db *redqdb.SafeDB) { - const prefix string = "POST /_redq/api" - - login := newLogin(db) - http.Handle(prefix+"/ac/login", login) - exampleApi := newExamplApiName(db) - http.Handle(prefix+"/example", exampleApi) - - http.HandleFunc("GET /{$}", home) - http.ListenAndServe(":8008", nil) -} - -func home(rw http.ResponseWriter, r *http.Request) { - const index string = ` - - - - - 🚨 redq - - -
-

- redq is active -

-

- we're soo back 🥳 -

-
- - - ` - - fmt.Fprint(rw, index) -} diff --git a/api/utils.go b/api/utils.go deleted file mode 100644 index 0fc7d35..0000000 --- a/api/utils.go +++ /dev/null @@ -1,31 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - "io" - "log" - "net/http" -) - -func unmarshal(r io.Reader, v any) error { - body, err := io.ReadAll(r) - if err != nil { - return err - } - - err = json.Unmarshal(body, v) - if err != nil { - return err - } - - return nil -} - -func handleError(err error, rw http.ResponseWriter, status int) { - log.Println(err) - - rw.WriteHeader(status) - json := fmt.Sprintf(`{"Error": "%v"}`, http.StatusText(status)) - rw.Write([]byte(json)) -} 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 Binary files /dev/null and b/bpf/bpf_bpfel.o 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 +#include +#include + +#include +#include + +#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 + } + } +} diff --git a/cmd/bpf/bpf_bpfel.go b/cmd/bpf/bpf_bpfel.go deleted file mode 100644 index 1efd705..0000000 --- a/cmd/bpf/bpf_bpfel.go +++ /dev/null @@ -1,125 +0,0 @@ -// 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 deleted file mode 100644 index 789c00f..0000000 Binary files a/cmd/bpf/bpf_bpfel.o and /dev/null differ diff --git a/cmd/bpf/bpf_usage.c b/cmd/bpf/bpf_usage.c deleted file mode 100644 index 5e505da..0000000 --- a/cmd/bpf/bpf_usage.c +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include -#include - -#include -#include - -#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 deleted file mode 100644 index c90c0a7..0000000 --- a/cmd/bpf/gen.go +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 5a5f842..0000000 --- a/cmd/bpf/main.go +++ /dev/null @@ -1,96 +0,0 @@ -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/cmd/redq/main.go b/cmd/redq/main.go index 1f153d3..d8b9b58 100644 --- a/cmd/redq/main.go +++ b/cmd/redq/main.go @@ -2,16 +2,16 @@ package main import ( "log" + "net" - redqapi "sinanmohd.com/redq/api" - redqdb "sinanmohd.com/redq/db" + redqbpf "sinanmohd.com/redq/bpf" ) func main() { - db, err := redqdb.NewSafeDB() - if err != nil { - log.Fatal(err) - } + iface, err := net.InterfaceByName("wlan0") + if err != nil { + log.Fatalf("lookup network: %s", err) + } - redqapi.Run(db) + redqbpf.Run(iface) } diff --git a/cmd/redqctl/main.go b/cmd/redqctl/main.go deleted file mode 100644 index ce77f77..0000000 --- a/cmd/redqctl/main.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "os" - - redqdb "sinanmohd.com/redq/db" -) - -func help() { - const helpString string = -`redqctl is a tool for managing redq. - -Usage: - - redqctl [arguments] - -The commands are: - - create create a redq account - help show this help cruft - -` - - fmt.Print(helpString) -} - -func create(args []string, db *redqdb.SafeDB) { - f := flag.NewFlagSet("create", flag.ExitOnError) - ac := &redqdb.Account{} - ac.Info = &redqdb.Login{} - - f.StringVar(&ac.UserName, "username", "", - "The username to associate with the account") - f.StringVar(&ac.Info.FirstName, "fname", "", - "The first name to associate with the account") - f.StringVar(&ac.Info.LastName, "lname", "", - "The last name to associate with the account") - f.StringVar(&ac.Password, "pass", "", - "The password to associate with the account") - f.UintVar(&ac.Info.Level, "level", 0, - "The level to associate with the account") - f.Parse(args) - - err := ac.CreateAccount(db) - if err != nil { - log.Fatal(err) - } -} - -func main() { - args := os.Args[1:] - if len(args) == 0 { - help() - os.Exit(2) - } - - db, err := redqdb.NewSafeDB() - if err != nil { - log.Fatal(err) - } - - switch args[0] { - case "help": - help() - case "create": - create(args[1:], db) - default: - help() - os.Exit(2) - } -} diff --git a/db/account.go b/db/account.go deleted file mode 100644 index 0668016..0000000 --- a/db/account.go +++ /dev/null @@ -1,129 +0,0 @@ -package db - -import ( - "errors" - "fmt" - "log" -) - -type Account struct { - UserName string `validate:"required,alphanum,max=64"` - Password string `json:",omitempty" validate:"required,min=10,max=128"` - - Info *Login -} - -type Login struct { - id uint - Level uint `validate:"gte=0,lte=100"` - FirstName, LastName string `validate:"required,alphanumunicode"` - Bearer *Bearer -} - -func (ac *Account) CreateAccount(safe *SafeDB) error { - const sqlStatement string = ` - INSERT INTO Accounts ( - id, - UserName, - Password, - Level, - FirstName, - LastName - ) - VALUES (NULL, ?, ?, ?, ?, ?); - ` - - err := safe.validate.Struct(ac) - if err != nil { - return err - } - - safe.mu.Lock() - defer safe.mu.Unlock() - - _, err = safe.db.Exec( - sqlStatement, - ac.UserName, - ToBlake3(ac.Password), - - ac.Info.FirstName, - ac.Info.LastName, - ac.Info.Level, - ) - - return err -} - -func (ac *Account) Login(safe *SafeDB) error { - const sqlStatementQuery string = ` - SELECT id, Password, Level, FirstName, LastName - FROM Accounts - WHERE Accounts.UserName = ? - ` - - err := safe.validate.Struct(ac) - fmt.Println(ac.Password, ac.UserName) - if err != nil { - log.Println(err) - return err - } - - ac.Info = &Login{} - ac.Info.Bearer = &Bearer{} - safe.mu.Lock() - row := safe.db.QueryRow(sqlStatementQuery, ac.UserName) - safe.mu.Unlock() - - var Password string - err = row.Scan( - &ac.Info.id, - &Password, - &ac.Info.FirstName, - &ac.Info.LastName, - &ac.Info.Level, - ) - if err != nil { - return err - } - if Password != ToBlake3(ac.Password) { - return errors.New("Auth failed") - } - ac.Password = "" - - err = ac.Info.Bearer.Generate(safe, ac.Info) - if err != nil { - return err - } - - return err -} - -func (ac *Account) fromBearer(safe *SafeDB, b *Bearer) error { - const sqlStatementAccount string = ` - SELECT UserName, Password, Level, FirstName, LastName - FROM Accounts - WHERE Accounts.id = ? - ` - - safe.mu.Lock() - row := safe.db.QueryRow(sqlStatementAccount, b.accountId) - safe.mu.Unlock() - - ac.Info = &Login{} - ac.Info.id = b.accountId - ac.Info.Bearer = b - err := row.Scan( - &ac.UserName, - &ac.Password, - - &ac.Info.FirstName, - &ac.Info.LastName, - &ac.Info.Level, - ) - if err != nil { - return err - } - ac.Info.Bearer = b - - return err -} diff --git a/db/bearer.go b/db/bearer.go deleted file mode 100644 index b16d506..0000000 --- a/db/bearer.go +++ /dev/null @@ -1,127 +0,0 @@ -package db - -import ( - "errors" - "time" - - _ "github.com/mattn/go-sqlite3" -) - -type Bearer struct { - id, accountId uint - Token string - ValidUpTo time.Time -} - -func (b *Bearer) FromToken(safe *SafeDB, Token string) error { - const sqlStatementBearer string = ` - SELECT id, ValidUpTo, accountId - FROM Bearer - WHERE Bearer.Token = ? - ` - - b.Token = Token - var ValidUpToString string - safe.mu.Lock() - row := safe.db.QueryRow(sqlStatementBearer, Token) - safe.mu.Unlock() - - err := row.Scan( - &b.id, - &ValidUpToString, - &b.accountId, - ) - if err != nil { - return err - } - - layout := "2006-01-02 15:04:05.999999999-07:00" - b.ValidUpTo, err = time.Parse(layout, ValidUpToString) - if err != nil { - return err - } - - timeNow := time.Now() - if timeNow.After(b.ValidUpTo) { - return errors.New("Outdated Bearer Token") - } - - return err -} - -func (b *Bearer) Update(safe *SafeDB) error { - const sqlStatementBearer string = ` - UPDATE Bearer - SET ValidUpTo = ? - WHERE id = ? - ` - - validUpTo := time.Now().Add(time.Hour * 24) - safe.mu.Lock() - _, err := safe.db.Exec(sqlStatementBearer, validUpTo, b.id) - safe.mu.Unlock() - if err != nil { - return err - } - b.ValidUpTo = validUpTo - - return nil -} - -func (b *Bearer) VerifyAndUpdate(safe *SafeDB, token string) error { - err := b.FromToken(safe, token) - if err != nil { - return err - } - - err = b.Update(safe) - if err != nil { - return err - } - - return nil -} - -func (b *Bearer) Generate(safe *SafeDB, lg *Login) error { - const sqlGenBearer string = ` - INSERT INTO Bearer ( - id, - Token, - ValidUpTo, - accountId - ) - VALUES (NULL, ?, ?, ?); - ` - - Token, err := GenRandomString(128) - if err != nil { - return err - } - - timeNow := time.Now() - ValidUpTo := timeNow.Add(time.Hour * 24) - safe.mu.Lock() - res, err := safe.db.Exec( - sqlGenBearer, - Token, - ValidUpTo, - lg.id, - ) - safe.mu.Unlock() - if err != nil { - return err - } - - id, err := res.LastInsertId() - if err != nil { - return err - } - - b.id = uint(id) - b.accountId = lg.id - b.Token = Token - b.ValidUpTo = ValidUpTo - lg.Bearer = b - - return err -} diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..5b8c8f5 --- /dev/null +++ b/db/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/db/main.go b/db/main.go deleted file mode 100644 index 49658cd..0000000 --- a/db/main.go +++ /dev/null @@ -1,77 +0,0 @@ -package db - -import ( - "database/sql" - "os" - "path/filepath" - "sync" - - "github.com/go-playground/validator/v10" - _ "github.com/mattn/go-sqlite3" -) - -type SafeDB struct { - mu sync.Mutex - validate *validator.Validate - - path string - db *sql.DB -} - -func (safe *SafeDB) setupPath() error { - const path string = "/var/lib/redq/" - const name string = "redq.sqlite3" - - err := os.MkdirAll(path, os.ModeDir) - if err != nil { - return err - } - - safe.path = filepath.Join(path, name) - return nil -} - -func NewSafeDB() (*SafeDB, error) { - const create string = ` - CREATE TABLE IF NOT EXISTS Accounts( - id INTEGER PRIMARY KEY, - UserName CHAR(64) NOT NULL UNIQUE, - Password CHAR(128) NOT NULL, - - Level INTEGER NOT NULL, - FirstName CHAR(32) NOT NULL, - LastName CHAR(32) NOT NULL - ); - - CREATE TABLE IF NOT EXISTS Bearer( - id INTEGER PRIMARY KEY, - Token CHAR(128) NOT NULL UNIQUE, - ValidUpTo TIME NOT NULL, - accountId INTEGER NOT NULL, - - FOREIGN KEY (accountId) - REFERENCES Accounts (id) - ); - ` - safe := &SafeDB{} - err := safe.setupPath() - if err != nil { - return nil, err - } - - safe.mu.Lock() - defer safe.mu.Unlock() - - safe.db, err = sql.Open("sqlite3", safe.path) - if err != nil { - return nil, err - } - - _, err = safe.db.Exec(create) - if err != nil { - return nil, err - } - - safe.validate = validator.New(validator.WithRequiredStructEnabled()) - return safe, nil -} diff --git a/db/models.go b/db/models.go new file mode 100644 index 0000000..83e4051 --- /dev/null +++ b/db/models.go @@ -0,0 +1,17 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 + +package db + +import ( + "github.com/jackc/pgx/v5/pgtype" +) + +type Usage struct { + Hardwareaddr int32 + Starttime pgtype.Timestamp + Stoptime pgtype.Timestamp + Egress int32 + Ingress int32 +} diff --git a/db/query.sql b/db/query.sql new file mode 100644 index 0000000..75d5b61 --- /dev/null +++ b/db/query.sql @@ -0,0 +1,6 @@ +-- name: EnterUsage :exec +INSERT INTO Usage ( + HardwareAddr, StartTime, StopTime, Egress, Ingress +) VALUES ( + $1, $2, $3, $4, $5 +); diff --git a/db/query.sql.go b/db/query.sql.go new file mode 100644 index 0000000..de68384 --- /dev/null +++ b/db/query.sql.go @@ -0,0 +1,39 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.26.0 +// source: query.sql + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const enterUsage = `-- name: EnterUsage :exec +INSERT INTO Usage ( + HardwareAddr, StartTime, StopTime, Egress, Ingress +) VALUES ( + $1, $2, $3, $4, $5 +) +` + +type EnterUsageParams struct { + Hardwareaddr int32 + Starttime pgtype.Timestamp + Stoptime pgtype.Timestamp + Egress int32 + Ingress int32 +} + +func (q *Queries) EnterUsage(ctx context.Context, arg EnterUsageParams) error { + _, err := q.db.Exec(ctx, enterUsage, + arg.Hardwareaddr, + arg.Starttime, + arg.Stoptime, + arg.Egress, + arg.Ingress, + ) + return err +} diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..b8f8540 --- /dev/null +++ b/db/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE Usage ( + HardwareAddr INTEGER NOT NULL, + StartTime TIMESTAMP NOT NULL, + StopTime TIMESTAMP NOT NULL, + Egress INTEGER NOT NULL, + Ingress INTEGER NOT NULL +); diff --git a/db/sqlc.yaml b/db/sqlc.yaml new file mode 100644 index 0000000..0cd3357 --- /dev/null +++ b/db/sqlc.yaml @@ -0,0 +1,10 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "query.sql" + schema: "schema.sql" + gen: + go: + package: "db" + out: "./" + sql_package: "pgx/v5" diff --git a/db/utils.go b/db/utils.go deleted file mode 100644 index 0b0f1cb..0000000 --- a/db/utils.go +++ /dev/null @@ -1,24 +0,0 @@ -package db - -import ( - "encoding/base64" - "lukechampine.com/blake3" - "math/rand" -) - -func ToBlake3(pass string) string { - hash := blake3.Sum512([]byte(pass)) - hash64b := base64.StdEncoding.EncodeToString(hash[:]) - - return "blake3-" + hash64b -} - -func GenRandomString(n int) (string, error) { - b := make([]byte, n) - _, err := rand.Read(b) - if err != nil { - return "", err - } - - return base64.URLEncoding.EncodeToString(b)[:n], nil -} diff --git a/flake.nix b/flake.nix index 115172a..c100e82 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ gopls jq - sqlite + sqlc libbpf ccls diff --git a/go.mod b/go.mod index 9d0ee8c..84638f1 100644 --- a/go.mod +++ b/go.mod @@ -4,20 +4,16 @@ 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 + github.com/jackc/pgx/v5 v5.6.0 + golang.org/x/net v0.10.0 ) require ( - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect - github.com/leodido/go-urn v1.4.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/stretchr/testify v1.8.4 // 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 ca6b136..5d343e8 100644 --- a/go.sum +++ b/go.sum @@ -1,49 +1,46 @@ 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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -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/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= -lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= -- cgit v1.2.3