From d08ae9402429bfca69d72c7a7d99975aa356539f Mon Sep 17 00:00:00 2001 From: sinanmohd Date: Mon, 8 Jul 2024 06:05:08 +0530 Subject: bpf/filter: init --- bpf/filter/bpf.c | 82 +++++++++++++++++++++++++++++++++ bpf/filter/bpf_bpfel.go | 119 ++++++++++++++++++++++++++++++++++++++++++++++++ bpf/filter/bpf_bpfel.o | Bin 0 -> 3728 bytes bpf/filter/gen.go | 3 ++ bpf/filter/main.go | 95 ++++++++++++++++++++++++++++++++++++++ cmd/main.go | 6 +++ db/models.go | 4 ++ db/query.sql | 14 ++++++ db/query.sql.go | 47 +++++++++++++++++++ db/schema.sql | 4 ++ 10 files changed, 374 insertions(+) create mode 100644 bpf/filter/bpf.c create mode 100644 bpf/filter/bpf_bpfel.go create mode 100644 bpf/filter/bpf_bpfel.o create mode 100644 bpf/filter/gen.go create mode 100644 bpf/filter/main.go diff --git a/bpf/filter/bpf.c b/bpf/filter/bpf.c new file mode 100644 index 0000000..ae62cb8 --- /dev/null +++ b/bpf/filter/bpf.c @@ -0,0 +1,82 @@ +#include +#include +#include + +#include +#include + +#define MAX_MAP_ENTRIES 4096 + +char __license[] SEC("license") = "GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, MAX_MAP_ENTRIES); + __type(key, __u64); // blocked mac address + // i just like her for the o(1) key lookup + // we don't care about the value + __type(value, __u16); +} mac_blacklist_map SEC(".maps"); + +static __always_inline __u64 nchar6_to_u64(unsigned char bytes[6]) +{ + union { + char bytes[6]; + __u64 i; + } ret; + + ret.i = 0; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + ret.bytes[0] = bytes[5]; + ret.bytes[1] = bytes[4]; + ret.bytes[2] = bytes[3]; + ret.bytes[3] = bytes[2]; + ret.bytes[4] = bytes[1]; + ret.bytes[5] = bytes[0]; +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + ret.bytes[0] = bytes[0]; + ret.bytes[1] = bytes[1]; + ret.bytes[2] = bytes[2]; + ret.bytes[3] = bytes[3]; + ret.bytes[4] = bytes[4]; + ret.bytes[5] = bytes[5]; +#endif + + return ret.i; +} + +static __always_inline int mac_src_parse(struct xdp_md *ctx, __u64 *mac) +{ + __u64 len, *usage; + + void *data_end = (void *)(long)ctx->data_end; + struct ethhdr *eth = (void *)(long)ctx->data; + + if ((void *) (eth + 1) > data_end) + return -1; + + if (eth->h_proto != bpf_htons(ETH_P_IP) && + eth->h_proto != bpf_htons(ETH_P_IPV6)) { + return -1; + } + + *mac = nchar6_to_u64(eth->h_source); + return 0; +} + +SEC("xdp") +int mac_filter(struct xdp_md *ctx) +{ + __u64 mac; + int ret, *blocked; + + ret = mac_src_parse(ctx, &mac); + if (ret < 0) + return XDP_PASS; + + blocked = bpf_map_lookup_elem(&mac_blacklist_map, &mac); + if (blocked) + return XDP_DROP; + + return XDP_PASS; +} diff --git a/bpf/filter/bpf_bpfel.go b/bpf/filter/bpf_bpfel.go new file mode 100644 index 0000000..53ec23f --- /dev/null +++ b/bpf/filter/bpf_bpfel.go @@ -0,0 +1,119 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 + +package filter + +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 { + MacFilter *ebpf.ProgramSpec `ebpf:"mac_filter"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { + MacBlacklistMap *ebpf.MapSpec `ebpf:"mac_blacklist_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 { + MacBlacklistMap *ebpf.Map `ebpf:"mac_blacklist_map"` +} + +func (m *bpfMaps) Close() error { + return _BpfClose( + m.MacBlacklistMap, + ) +} + +// 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 { + MacFilter *ebpf.Program `ebpf:"mac_filter"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.MacFilter, + ) +} + +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/filter/bpf_bpfel.o b/bpf/filter/bpf_bpfel.o new file mode 100644 index 0000000..2167b99 Binary files /dev/null and b/bpf/filter/bpf_bpfel.o differ diff --git a/bpf/filter/gen.go b/bpf/filter/gen.go new file mode 100644 index 0000000..d70c549 --- /dev/null +++ b/bpf/filter/gen.go @@ -0,0 +1,3 @@ +package filter + +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel bpf bpf.c diff --git a/bpf/filter/main.go b/bpf/filter/main.go new file mode 100644 index 0000000..4df312c --- /dev/null +++ b/bpf/filter/main.go @@ -0,0 +1,95 @@ +package filter + +import ( + "context" + "fmt" + "log" + "net" + + "github.com/cilium/ebpf/link" + "sinanmohd.com/redq/db" +) + +type Filter struct { + ctxDb context.Context + queries *db.Queries + objs bpfObjects + xdpLink link.Link +} + +func Close(f *Filter) { + f.objs.Close() + f.xdpLink.Close() +} + +func New(iface *net.Interface, queries *db.Queries, ctxDb context.Context) (*Filter, error) { + var err error + var f Filter + + if err := loadBpfObjects(&f.objs, nil); err != nil { + log.Printf("loading objects: %s", err) + return nil, err + } + defer func() { + if err != nil { + f.objs.Close() + } + }() + + f.xdpLink, err = link.AttachXDP(link.XDPOptions{ + Interface: iface.Index, + Program: f.objs.MacFilter, + }) + if err != nil { + log.Printf("could not attach TCx program: %s", err) + return nil, err + } + defer func() { + if err != nil { + f.xdpLink.Close() + } + }() + + blackList, err := queries.GetMacBlackList(ctxDb) + zeros := make([]uint16, len(blackList)) + _, err = f.objs.bpfMaps.MacBlacklistMap.BatchUpdate(blackList[:], zeros, nil) + if err != nil { + log.Printf("loading mac blacklist: %s", err) + return nil, err + } + + f.queries = queries + return &f, nil +} + +func (f *Filter) Block(mac uint64) error { + err := f.queries.EnterMacBlackList(f.ctxDb, int64(mac)) + if err != nil { + log.Printf("adding mac blacklist: %s", err) + return err + } + + err = f.objs.bpfMaps.MacBlacklistMap.Put(mac, true) + if err != nil { + log.Printf("adding mac blacklist: %s", err) + return err + } + + return nil +} + +func (f *Filter) Unblock(mac uint64) error { + err := f.queries.DeleteDnsBlackList(f.ctxDb, fmt.Sprintf("%v", mac)) + if err != nil { + log.Printf("adding mac blacklist: %s", err) + return err + } + + err = f.objs.bpfMaps.MacBlacklistMap.Delete(mac) + if err != nil { + log.Printf("adding mac blacklist: %s", err) + return err + } + + return nil +} diff --git a/cmd/main.go b/cmd/main.go index 5c58d44..dc456ce 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -13,6 +13,7 @@ import ( "sinanmohd.com/redq/db" "sinanmohd.com/redq/dns" "sinanmohd.com/redq/bpf/usage" + "sinanmohd.com/redq/bpf/filter" ) func main() { @@ -37,6 +38,10 @@ func main() { if err != nil { os.Exit(0) } + f, err := filter.New(iface, queries, ctx) + if err != nil { + os.Exit(0) + } u, err := usage.New(iface) if err != nil { os.Exit(0) @@ -47,6 +52,7 @@ func main() { go func() { <-sigs usage.Close(u, queries, ctx) + filter.Close(f) api.Close(a) os.Exit(0) }() diff --git a/db/models.go b/db/models.go index 1ea2797..3beb527 100644 --- a/db/models.go +++ b/db/models.go @@ -12,6 +12,10 @@ type Dnsblacklist struct { Name string } +type Macblacklist struct { + Hardwareaddr int64 +} + type Usage struct { Hardwareaddr int64 Starttime pgtype.Timestamp diff --git a/db/query.sql b/db/query.sql index f4b17dd..624442f 100644 --- a/db/query.sql +++ b/db/query.sql @@ -21,3 +21,17 @@ WHERE Name = $1; -- name: GetDnsBlackList :many SELECT * FROM DnsBlackList; + +-- name: EnterMacBlackList :exec +INSERT INTO MacBlackList ( + HardwareAddr +) VALUES ( + $1 +); + +-- name: DeleteMacBlackList :exec +DELETE FROM MacBlackList +WHERE HardwareAddr = $1; + +-- name: GetMacBlackList :many +SELECT * FROM MacBlackList; diff --git a/db/query.sql.go b/db/query.sql.go index 7c28723..57bee9f 100644 --- a/db/query.sql.go +++ b/db/query.sql.go @@ -21,6 +21,16 @@ func (q *Queries) DeleteDnsBlackList(ctx context.Context, name string) error { return err } +const deleteMacBlackList = `-- name: DeleteMacBlackList :exec +DELETE FROM MacBlackList +WHERE HardwareAddr = $1 +` + +func (q *Queries) DeleteMacBlackList(ctx context.Context, hardwareaddr int64) error { + _, err := q.db.Exec(ctx, deleteMacBlackList, hardwareaddr) + return err +} + const enterDnsBlackList = `-- name: EnterDnsBlackList :exec INSERT INTO DnsBlackList ( Name @@ -34,6 +44,19 @@ func (q *Queries) EnterDnsBlackList(ctx context.Context, name string) error { return err } +const enterMacBlackList = `-- name: EnterMacBlackList :exec +INSERT INTO MacBlackList ( + HardwareAddr +) VALUES ( + $1 +) +` + +func (q *Queries) EnterMacBlackList(ctx context.Context, hardwareaddr int64) error { + _, err := q.db.Exec(ctx, enterMacBlackList, hardwareaddr) + return err +} + const enterUsage = `-- name: EnterUsage :exec INSERT INTO Usage ( HardwareAddr, StartTime, StopTime, Egress, Ingress @@ -85,6 +108,30 @@ func (q *Queries) GetDnsBlackList(ctx context.Context) ([]string, error) { return items, nil } +const getMacBlackList = `-- name: GetMacBlackList :many +SELECT hardwareaddr FROM MacBlackList +` + +func (q *Queries) GetMacBlackList(ctx context.Context) ([]int64, error) { + rows, err := q.db.Query(ctx, getMacBlackList) + if err != nil { + return nil, err + } + defer rows.Close() + var items []int64 + for rows.Next() { + var hardwareaddr int64 + if err := rows.Scan(&hardwareaddr); err != nil { + return nil, err + } + items = append(items, hardwareaddr) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getUsage = `-- name: GetUsage :one SELECT SUM(Ingress) AS Ingress, SUM(Egress) AS Egress FROM Usage ` diff --git a/db/schema.sql b/db/schema.sql index c8c35a1..78668c6 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -9,3 +9,7 @@ CREATE TABLE IF NOT EXISTS Usage ( CREATE TABLE IF NOT EXISTS DnsBlackList ( Name TEXT NOT NULL UNIQUE ); + +CREATE TABLE IF NOT EXISTS MacBlackList ( + HardwareAddr BIGINT NOT NULL UNIQUE +); -- cgit v1.2.3