diff --git a/firewall/interception/ebpf/bandwidth/bpf_bpfeb.go b/firewall/interception/ebpf/bandwidth/bpf_bpfeb.go new file mode 100644 index 00000000..6c5d088c --- /dev/null +++ b/firewall/interception/ebpf/bandwidth/bpf_bpfeb.go @@ -0,0 +1,147 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64 +// +build arm64be armbe mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64 + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type bpfSkInfo struct { + Rx uint64 + Tx uint64 +} + +type bpfSkKey struct { + SrcIp [4]uint32 + DstIp [4]uint32 + SrcPort uint16 + DstPort uint16 + Protocol uint8 + Ipv6 uint8 + _ [2]byte +} + +// 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 { + SocketOperations *ebpf.ProgramSpec `ebpf:"socket_operations"` + UdpRecvmsg *ebpf.ProgramSpec `ebpf:"udp_recvmsg"` + UdpSendmsg *ebpf.ProgramSpec `ebpf:"udp_sendmsg"` + Udpv6Recvmsg *ebpf.ProgramSpec `ebpf:"udpv6_recvmsg"` + Udpv6Sendmsg *ebpf.ProgramSpec `ebpf:"udpv6_sendmsg"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { + PmBandwidthMap *ebpf.MapSpec `ebpf:"pm_bandwidth_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 { + PmBandwidthMap *ebpf.Map `ebpf:"pm_bandwidth_map"` +} + +func (m *bpfMaps) Close() error { + return _BpfClose( + m.PmBandwidthMap, + ) +} + +// 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 { + SocketOperations *ebpf.Program `ebpf:"socket_operations"` + UdpRecvmsg *ebpf.Program `ebpf:"udp_recvmsg"` + UdpSendmsg *ebpf.Program `ebpf:"udp_sendmsg"` + Udpv6Recvmsg *ebpf.Program `ebpf:"udpv6_recvmsg"` + Udpv6Sendmsg *ebpf.Program `ebpf:"udpv6_sendmsg"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.SocketOperations, + p.UdpRecvmsg, + p.UdpSendmsg, + p.Udpv6Recvmsg, + p.Udpv6Sendmsg, + ) +} + +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_bpfeb.o +var _BpfBytes []byte diff --git a/firewall/interception/ebpf/bandwidth/bpf_bpfeb.o b/firewall/interception/ebpf/bandwidth/bpf_bpfeb.o new file mode 100644 index 00000000..dea3c5f6 Binary files /dev/null and b/firewall/interception/ebpf/bandwidth/bpf_bpfeb.o differ diff --git a/firewall/interception/ebpf/bandwidth/bpf_bpfel.go b/firewall/interception/ebpf/bandwidth/bpf_bpfel.go new file mode 100644 index 00000000..100ab23e --- /dev/null +++ b/firewall/interception/ebpf/bandwidth/bpf_bpfel.go @@ -0,0 +1,147 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 +// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 + +package ebpf + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type bpfSkInfo struct { + Rx uint64 + Tx uint64 +} + +type bpfSkKey struct { + SrcIp [4]uint32 + DstIp [4]uint32 + SrcPort uint16 + DstPort uint16 + Protocol uint8 + Ipv6 uint8 + _ [2]byte +} + +// 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 { + SocketOperations *ebpf.ProgramSpec `ebpf:"socket_operations"` + UdpRecvmsg *ebpf.ProgramSpec `ebpf:"udp_recvmsg"` + UdpSendmsg *ebpf.ProgramSpec `ebpf:"udp_sendmsg"` + Udpv6Recvmsg *ebpf.ProgramSpec `ebpf:"udpv6_recvmsg"` + Udpv6Sendmsg *ebpf.ProgramSpec `ebpf:"udpv6_sendmsg"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { + PmBandwidthMap *ebpf.MapSpec `ebpf:"pm_bandwidth_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 { + PmBandwidthMap *ebpf.Map `ebpf:"pm_bandwidth_map"` +} + +func (m *bpfMaps) Close() error { + return _BpfClose( + m.PmBandwidthMap, + ) +} + +// 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 { + SocketOperations *ebpf.Program `ebpf:"socket_operations"` + UdpRecvmsg *ebpf.Program `ebpf:"udp_recvmsg"` + UdpSendmsg *ebpf.Program `ebpf:"udp_sendmsg"` + Udpv6Recvmsg *ebpf.Program `ebpf:"udpv6_recvmsg"` + Udpv6Sendmsg *ebpf.Program `ebpf:"udpv6_sendmsg"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.SocketOperations, + p.UdpRecvmsg, + p.UdpSendmsg, + p.Udpv6Recvmsg, + p.Udpv6Sendmsg, + ) +} + +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/firewall/interception/ebpf/bandwidth/bpf_bpfel.o b/firewall/interception/ebpf/bandwidth/bpf_bpfel.o new file mode 100644 index 00000000..3607e301 Binary files /dev/null and b/firewall/interception/ebpf/bandwidth/bpf_bpfel.o differ diff --git a/firewall/interception/ebpf/bandwidth/interface.go b/firewall/interception/ebpf/bandwidth/interface.go new file mode 100644 index 00000000..3930a45e --- /dev/null +++ b/firewall/interception/ebpf/bandwidth/interface.go @@ -0,0 +1,179 @@ +package ebpf + +import ( + "fmt" + "net" + "path/filepath" + "syscall" + "time" + "unsafe" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/rlimit" + "github.com/safing/portbase/log" + "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{}, +} + +func SetupBandwidthInterface() 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) + } + + // 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() + } + }() + + // Find the cgroup path + path, err := findCgroupPath() + if err != nil { + return fmt.Errorf("faield to find cgroup paths: %s", err) + } + + // Attach socket options for monitoring connections + ebpfInterface.sockOptionsLink, err = link.AttachCgroup(link.CgroupOptions{ + Path: path, + Program: ebpfInterface.objs.bpfPrograms.SocketOperations, + Attach: ebpf.AttachCGroupSockOps, + }) + if err != nil { + return fmt.Errorf("Failed to open module sockops: %s", err) + } + + // Attach Udp Ipv4 recive message tracing + ebpfInterface.udpv4RMLink, err = link.AttachTracing(link.TracingOptions{ + Program: ebpfInterface.objs.UdpRecvmsg, + }) + if err != nil { + return fmt.Errorf("Failed to open trace Udp IPv4 recvmsg: %s", err) + } + + // Attach UDP IPv4 send message tracing + ebpfInterface.udpv4SMLink, err = link.AttachTracing(link.TracingOptions{ + Program: ebpfInterface.objs.UdpSendmsg, + }) + if err != nil { + return fmt.Errorf("Failed to open trace Udp IPv4 sendmsg: %s", err) + } + + // Attach UDP IPv6 receive message tracing + ebpfInterface.udpv6RMLink, err = link.AttachTracing(link.TracingOptions{ + Program: ebpfInterface.objs.Udpv6Recvmsg, + }) + if err != nil { + return fmt.Errorf("Failed to open trace Udp IPv6 recvmsg: %s", err) + } + + // Attach UDP IPv6 send message tracing + ebpfInterface.udpv6RMLink, err = link.AttachTracing(link.TracingOptions{ + Program: ebpfInterface.objs.Udpv6Sendmsg, + }) + if err != nil { + return fmt.Errorf("Failed to open trace Udp IPv6 sendmsg: %s", err) + } + + // 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() + // } + // }() + + 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() + } + + // Close the udpv4SMLink if it is not nil + if ebpfInterface.udpv4SMLink != nil { + ebpfInterface.udpv4SMLink.Close() + } + + // 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() +} + +func findCgroupPath() (string, error) { + cgroupPath := "/sys/fs/cgroup" + + var st syscall.Statfs_t + err := syscall.Statfs(cgroupPath, &st) + if err != nil { + return "", err + } + isCgroupV2Enabled := st.Type == unix.CGROUP2_SUPER_MAGIC + if !isCgroupV2Enabled { + cgroupPath = filepath.Join(cgroupPath, "unified") + } + return cgroupPath, nil +} + +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, + arrayToIP(skKey.SrcIp, skKey.Ipv6).String(), skKey.SrcPort, + arrayToIP(skKey.DstIp, skKey.Ipv6).String(), skKey.DstPort, + skInfo.Rx, skInfo.Tx, + ) + } +} + +// arrayToIP converts IP number array to net.IP +func arrayToIP(ipNum [4]uint32, ipv6 uint8) net.IP { + if ipv6 == 0 { + return unsafe.Slice((*byte)(unsafe.Pointer(&ipNum)), 4) + } else { + return unsafe.Slice((*byte)(unsafe.Pointer(&ipNum)), 16) + } +} diff --git a/firewall/interception/ebpf/bpf_bpfeb.o b/firewall/interception/ebpf/bpf_bpfeb.o deleted file mode 100644 index 93a2bb8f..00000000 Binary files a/firewall/interception/ebpf/bpf_bpfeb.o and /dev/null differ diff --git a/firewall/interception/ebpf/bpf_bpfel.o b/firewall/interception/ebpf/bpf_bpfel.o deleted file mode 100644 index f5396ab0..00000000 Binary files a/firewall/interception/ebpf/bpf_bpfel.o and /dev/null differ diff --git a/firewall/interception/ebpf/bpf_bpfeb.go b/firewall/interception/ebpf/connection_listener/bpf_bpfeb.go similarity index 95% rename from firewall/interception/ebpf/bpf_bpfeb.go rename to firewall/interception/ebpf/connection_listener/bpf_bpfeb.go index be2d6d56..3376275e 100644 --- a/firewall/interception/ebpf/bpf_bpfeb.go +++ b/firewall/interception/ebpf/connection_listener/bpf_bpfeb.go @@ -75,7 +75,7 @@ type bpfProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type bpfMapSpecs struct { - Events *ebpf.MapSpec `ebpf:"events"` + PmConnectionEvents *ebpf.MapSpec `ebpf:"pm_connection_events"` } // bpfObjects contains all objects after they have been loaded into the kernel. @@ -97,12 +97,12 @@ func (o *bpfObjects) Close() error { // // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. type bpfMaps struct { - Events *ebpf.Map `ebpf:"events"` + PmConnectionEvents *ebpf.Map `ebpf:"pm_connection_events"` } func (m *bpfMaps) Close() error { return _BpfClose( - m.Events, + m.PmConnectionEvents, ) } diff --git a/firewall/interception/ebpf/connection_listener/bpf_bpfeb.o b/firewall/interception/ebpf/connection_listener/bpf_bpfeb.o new file mode 100644 index 00000000..bb16c6cf Binary files /dev/null and b/firewall/interception/ebpf/connection_listener/bpf_bpfeb.o differ diff --git a/firewall/interception/ebpf/bpf_bpfel.go b/firewall/interception/ebpf/connection_listener/bpf_bpfel.go similarity index 95% rename from firewall/interception/ebpf/bpf_bpfel.go rename to firewall/interception/ebpf/connection_listener/bpf_bpfel.go index a72d9e29..8a0ab7be 100644 --- a/firewall/interception/ebpf/bpf_bpfel.go +++ b/firewall/interception/ebpf/connection_listener/bpf_bpfel.go @@ -75,7 +75,7 @@ type bpfProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type bpfMapSpecs struct { - Events *ebpf.MapSpec `ebpf:"events"` + PmConnectionEvents *ebpf.MapSpec `ebpf:"pm_connection_events"` } // bpfObjects contains all objects after they have been loaded into the kernel. @@ -97,12 +97,12 @@ func (o *bpfObjects) Close() error { // // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. type bpfMaps struct { - Events *ebpf.Map `ebpf:"events"` + PmConnectionEvents *ebpf.Map `ebpf:"pm_connection_events"` } func (m *bpfMaps) Close() error { return _BpfClose( - m.Events, + m.PmConnectionEvents, ) } diff --git a/firewall/interception/ebpf/packet.go b/firewall/interception/ebpf/connection_listener/packet.go similarity index 84% rename from firewall/interception/ebpf/packet.go rename to firewall/interception/ebpf/connection_listener/packet.go index 19895646..fffd5558 100644 --- a/firewall/interception/ebpf/packet.go +++ b/firewall/interception/ebpf/connection_listener/packet.go @@ -13,12 +13,6 @@ type infoPacket struct { pmpacket.Base } -// InfoOnly returns whether the packet is informational only and does not -// represent an actual packet. -func (pkt *infoPacket) InfoOnly() bool { - return true -} - // LoadPacketData does nothing on Linux, as data is always fully parsed. func (pkt *infoPacket) LoadPacketData() error { return fmt.Errorf("can't load data in info only packet") diff --git a/firewall/interception/ebpf/worker.go b/firewall/interception/ebpf/connection_listener/worker.go similarity index 84% rename from firewall/interception/ebpf/worker.go rename to firewall/interception/ebpf/connection_listener/worker.go index 2fdf50c2..ba3d9022 100644 --- a/firewall/interception/ebpf/worker.go +++ b/firewall/interception/ebpf/connection_listener/worker.go @@ -5,21 +5,18 @@ import ( "encoding/binary" "errors" "net" - "time" "unsafe" "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/ringbuf" "github.com/cilium/ebpf/rlimit" - "github.com/safing/portbase/log" "github.com/safing/portmaster/network/packet" ) -//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -cflags "-O2 -g -Wall -Werror" -type Event bpf program/monitor.c +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -cflags "-O2 -g -Wall -Werror" -type Event bpf ../programs/monitor.c var stopper chan struct{} -// StartEBPFWorker starts the ebpf worker. func StartEBPFWorker(ch chan packet.Packet) { stopper = make(chan struct{}) go func() { @@ -35,7 +32,7 @@ func StartEBPFWorker(ch chan packet.Packet) { log.Errorf("ebpf: failed to load ebpf object: %s", err) return } - defer objs.Close() //nolint:errcheck + defer objs.Close() // Create a link to the tcp_connect program. linkTCPConnect, err := link.AttachTracing(link.TracingOptions{ @@ -45,7 +42,7 @@ func StartEBPFWorker(ch chan packet.Packet) { log.Errorf("ebpf: failed to attach to tcp_v4_connect: %s ", err) return } - defer linkTCPConnect.Close() //nolint:errcheck + defer linkTCPConnect.Close() // Create a link to the udp_v4_connect program. linkUDPV4, err := link.AttachTracing(link.TracingOptions{ @@ -55,7 +52,7 @@ func StartEBPFWorker(ch chan packet.Packet) { log.Errorf("ebpf: failed to attach to udp_v4_connect: %s ", err) return } - defer linkUDPV4.Close() //nolint:errcheck + defer linkUDPV4.Close() // Create a link to the udp_v6_connect program. linkUDPV6, err := link.AttachTracing(link.TracingOptions{ @@ -65,14 +62,14 @@ func StartEBPFWorker(ch chan packet.Packet) { log.Errorf("ebpf: failed to attach to udp_v6_connect: %s ", err) return } - defer linkUDPV6.Close() //nolint:errcheck + defer linkUDPV6.Close() - rd, err := ringbuf.NewReader(objs.bpfMaps.Events) + rd, err := ringbuf.NewReader(objs.bpfMaps.PmConnectionEvents) if err != nil { log.Errorf("ebpf: failed to open ring buffer: %s", err) return } - defer rd.Close() //nolint:errcheck + defer rd.Close() go func() { <-stopper @@ -111,7 +108,6 @@ func StartEBPFWorker(ch chan packet.Packet) { Src: arrayToIP(event.Saddr, packet.IPVersion(event.IpVersion)), Dst: arrayToIP(event.Daddr, packet.IPVersion(event.IpVersion)), PID: int(event.Pid), - SeenAt: time.Now(), } if isEventValid(event) { log.Debugf("ebpf: PID: %d conn: %s:%d -> %s:%d %s %s", info.PID, info.LocalIP(), info.LocalPort(), info.RemoteIP(), info.RemotePort(), info.Version.String(), info.Protocol.String()) @@ -127,7 +123,6 @@ func StartEBPFWorker(ch chan packet.Packet) { }() } -// StopEBPFWorker stops the ebpf worker. func StopEBPFWorker() { close(stopper) } @@ -153,12 +148,11 @@ func isEventValid(event bpfEvent) bool { return true } -// arrayToIP converts IP number array to net.IP. +// arrayToIP converts IP number array to net.IP func arrayToIP(ipNum [4]uint32, ipVersion packet.IPVersion) net.IP { if ipVersion == packet.IPv4 { - // FIXME: maybe convertIPv4 from windowskext package return unsafe.Slice((*byte)(unsafe.Pointer(&ipNum)), 4) + } else { + return unsafe.Slice((*byte)(unsafe.Pointer(&ipNum)), 16) } - // FIXME: maybe use convertIPv6 from windowskext package - return unsafe.Slice((*byte)(unsafe.Pointer(&ipNum)), 16) } diff --git a/firewall/interception/ebpf/programs/bandwidth.c b/firewall/interception/ebpf/programs/bandwidth.c new file mode 100644 index 00000000..8971de48 --- /dev/null +++ b/firewall/interception/ebpf/programs/bandwidth.c @@ -0,0 +1,204 @@ +#include "vmlinux-x86.h" +#include "bpf/bpf_helpers.h" +#include "bpf/bpf_tracing.h" +#include "bpf/bpf_core_read.h" + +#define AF_INET 2 +#define AF_INET6 10 + +#define PROTOCOL_TCP 6 +#define PROTOCOL_UDP 17 + +char __license[] SEC("license") = "Dual MIT/GPL"; + +struct sk_key { + u32 src_ip[4]; + u32 dst_ip[4]; + u16 src_port; + u16 dst_port; + u8 protocol; + u8 ipv6; +}; + +struct sk_info { + u64 rx; + u64 tx; +}; + +// Max number of connections that will be kept. Increse the number if it's not enough. +#define SOCKOPS_MAP_SIZE 5000 +struct { + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(max_entries, SOCKOPS_MAP_SIZE); + __type(key, struct sk_key); + __type(value, struct sk_info); +} pm_bandwidth_map SEC(".maps"); + +SEC("sockops") +int socket_operations(struct bpf_sock_ops *skops) { + switch (skops->op) { + case BPF_SOCK_OPS_TCP_CONNECT_CB: // Outgoing connections + // Set flag so any modification on the socket, will trigger this function. + bpf_sock_ops_cb_flags_set(skops, BPF_SOCK_OPS_ALL_CB_FLAGS); + return 0; + case BPF_SOCK_OPS_TCP_LISTEN_CB: // Listening ports + bpf_sock_ops_cb_flags_set(skops, BPF_SOCK_OPS_ALL_CB_FLAGS); + // No rx tx data for this socket object. + return 0; + case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB: // Incoming connections + // Set flag so any modification on the socket, will trigger this function. + bpf_sock_ops_cb_flags_set(skops, BPF_SOCK_OPS_ALL_CB_FLAGS); + return 0; + default: + break; + } + + struct bpf_sock *sk = skops->sk; + if (sk == NULL) { + return 0; + } + + struct sk_key key = {0}; + key.protocol = PROTOCOL_TCP; + if(sk->family == AF_INET) { + // Generate key for IPv4 + key.src_ip[0] = sk->src_ip4; + key.src_port = sk->src_port; + key.dst_ip[0] = sk->dst_ip4; + key.dst_port = __builtin_bswap16(sk->dst_port); + key.ipv6 = 0; + + 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); + } else if(sk->family == AF_INET6){ + // Generate key for IPv6 + for(int i = 0; i < 4; i++) { + key.src_ip[i] = sk->src_ip6[i]; + } + key.src_port = sk->src_port; + for(int i = 0; i < 4; i++) { + key.dst_ip[i] = sk->dst_ip6[i]; + } + key.dst_port = __builtin_bswap16(sk->dst_port); + key.ipv6 = 1; + } + + return 0; +} + +// udp_sendmsg hookes to the equvelent kernel function and saves the bandwoth 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; + + // Create a key for the map and set all the nececery information. + struct sk_key key = {0}; + key.protocol = PROTOCOL_UDP; + key.src_ip[0] = skc->skc_rcv_saddr; + key.dst_ip[0] = skc->skc_daddr; + key.src_port = skc->skc_num; + key.dst_port = __builtin_bswap16(skc->skc_dport); + key.ipv6 = 0; + + // 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); + } else { + struct sk_info newInfo = {0}; + + newInfo.tx = len; + bpf_map_update_elem(&pm_bandwidth_map, &key, &newInfo, BPF_ANY); + } + + return 0; +}; + +// udp_recvmsg hookes to the equvelent kernel function and saves the bandwoth 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; + + // Create a key for the map and set all the nececery information. + struct sk_key key = {0}; + key.protocol = PROTOCOL_UDP; + key.src_ip[0] = skc->skc_rcv_saddr; + key.dst_ip[0] = skc->skc_daddr; + key.src_port = skc->skc_num; + key.dst_port = __builtin_bswap16(skc->skc_dport); + key.ipv6 = 0; + + // 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); + } else { + struct sk_info newInfo = {0}; + + newInfo.rx = len; + bpf_map_update_elem(&pm_bandwidth_map, &key, &newInfo, BPF_ANY); + } + + return 0; +}; + +// udpv6_sendmsg hookes to the equvelent kernel function and saves the bandwoth 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; + + // Create a key for the map and set all the nececery information. + struct sk_key key = {0}; + key.protocol = PROTOCOL_UDP; + for (int i = 0; i < 4; i++) { + key.src_ip[i] = skc->skc_v6_rcv_saddr.in6_u.u6_addr32[i]; + key.dst_ip[i] = skc->skc_v6_rcv_saddr.in6_u.u6_addr32[i]; + } + key.src_port = skc->skc_num; + key.dst_port = __builtin_bswap16(skc->skc_dport); + key.ipv6 = 1; + + // 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); + } else { + struct sk_info newInfo = {0}; + newInfo.tx = len; + bpf_map_update_elem(&pm_bandwidth_map, &key, &newInfo, BPF_ANY); + } + + return 0; +} + +// udpv6_recvmsg hookes to the equvelent kernel function and saves the bandwoth 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; + + // Create a key for the map and set all the nececery information. + struct sk_key key = {0}; + key.protocol = PROTOCOL_UDP; + for (int i = 0; i < 4; i++) { + key.src_ip[i] = skc->skc_v6_rcv_saddr.in6_u.u6_addr32[i]; + key.dst_ip[i] = skc->skc_v6_rcv_saddr.in6_u.u6_addr32[i]; + } + key.src_port = skc->skc_num; + key.dst_port = __builtin_bswap16(skc->skc_dport); + key.ipv6 = 1; + + // 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); + } else { + struct sk_info newInfo = {0}; + newInfo.rx = len; + bpf_map_update_elem(&pm_bandwidth_map, &key, &newInfo, BPF_ANY); + } + + return 0; +} \ No newline at end of file diff --git a/firewall/interception/ebpf/program/bpf/bpf_core_read.h b/firewall/interception/ebpf/programs/bpf/bpf_core_read.h similarity index 100% rename from firewall/interception/ebpf/program/bpf/bpf_core_read.h rename to firewall/interception/ebpf/programs/bpf/bpf_core_read.h diff --git a/firewall/interception/ebpf/program/bpf/bpf_helper_defs.h b/firewall/interception/ebpf/programs/bpf/bpf_helper_defs.h similarity index 100% rename from firewall/interception/ebpf/program/bpf/bpf_helper_defs.h rename to firewall/interception/ebpf/programs/bpf/bpf_helper_defs.h diff --git a/firewall/interception/ebpf/program/bpf/bpf_helpers.h b/firewall/interception/ebpf/programs/bpf/bpf_helpers.h similarity index 100% rename from firewall/interception/ebpf/program/bpf/bpf_helpers.h rename to firewall/interception/ebpf/programs/bpf/bpf_helpers.h diff --git a/firewall/interception/ebpf/program/bpf/bpf_tracing.h b/firewall/interception/ebpf/programs/bpf/bpf_tracing.h similarity index 100% rename from firewall/interception/ebpf/program/bpf/bpf_tracing.h rename to firewall/interception/ebpf/programs/bpf/bpf_tracing.h diff --git a/firewall/interception/ebpf/program/monitor.c b/firewall/interception/ebpf/programs/monitor.c similarity index 94% rename from firewall/interception/ebpf/program/monitor.c rename to firewall/interception/ebpf/programs/monitor.c index 9ba6a309..e82c6756 100644 --- a/firewall/interception/ebpf/program/monitor.c +++ b/firewall/interception/ebpf/programs/monitor.c @@ -20,7 +20,7 @@ char __license[] SEC("license") = "GPL"; struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 1 << 24); -} events SEC(".maps"); +} pm_connection_events SEC(".maps"); // Event struct that will be sent to Go on each new connection. (The name should be the same as the go generate command) struct Event { @@ -41,7 +41,7 @@ SEC("fentry/tcp_connect") int BPF_PROG(tcp_connect, struct sock *sk) { // Alloc space for the event struct Event *tcp_info; - tcp_info = bpf_ringbuf_reserve(&events, sizeof(struct Event), 0); + tcp_info = bpf_ringbuf_reserve(&pm_connection_events, sizeof(struct Event), 0); if (!tcp_info) { return 0; } @@ -97,7 +97,7 @@ int BPF_PROG(udp_v4_connect, struct sock *sk) { // Allocate space for the event. struct Event *udp_info; - udp_info = bpf_ringbuf_reserve(&events, sizeof(struct Event), 0); + udp_info = bpf_ringbuf_reserve(&pm_connection_events, sizeof(struct Event), 0); if (!udp_info) { return 0; } @@ -146,7 +146,7 @@ int BPF_PROG(udp_v6_connect, struct sock *sk) { // Allocate space for the event. struct Event *udp_info; - udp_info = bpf_ringbuf_reserve(&events, sizeof(struct Event), 0); + udp_info = bpf_ringbuf_reserve(&pm_connection_events, sizeof(struct Event), 0); if (!udp_info) { return 0; } diff --git a/firewall/interception/ebpf/program/update.sh b/firewall/interception/ebpf/programs/update.sh similarity index 100% rename from firewall/interception/ebpf/program/update.sh rename to firewall/interception/ebpf/programs/update.sh diff --git a/firewall/interception/ebpf/program/vmlinux-x86.h b/firewall/interception/ebpf/programs/vmlinux-x86.h similarity index 100% rename from firewall/interception/ebpf/program/vmlinux-x86.h rename to firewall/interception/ebpf/programs/vmlinux-x86.h diff --git a/firewall/interception/interception_linux.go b/firewall/interception/interception_linux.go index 01aa3398..b657c080 100644 --- a/firewall/interception/interception_linux.go +++ b/firewall/interception/interception_linux.go @@ -1,7 +1,8 @@ package interception import ( - "github.com/safing/portmaster/firewall/interception/ebpf" + // bandwidth "github.com/safing/portmaster/firewall/interception/ebpf/bandwidth" + conn_listener "github.com/safing/portmaster/firewall/interception/ebpf/connection_listener" "github.com/safing/portmaster/firewall/interception/nfq" "github.com/safing/portmaster/network" "github.com/safing/portmaster/network/packet" @@ -9,13 +10,19 @@ import ( // start starts the interception. func start(ch chan packet.Packet) error { - ebpf.StartEBPFWorker(ch) + // Start ebpf new connection listener + conn_listener.StartEBPFWorker(ch) + // Start ebpf bandwidth listener + // bandwidth.SetupBandwidthInterface() return StartNfqueueInterception(ch) } // stop starts the interception. func stop() error { - ebpf.StopEBPFWorker() + // Stop ebpf connection listener + conn_listener.StopEBPFWorker() + // Stop ebpf bandwidth listener + // bandwidth.ShutdownBandwithInterface() return StopNfqueueInterception() }