From 6d569ca346e353c29461fd5f9b0ad2a8c3423029 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 20 Jul 2023 14:02:21 +0200 Subject: [PATCH] Improve epbf bandwidth stats monitor --- .../interception/ebpf/bandwidth/bpf_bpfeb.go | 5 +- .../interception/ebpf/bandwidth/bpf_bpfeb.o | Bin 41832 -> 35488 bytes .../interception/ebpf/bandwidth/bpf_bpfel.go | 5 +- .../interception/ebpf/bandwidth/bpf_bpfel.o | Bin 41832 -> 35488 bytes .../interception/ebpf/bandwidth/interface.go | 194 +++++++++--------- .../interception/ebpf/programs/bandwidth.c | 32 ++- 6 files changed, 121 insertions(+), 115 deletions(-) diff --git a/firewall/interception/ebpf/bandwidth/bpf_bpfeb.go b/firewall/interception/ebpf/bandwidth/bpf_bpfeb.go index 6c5d088c..a691e3bd 100644 --- a/firewall/interception/ebpf/bandwidth/bpf_bpfeb.go +++ b/firewall/interception/ebpf/bandwidth/bpf_bpfeb.go @@ -14,8 +14,9 @@ import ( ) type bpfSkInfo struct { - Rx uint64 - Tx uint64 + Rx uint64 + Tx uint64 + Reported uint64 } type bpfSkKey struct { diff --git a/firewall/interception/ebpf/bandwidth/bpf_bpfeb.o b/firewall/interception/ebpf/bandwidth/bpf_bpfeb.o index dea3c5f67ba7d8dd1c40046a0995b1410c19aed7..abeaf00deb42c02e2d69d6950f8c8f9265b36a63 100644 GIT binary patch literal 35488 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 ca3CSP%TTn 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 BuKNH0 diff --git a/firewall/interception/ebpf/bandwidth/interface.go b/firewall/interception/ebpf/bandwidth/interface.go index cd85572d..f23d4452 100644 --- a/firewall/interception/ebpf/bandwidth/interface.go +++ b/firewall/interception/ebpf/bandwidth/interface.go @@ -1,148 +1,152 @@ package ebpf import ( + "context" "encoding/binary" "fmt" "net" "path/filepath" + "sync/atomic" "syscall" + "time" "github.com/cilium/ebpf" "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/rlimit" + "golang.org/x/sys/unix" + "github.com/safing/portbase/log" "github.com/safing/portmaster/network/packet" - "golang.org/x/sys/unix" ) //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -cflags "-O2 -g -Wall -Werror" bpf ../programs/bandwidth.c -var ebpfInterface = struct { - objs bpfObjects - sockOptionsLink link.Link - udpv4SMLink link.Link - udpv4RMLink link.Link - udpv6SMLink link.Link - udpv6RMLink link.Link -}{ - objs: bpfObjects{}, -} - -// SetupBandwidthInterface initializes the ebpf interface and starts gattering bandwidth information for all connections. -func SetupBandwidthInterface() error { +var ebpfLoadingFailed atomic.Uint32 +// BandwidthStatsWorker monitors connection bandwidth using ebpf. +func BandwidthStatsWorker(ctx context.Context, collectInterval time.Duration, bandwidthUpdates chan *packet.BandwidthUpdate) error { // Allow the current process to lock memory for eBPF resources. err := rlimit.RemoveMemlock() if err != nil { - return fmt.Errorf("failed to remove memlock: %s", err) + return fmt.Errorf("ebpf: failed to remove memlock: %w", err) } // Load pre-compiled programs and maps into the kernel. - err = loadBpfObjects(&ebpfInterface.objs, nil) - if err != nil { - return fmt.Errorf("feiled loading objects: %s", err) - } - - defer func() { - if err != nil { - // Defer the cleanup function to be called at the end of the enclosing function - // If there was an error during the execution, shutdown the BandwithInterface - ShutdownBandwithInterface() + objs := bpfObjects{} + if err := loadBpfObjects(&objs, nil); err != nil { + if ebpfLoadingFailed.Add(1) >= 5 { + log.Warningf("ebpf: failed to load ebpf object 5 times, giving up with error %s", err) + return nil } - }() + return fmt.Errorf("ebpf: failed to load ebpf object: %w", err) + } + defer objs.Close() //nolint:errcheck // Find the cgroup path path, err := findCgroupPath() if err != nil { - return fmt.Errorf("faield to find cgroup paths: %s", err) + return fmt.Errorf("ebpf: failed to find cgroup paths: %w", err) } // Attach socket options for monitoring connections - ebpfInterface.sockOptionsLink, err = link.AttachCgroup(link.CgroupOptions{ + sockOptionsLink, err := link.AttachCgroup(link.CgroupOptions{ Path: path, - Program: ebpfInterface.objs.bpfPrograms.SocketOperations, + Program: objs.bpfPrograms.SocketOperations, Attach: ebpf.AttachCGroupSockOps, }) if err != nil { - return fmt.Errorf("Failed to open module sockops: %s", err) + return fmt.Errorf("ebpf: failed to open module sockops: %w", err) } + defer sockOptionsLink.Close() //nolint:errcheck // Attach Udp Ipv4 recive message tracing - ebpfInterface.udpv4RMLink, err = link.AttachTracing(link.TracingOptions{ - Program: ebpfInterface.objs.UdpRecvmsg, + udpv4RMLink, err := link.AttachTracing(link.TracingOptions{ + Program: objs.bpfPrograms.UdpRecvmsg, }) if err != nil { - return fmt.Errorf("Failed to open trace Udp IPv4 recvmsg: %s", err) + return fmt.Errorf("ebpf: failed to open trace Udp IPv4 recvmsg: %w", err) } + defer udpv4RMLink.Close() //nolint:errcheck // Attach UDP IPv4 send message tracing - ebpfInterface.udpv4SMLink, err = link.AttachTracing(link.TracingOptions{ - Program: ebpfInterface.objs.UdpSendmsg, + udpv4SMLink, err := link.AttachTracing(link.TracingOptions{ + Program: objs.bpfPrograms.UdpSendmsg, }) if err != nil { - return fmt.Errorf("Failed to open trace Udp IPv4 sendmsg: %s", err) + return fmt.Errorf("ebpf: failed to open trace Udp IPv4 sendmsg: %w", err) } + defer udpv4SMLink.Close() //nolint:errcheck // Attach UDP IPv6 receive message tracing - ebpfInterface.udpv6RMLink, err = link.AttachTracing(link.TracingOptions{ - Program: ebpfInterface.objs.Udpv6Recvmsg, + udpv6RMLink, err := link.AttachTracing(link.TracingOptions{ + Program: objs.bpfPrograms.Udpv6Recvmsg, }) if err != nil { - return fmt.Errorf("Failed to open trace Udp IPv6 recvmsg: %s", err) + return fmt.Errorf("ebpf: failed to open trace Udp IPv6 recvmsg: %w", err) } + defer udpv6RMLink.Close() //nolint:errcheck // Attach UDP IPv6 send message tracing - ebpfInterface.udpv6RMLink, err = link.AttachTracing(link.TracingOptions{ - Program: ebpfInterface.objs.Udpv6Sendmsg, + udpv6SMLink, err := link.AttachTracing(link.TracingOptions{ + Program: objs.bpfPrograms.Udpv6Sendmsg, }) if err != nil { - return fmt.Errorf("Failed to open trace Udp IPv6 sendmsg: %s", err) + return fmt.Errorf("ebpf: failed to open trace Udp IPv6 sendmsg: %w", err) } + defer udpv6SMLink.Close() //nolint:errcheck - // Example code that will print the bandwidth table every 10 seconds - // go func() { - // ticker := time.NewTicker(10 * time.Second) - // defer ticker.Stop() - // for range ticker.C { - // printBandwidthData() - // } - // }() + // Setup ticker. + ticker := time.NewTicker(collectInterval) + defer ticker.Stop() - return nil + // Collect bandwidth at every tick. + for { + select { + case <-ticker.C: + reportBandwidth(ctx, objs, bandwidthUpdates) + case <-ctx.Done(): + return nil + } + } } -// ShutdownBandwithInterface shuts down the bandwidth interface by closing the associated links and objects. -func ShutdownBandwithInterface() { - // Close the sockOptionsLink if it is not nil - if ebpfInterface.sockOptionsLink != nil { - ebpfInterface.sockOptionsLink.Close() - } +// reportBandwidth reports the bandwidth to the given updates channel. +func reportBandwidth(ctx context.Context, objs bpfObjects, bandwidthUpdates chan *packet.BandwidthUpdate) { + iter := objs.bpfMaps.PmBandwidthMap.Iterate() + var skKey bpfSkKey + var skInfo bpfSkInfo + for iter.Next(&skKey, &skInfo) { + // Check if already reported. + if skInfo.Reported >= 1 { + continue + } + // Mark as reported and update the map. + skInfo.Reported = 1 + if err := objs.bpfMaps.PmBandwidthMap.Put(&skKey, &skInfo); err != nil { + log.Debugf("ebpf: failed to update map: %s", err) + } - // Close the udpv4SMLink if it is not nil - if ebpfInterface.udpv4SMLink != nil { - ebpfInterface.udpv4SMLink.Close() + connID := packet.CreateConnectionID( + packet.IPProtocol(skKey.Protocol), + convertArrayToIP(skKey.SrcIp, skKey.Ipv6 == 1), skKey.SrcPort, + convertArrayToIP(skKey.DstIp, skKey.Ipv6 == 1), skKey.DstPort, + false, + ) + update := &packet.BandwidthUpdate{ + ConnID: connID, + RecvBytes: skInfo.Rx, + SentBytes: skInfo.Tx, + Method: packet.Absolute, + } + select { + case bandwidthUpdates <- update: + case <-ctx.Done(): + return + } } - - // Close the udpv4RMLink if it is not nil - if ebpfInterface.udpv4RMLink != nil { - ebpfInterface.udpv4RMLink.Close() - } - - // Close the udpv6SMLink if it is not nil - if ebpfInterface.udpv6SMLink != nil { - ebpfInterface.udpv6SMLink.Close() - } - - // Close the udpv6RMLink if it is not nil - if ebpfInterface.udpv6RMLink != nil { - ebpfInterface.udpv6RMLink.Close() - } - - // Close the ebpfInterface objects - ebpfInterface.objs.Close() } -// findCgroupPath returns the default unified path of the cgroup +// findCgroupPath returns the default unified path of the cgroup. func findCgroupPath() (string, error) { cgroupPath := "/sys/fs/cgroup" @@ -158,31 +162,17 @@ func findCgroupPath() (string, error) { return cgroupPath, nil } -// printBandwidthData prints the contencs of the shared map in the ebpf program. -func printBandwidthData() { - iter := ebpfInterface.objs.bpfMaps.PmBandwidthMap.Iterate() - var skKey bpfSkKey - var skInfo bpfSkInfo - for iter.Next(&skKey, &skInfo) { - log.Debugf("Connection: %d %s:%d %s:%d %d %d", skKey.Protocol, - convertArrayToIPv4(skKey.SrcIp, packet.IPVersion(skKey.Ipv6)).String(), skKey.SrcPort, - convertArrayToIPv4(skKey.DstIp, packet.IPVersion(skKey.Ipv6)).String(), skKey.DstPort, - skInfo.Rx, skInfo.Tx, - ) - } -} - -// convertArrayToIPv4 converts an array of uint32 values to an IPv4 net.IP address. -func convertArrayToIPv4(input [4]uint32, ipVersion packet.IPVersion) net.IP { - if ipVersion == packet.IPv4 { +// convertArrayToIP converts an array of uint32 values to a net.IP address. +func convertArrayToIP(input [4]uint32, ipv6 bool) net.IP { + if !ipv6 { addressBuf := make([]byte, 4) binary.LittleEndian.PutUint32(addressBuf, input[0]) return net.IP(addressBuf) - } else { - addressBuf := make([]byte, 16) - for i := 0; i < 4; i++ { - binary.LittleEndian.PutUint32(addressBuf[i*4:i*4+4], input[i]) - } - return net.IP(addressBuf) } + + addressBuf := make([]byte, 16) + for i := 0; i < 4; i++ { + binary.LittleEndian.PutUint32(addressBuf[i*4:i*4+4], input[i]) + } + return net.IP(addressBuf) } diff --git a/firewall/interception/ebpf/programs/bandwidth.c b/firewall/interception/ebpf/programs/bandwidth.c index 8971de48..b56ce763 100644 --- a/firewall/interception/ebpf/programs/bandwidth.c +++ b/firewall/interception/ebpf/programs/bandwidth.c @@ -9,7 +9,7 @@ #define PROTOCOL_TCP 6 #define PROTOCOL_UDP 17 -char __license[] SEC("license") = "Dual MIT/GPL"; +char __license[] SEC("license") = "GPL"; struct sk_key { u32 src_ip[4]; @@ -23,6 +23,7 @@ struct sk_key { struct sk_info { u64 rx; u64 tx; + u64 reported; }; // Max number of connections that will be kept. Increse the number if it's not enough. @@ -84,12 +85,21 @@ int socket_operations(struct bpf_sock_ops *skops) { } key.dst_port = __builtin_bswap16(sk->dst_port); key.ipv6 = 1; + + // FIXME: This should be added here too, but loading the ebpf module fails if we add it. + // This 100% the same thing as above. No clue, man. + + // struct sk_info newInfo = {0}; + // newInfo.rx = skops->bytes_received; + // newInfo.tx = skops->bytes_acked; + + // bpf_map_update_elem(&pm_bandwidth_map, &key, &newInfo, BPF_ANY); } return 0; } -// udp_sendmsg hookes to the equvelent kernel function and saves the bandwoth data +// udp_sendmsg hookes to the respective kernel function and saves the bandwidth data SEC("fentry/udp_sendmsg") int BPF_PROG(udp_sendmsg, struct sock *sk, struct msghdr *msg, size_t len) { struct sock_common *skc = &sk->__sk_common; @@ -106,7 +116,8 @@ int BPF_PROG(udp_sendmsg, struct sock *sk, struct msghdr *msg, size_t len) { // Update the map with the new information struct sk_info *info = bpf_map_lookup_elem(&pm_bandwidth_map, &key); if (info != NULL) { - __sync_fetch_and_add(&info->tx, len); + __sync_fetch_and_add(&info->tx, len); // TODO: Use atomic instead. + __sync_fetch_and_and(&info->reported, 0); // TODO: Use atomic instead. } else { struct sk_info newInfo = {0}; @@ -117,7 +128,7 @@ int BPF_PROG(udp_sendmsg, struct sock *sk, struct msghdr *msg, size_t len) { return 0; }; -// udp_recvmsg hookes to the equvelent kernel function and saves the bandwoth data +// udp_recvmsg hookes to the respective kernel function and saves the bandwidth data SEC("fentry/udp_recvmsg") int BPF_PROG(udp_recvmsg, struct sock *sk, struct msghdr *msg, size_t len, int flags, int *addr_len) { struct sock_common *skc = &sk->__sk_common; @@ -134,7 +145,8 @@ int BPF_PROG(udp_recvmsg, struct sock *sk, struct msghdr *msg, size_t len, int f // Update the map with the new information struct sk_info *info = bpf_map_lookup_elem(&pm_bandwidth_map, &key); if (info != NULL) { - __sync_fetch_and_add(&info->rx, len); + __sync_fetch_and_add(&info->rx, len); // TODO: Use atomic instead. + __sync_fetch_and_and(&info->reported, 0); // TODO: Use atomic instead. } else { struct sk_info newInfo = {0}; @@ -145,7 +157,7 @@ int BPF_PROG(udp_recvmsg, struct sock *sk, struct msghdr *msg, size_t len, int f return 0; }; -// udpv6_sendmsg hookes to the equvelent kernel function and saves the bandwoth data +// udpv6_sendmsg hookes to the respective kernel function and saves the bandwidth data SEC("fentry/udpv6_sendmsg") int BPF_PROG(udpv6_sendmsg, struct sock *sk, struct msghdr *msg, size_t len) { struct sock_common *skc = &sk->__sk_common; @@ -164,7 +176,8 @@ int BPF_PROG(udpv6_sendmsg, struct sock *sk, struct msghdr *msg, size_t len) { // Update the map with the new information struct sk_info *info = bpf_map_lookup_elem(&pm_bandwidth_map, &key); if (info != NULL) { - __sync_fetch_and_add(&info->tx, len); + __sync_fetch_and_add(&info->tx, len); // TODO: Use atomic instead. + __sync_fetch_and_and(&info->reported, 0); // TODO: Use atomic instead. } else { struct sk_info newInfo = {0}; newInfo.tx = len; @@ -174,7 +187,7 @@ int BPF_PROG(udpv6_sendmsg, struct sock *sk, struct msghdr *msg, size_t len) { return 0; } -// udpv6_recvmsg hookes to the equvelent kernel function and saves the bandwoth data +// udpv6_recvmsg hookes to the respective kernel function and saves the bandwidth data SEC("fentry/udpv6_recvmsg") int BPF_PROG(udpv6_recvmsg, struct sock *sk, struct msghdr *msg, size_t len, int flags, int *addr_len) { struct sock_common *skc = &sk->__sk_common; @@ -193,7 +206,8 @@ int BPF_PROG(udpv6_recvmsg, struct sock *sk, struct msghdr *msg, size_t len, int // Update the map with the new information struct sk_info *info = bpf_map_lookup_elem(&pm_bandwidth_map, &key); if (info != NULL) { - __sync_fetch_and_add(&info->rx, len); + __sync_fetch_and_add(&info->rx, len); // TODO: Use atomic instead. + __sync_fetch_and_and(&info->reported, 0); // TODO: Use atomic instead. } else { struct sk_info newInfo = {0}; newInfo.rx = len;