Merge pull request #1234 from safing/fix/process-detection

Improve process detection
This commit is contained in:
Daniel Hovie
2023-07-20 15:06:13 +02:00
committed by GitHub
57 changed files with 129525 additions and 631 deletions

View File

@@ -7,6 +7,7 @@ linters:
- containedctx
- contextcheck
- cyclop
- depguard
- exhaustivestruct
- exhaustruct
- forbidigo

View File

@@ -104,6 +104,7 @@ func apiAuthenticator(r *http.Request, s *http.Server) (token *api.AuthToken, er
SrcPort: remotePort, // source as in the process we are looking for
Dst: localIP,
DstPort: localPort,
PID: process.UndefinedProcessID,
},
)
if !retry {
@@ -141,7 +142,11 @@ func authenticateAPIRequest(ctx context.Context, pktInfo *packet.Info) (retry bo
authenticatedPath += string(filepath.Separator)
// Get process of request.
proc, _, err := process.GetProcessByConnection(ctx, pktInfo)
pid, _, _ := process.GetPidOfConnection(ctx, pktInfo)
if pid < 0 {
return false, fmt.Errorf(deniedMsgUnidentified, api.ErrAPIAccessDeniedMessage) //nolint:stylecheck // message for user
}
proc, err := process.GetOrFindProcess(ctx, pid)
if err != nil {
log.Tracer(ctx).Debugf("filter: failed to get process of api request: %s", err)
originalPid = process.UnidentifiedProcessID

View File

@@ -1,33 +0,0 @@
package firewall
import (
"github.com/safing/portbase/config"
"github.com/safing/portbase/modules"
"github.com/safing/portbase/modules/subsystems"
_ "github.com/safing/portmaster/core"
)
var filterModule *modules.Module
func init() {
filterModule = modules.Register("filter", nil, nil, nil, "core", "interception", "intel")
subsystems.Register(
"filter",
"Privacy Filter",
"DNS and Network Filter",
filterModule,
"config:filter/",
&config.Option{
Name: "Privacy Filter Module",
Key: CfgOptionEnableFilterKey,
Description: "Start the Privacy Filter module. If turned off, all privacy filter protections are fully disabled on this device.",
OptType: config.OptTypeBool,
ExpertiseLevel: config.ExpertiseLevelDeveloper,
ReleaseLevel: config.ReleaseLevelStable,
DefaultValue: true,
Annotations: config.Annotations{
config.CategoryAnnotation: "General",
},
},
)
}

View File

@@ -0,0 +1,148 @@
// 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
Reported 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

Binary file not shown.

View File

@@ -0,0 +1,148 @@
// 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
Reported 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

Binary file not shown.

View File

@@ -0,0 +1,178 @@
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"
)
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -cflags "-O2 -g -Wall -Werror" bpf ../programs/bandwidth.c
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("ebpf: failed to remove memlock: %w", err)
}
// Load pre-compiled programs and maps into the kernel.
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("ebpf: failed to find cgroup paths: %w", err)
}
// Attach socket options for monitoring connections
sockOptionsLink, err := link.AttachCgroup(link.CgroupOptions{
Path: path,
Program: objs.bpfPrograms.SocketOperations,
Attach: ebpf.AttachCGroupSockOps,
})
if err != nil {
return fmt.Errorf("ebpf: failed to open module sockops: %w", err)
}
defer sockOptionsLink.Close() //nolint:errcheck
// Attach Udp Ipv4 recive message tracing
udpv4RMLink, err := link.AttachTracing(link.TracingOptions{
Program: objs.bpfPrograms.UdpRecvmsg,
})
if err != nil {
return fmt.Errorf("ebpf: failed to open trace Udp IPv4 recvmsg: %w", err)
}
defer udpv4RMLink.Close() //nolint:errcheck
// Attach UDP IPv4 send message tracing
udpv4SMLink, err := link.AttachTracing(link.TracingOptions{
Program: objs.bpfPrograms.UdpSendmsg,
})
if err != nil {
return fmt.Errorf("ebpf: failed to open trace Udp IPv4 sendmsg: %w", err)
}
defer udpv4SMLink.Close() //nolint:errcheck
// Attach UDP IPv6 receive message tracing
udpv6RMLink, err := link.AttachTracing(link.TracingOptions{
Program: objs.bpfPrograms.Udpv6Recvmsg,
})
if err != nil {
return fmt.Errorf("ebpf: failed to open trace Udp IPv6 recvmsg: %w", err)
}
defer udpv6RMLink.Close() //nolint:errcheck
// Attach UDP IPv6 send message tracing
udpv6SMLink, err := link.AttachTracing(link.TracingOptions{
Program: objs.bpfPrograms.Udpv6Sendmsg,
})
if err != nil {
return fmt.Errorf("ebpf: failed to open trace Udp IPv6 sendmsg: %w", err)
}
defer udpv6SMLink.Close() //nolint:errcheck
// Setup ticker.
ticker := time.NewTicker(collectInterval)
defer ticker.Stop()
// Collect bandwidth at every tick.
for {
select {
case <-ticker.C:
reportBandwidth(ctx, objs, bandwidthUpdates)
case <-ctx.Done():
return nil
}
}
}
// 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)
}
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
}
}
}
// findCgroupPath returns the default unified path of the cgroup.
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
}
// 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)
}
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)
}

View File

@@ -0,0 +1,138 @@
// 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 bpfEvent struct {
Saddr [4]uint32
Daddr [4]uint32
Sport uint16
Dport uint16
Pid uint32
IpVersion uint8
Protocol uint8
Direction uint8
_ [1]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 {
TcpConnect *ebpf.ProgramSpec `ebpf:"tcp_connect"`
UdpV4Connect *ebpf.ProgramSpec `ebpf:"udp_v4_connect"`
UdpV6Connect *ebpf.ProgramSpec `ebpf:"udp_v6_connect"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
PmConnectionEvents *ebpf.MapSpec `ebpf:"pm_connection_events"`
}
// 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 {
PmConnectionEvents *ebpf.Map `ebpf:"pm_connection_events"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.PmConnectionEvents,
)
}
// 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 {
TcpConnect *ebpf.Program `ebpf:"tcp_connect"`
UdpV4Connect *ebpf.Program `ebpf:"udp_v4_connect"`
UdpV6Connect *ebpf.Program `ebpf:"udp_v6_connect"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.TcpConnect,
p.UdpV4Connect,
p.UdpV6Connect,
)
}
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

View File

@@ -0,0 +1,138 @@
// 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 bpfEvent struct {
Saddr [4]uint32
Daddr [4]uint32
Sport uint16
Dport uint16
Pid uint32
IpVersion uint8
Protocol uint8
Direction uint8
_ [1]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 {
TcpConnect *ebpf.ProgramSpec `ebpf:"tcp_connect"`
UdpV4Connect *ebpf.ProgramSpec `ebpf:"udp_v4_connect"`
UdpV6Connect *ebpf.ProgramSpec `ebpf:"udp_v6_connect"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
PmConnectionEvents *ebpf.MapSpec `ebpf:"pm_connection_events"`
}
// 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 {
PmConnectionEvents *ebpf.Map `ebpf:"pm_connection_events"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.PmConnectionEvents,
)
}
// 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 {
TcpConnect *ebpf.Program `ebpf:"tcp_connect"`
UdpV4Connect *ebpf.Program `ebpf:"udp_v4_connect"`
UdpV6Connect *ebpf.Program `ebpf:"udp_v6_connect"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.TcpConnect,
p.UdpV4Connect,
p.UdpV6Connect,
)
}
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

View File

@@ -0,0 +1,169 @@
package ebpf
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"net"
"sync/atomic"
"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 ../programs/monitor.c
var ebpfLoadingFailed atomic.Uint32
// ConnectionListenerWorker listens to new connections using ebpf.
func ConnectionListenerWorker(ctx context.Context, packets chan packet.Packet) error {
// Allow the current process to lock memory for eBPF resources.
if err := rlimit.RemoveMemlock(); err != nil {
return fmt.Errorf("ebpf: failed to remove ebpf memlock: %w", err)
}
// Load pre-compiled programs and maps into the kernel.
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
// Create a link to the tcp_connect program.
linkTCPConnect, err := link.AttachTracing(link.TracingOptions{
Program: objs.bpfPrograms.TcpConnect,
})
if err != nil {
return fmt.Errorf("ebpf: failed to attach to tcp_v4_connect: %w", err)
}
defer linkTCPConnect.Close() //nolint:errcheck
// Create a link to the udp_v4_connect program.
linkUDPV4, err := link.AttachTracing(link.TracingOptions{
Program: objs.bpfPrograms.UdpV4Connect,
})
if err != nil {
return fmt.Errorf("ebpf: failed to attach to udp_v4_connect: %w", err)
}
defer linkUDPV4.Close() //nolint:errcheck
// Create a link to the udp_v6_connect program.
linkUDPV6, err := link.AttachTracing(link.TracingOptions{
Program: objs.bpfPrograms.UdpV6Connect,
})
if err != nil {
return fmt.Errorf("ebpf: failed to attach to udp_v6_connect: %w", err)
}
defer linkUDPV6.Close() //nolint:errcheck
// Create new reader to read events.
rd, err := ringbuf.NewReader(objs.bpfMaps.PmConnectionEvents)
if err != nil {
return fmt.Errorf("ebpf: failed to open ring buffer: %w", err)
}
defer rd.Close() //nolint:errcheck
// Start watcher to close the reader when the context is canceled.
// TODO: Can we put this into a worker?
go func() {
<-ctx.Done()
if err := rd.Close(); err != nil {
log.Errorf("ebpf: failed closing ringbuf reader: %s", err)
}
}()
for {
// Read next event
record, err := rd.Read()
if err != nil {
if errors.Is(err, ringbuf.ErrClosed) {
// Normal return
return nil
}
log.Errorf("ebpf: failed to read from ring buffer: %s", err)
continue
}
var event bpfEvent
// Parse the ringbuf event entry into a bpfEvent structure.
if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.BigEndian, &event); err != nil {
log.Errorf("ebpf: failed to parse ringbuf event: %s", err)
continue
}
pkt := packet.NewInfoPacket(packet.Info{
Inbound: event.Direction == 1,
InTunnel: false,
Version: packet.IPVersion(event.IpVersion),
Protocol: packet.IPProtocol(event.Protocol),
SrcPort: event.Sport,
DstPort: event.Dport,
Src: convertArrayToIPv4(event.Saddr, packet.IPVersion(event.IpVersion)),
Dst: convertArrayToIPv4(event.Daddr, packet.IPVersion(event.IpVersion)),
PID: int(event.Pid),
})
if isEventValid(event) {
log.Debugf("ebpf: received valid connect event: PID: %d Conn: %s", pkt.Info().PID, pkt)
packets <- pkt
} else {
log.Warningf("ebpf: received invalid connect event: PID: %d Conn: %s", pkt.Info().PID, pkt)
}
}
}
// isEventValid checks whether the given bpfEvent is valid or not.
// It returns true if the event is valid, otherwise false.
func isEventValid(event bpfEvent) bool {
// Check if the destination port is 0
if event.Dport == 0 {
return false
}
// Check if the source port is 0
if event.Sport == 0 {
return false
}
// Check if the process ID is 0
if event.Pid == 0 {
return false
}
// If the IP version is IPv4
if event.IpVersion == 4 {
if event.Saddr[0] == 0 {
return false
}
if event.Daddr[0] == 0 {
return false
}
}
return true
}
// 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 {
addressBuf := make([]byte, 4)
binary.LittleEndian.PutUint32(addressBuf, input[0])
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)
}

View File

@@ -0,0 +1,218 @@
#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") = "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;
u64 reported;
};
// 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;
// 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 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;
// 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); // TODO: Use atomic instead.
__sync_fetch_and_and(&info->reported, 0); // TODO: Use atomic instead.
} 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 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;
// 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); // TODO: Use atomic instead.
__sync_fetch_and_and(&info->reported, 0); // TODO: Use atomic instead.
} 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 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;
// 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); // TODO: Use atomic instead.
__sync_fetch_and_and(&info->reported, 0); // TODO: Use atomic instead.
} 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 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;
// 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); // TODO: Use atomic instead.
__sync_fetch_and_and(&info->reported, 0); // TODO: Use atomic instead.
} else {
struct sk_info newInfo = {0};
newInfo.rx = len;
bpf_map_update_elem(&pm_bandwidth_map, &key, &newInfo, BPF_ANY);
}
return 0;
}

View File

@@ -0,0 +1,484 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_CORE_READ_H__
#define __BPF_CORE_READ_H__
/*
* enum bpf_field_info_kind is passed as a second argument into
* __builtin_preserve_field_info() built-in to get a specific aspect of
* a field, captured as a first argument. __builtin_preserve_field_info(field,
* info_kind) returns __u32 integer and produces BTF field relocation, which
* is understood and processed by libbpf during BPF object loading. See
* selftests/bpf for examples.
*/
enum bpf_field_info_kind {
BPF_FIELD_BYTE_OFFSET = 0, /* field byte offset */
BPF_FIELD_BYTE_SIZE = 1,
BPF_FIELD_EXISTS = 2, /* field existence in target kernel */
BPF_FIELD_SIGNED = 3,
BPF_FIELD_LSHIFT_U64 = 4,
BPF_FIELD_RSHIFT_U64 = 5,
};
/* second argument to __builtin_btf_type_id() built-in */
enum bpf_type_id_kind {
BPF_TYPE_ID_LOCAL = 0, /* BTF type ID in local program */
BPF_TYPE_ID_TARGET = 1, /* BTF type ID in target kernel */
};
/* second argument to __builtin_preserve_type_info() built-in */
enum bpf_type_info_kind {
BPF_TYPE_EXISTS = 0, /* type existence in target kernel */
BPF_TYPE_SIZE = 1, /* type size in target kernel */
BPF_TYPE_MATCHES = 2, /* type match in target kernel */
};
/* second argument to __builtin_preserve_enum_value() built-in */
enum bpf_enum_value_kind {
BPF_ENUMVAL_EXISTS = 0, /* enum value existence in kernel */
BPF_ENUMVAL_VALUE = 1, /* enum value value relocation */
};
#define __CORE_RELO(src, field, info) \
__builtin_preserve_field_info((src)->field, BPF_FIELD_##info)
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define __CORE_BITFIELD_PROBE_READ(dst, src, fld) \
bpf_probe_read_kernel( \
(void *)dst, \
__CORE_RELO(src, fld, BYTE_SIZE), \
(const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
#else
/* semantics of LSHIFT_64 assumes loading values into low-ordered bytes, so
* for big-endian we need to adjust destination pointer accordingly, based on
* field byte size
*/
#define __CORE_BITFIELD_PROBE_READ(dst, src, fld) \
bpf_probe_read_kernel( \
(void *)dst + (8 - __CORE_RELO(src, fld, BYTE_SIZE)), \
__CORE_RELO(src, fld, BYTE_SIZE), \
(const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
#endif
/*
* Extract bitfield, identified by s->field, and return its value as u64.
* All this is done in relocatable manner, so bitfield changes such as
* signedness, bit size, offset changes, this will be handled automatically.
* This version of macro is using bpf_probe_read_kernel() to read underlying
* integer storage. Macro functions as an expression and its return type is
* bpf_probe_read_kernel()'s return value: 0, on success, <0 on error.
*/
#define BPF_CORE_READ_BITFIELD_PROBED(s, field) ({ \
unsigned long long val = 0; \
\
__CORE_BITFIELD_PROBE_READ(&val, s, field); \
val <<= __CORE_RELO(s, field, LSHIFT_U64); \
if (__CORE_RELO(s, field, SIGNED)) \
val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64); \
else \
val = val >> __CORE_RELO(s, field, RSHIFT_U64); \
val; \
})
/*
* Extract bitfield, identified by s->field, and return its value as u64.
* This version of macro is using direct memory reads and should be used from
* BPF program types that support such functionality (e.g., typed raw
* tracepoints).
*/
#define BPF_CORE_READ_BITFIELD(s, field) ({ \
const void *p = (const void *)s + __CORE_RELO(s, field, BYTE_OFFSET); \
unsigned long long val; \
\
/* This is a so-called barrier_var() operation that makes specified \
* variable "a black box" for optimizing compiler. \
* It forces compiler to perform BYTE_OFFSET relocation on p and use \
* its calculated value in the switch below, instead of applying \
* the same relocation 4 times for each individual memory load. \
*/ \
asm volatile("" : "=r"(p) : "0"(p)); \
\
switch (__CORE_RELO(s, field, BYTE_SIZE)) { \
case 1: val = *(const unsigned char *)p; break; \
case 2: val = *(const unsigned short *)p; break; \
case 4: val = *(const unsigned int *)p; break; \
case 8: val = *(const unsigned long long *)p; break; \
} \
val <<= __CORE_RELO(s, field, LSHIFT_U64); \
if (__CORE_RELO(s, field, SIGNED)) \
val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64); \
else \
val = val >> __CORE_RELO(s, field, RSHIFT_U64); \
val; \
})
#define ___bpf_field_ref1(field) (field)
#define ___bpf_field_ref2(type, field) (((typeof(type) *)0)->field)
#define ___bpf_field_ref(args...) \
___bpf_apply(___bpf_field_ref, ___bpf_narg(args))(args)
/*
* Convenience macro to check that field actually exists in target kernel's.
* Returns:
* 1, if matching field is present in target kernel;
* 0, if no matching field found.
*
* Supports two forms:
* - field reference through variable access:
* bpf_core_field_exists(p->my_field);
* - field reference through type and field names:
* bpf_core_field_exists(struct my_type, my_field).
*/
#define bpf_core_field_exists(field...) \
__builtin_preserve_field_info(___bpf_field_ref(field), BPF_FIELD_EXISTS)
/*
* Convenience macro to get the byte size of a field. Works for integers,
* struct/unions, pointers, arrays, and enums.
*
* Supports two forms:
* - field reference through variable access:
* bpf_core_field_size(p->my_field);
* - field reference through type and field names:
* bpf_core_field_size(struct my_type, my_field).
*/
#define bpf_core_field_size(field...) \
__builtin_preserve_field_info(___bpf_field_ref(field), BPF_FIELD_BYTE_SIZE)
/*
* Convenience macro to get field's byte offset.
*
* Supports two forms:
* - field reference through variable access:
* bpf_core_field_offset(p->my_field);
* - field reference through type and field names:
* bpf_core_field_offset(struct my_type, my_field).
*/
#define bpf_core_field_offset(field...) \
__builtin_preserve_field_info(___bpf_field_ref(field), BPF_FIELD_BYTE_OFFSET)
/*
* Convenience macro to get BTF type ID of a specified type, using a local BTF
* information. Return 32-bit unsigned integer with type ID from program's own
* BTF. Always succeeds.
*/
#define bpf_core_type_id_local(type) \
__builtin_btf_type_id(*(typeof(type) *)0, BPF_TYPE_ID_LOCAL)
/*
* Convenience macro to get BTF type ID of a target kernel's type that matches
* specified local type.
* Returns:
* - valid 32-bit unsigned type ID in kernel BTF;
* - 0, if no matching type was found in a target kernel BTF.
*/
#define bpf_core_type_id_kernel(type) \
__builtin_btf_type_id(*(typeof(type) *)0, BPF_TYPE_ID_TARGET)
/*
* Convenience macro to check that provided named type
* (struct/union/enum/typedef) exists in a target kernel.
* Returns:
* 1, if such type is present in target kernel's BTF;
* 0, if no matching type is found.
*/
#define bpf_core_type_exists(type) \
__builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_EXISTS)
/*
* Convenience macro to check that provided named type
* (struct/union/enum/typedef) "matches" that in a target kernel.
* Returns:
* 1, if the type matches in the target kernel's BTF;
* 0, if the type does not match any in the target kernel
*/
#define bpf_core_type_matches(type) \
__builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_MATCHES)
/*
* Convenience macro to get the byte size of a provided named type
* (struct/union/enum/typedef) in a target kernel.
* Returns:
* >= 0 size (in bytes), if type is present in target kernel's BTF;
* 0, if no matching type is found.
*/
#define bpf_core_type_size(type) \
__builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_SIZE)
/*
* Convenience macro to check that provided enumerator value is defined in
* a target kernel.
* Returns:
* 1, if specified enum type and its enumerator value are present in target
* kernel's BTF;
* 0, if no matching enum and/or enum value within that enum is found.
*/
#define bpf_core_enum_value_exists(enum_type, enum_value) \
__builtin_preserve_enum_value(*(typeof(enum_type) *)enum_value, BPF_ENUMVAL_EXISTS)
/*
* Convenience macro to get the integer value of an enumerator value in
* a target kernel.
* Returns:
* 64-bit value, if specified enum type and its enumerator value are
* present in target kernel's BTF;
* 0, if no matching enum and/or enum value within that enum is found.
*/
#define bpf_core_enum_value(enum_type, enum_value) \
__builtin_preserve_enum_value(*(typeof(enum_type) *)enum_value, BPF_ENUMVAL_VALUE)
/*
* bpf_core_read() abstracts away bpf_probe_read_kernel() call and captures
* offset relocation for source address using __builtin_preserve_access_index()
* built-in, provided by Clang.
*
* __builtin_preserve_access_index() takes as an argument an expression of
* taking an address of a field within struct/union. It makes compiler emit
* a relocation, which records BTF type ID describing root struct/union and an
* accessor string which describes exact embedded field that was used to take
* an address. See detailed description of this relocation format and
* semantics in comments to struct bpf_field_reloc in libbpf_internal.h.
*
* This relocation allows libbpf to adjust BPF instruction to use correct
* actual field offset, based on target kernel BTF type that matches original
* (local) BTF, used to record relocation.
*/
#define bpf_core_read(dst, sz, src) \
bpf_probe_read_kernel(dst, sz, (const void *)__builtin_preserve_access_index(src))
/* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use. */
#define bpf_core_read_user(dst, sz, src) \
bpf_probe_read_user(dst, sz, (const void *)__builtin_preserve_access_index(src))
/*
* bpf_core_read_str() is a thin wrapper around bpf_probe_read_str()
* additionally emitting BPF CO-RE field relocation for specified source
* argument.
*/
#define bpf_core_read_str(dst, sz, src) \
bpf_probe_read_kernel_str(dst, sz, (const void *)__builtin_preserve_access_index(src))
/* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use. */
#define bpf_core_read_user_str(dst, sz, src) \
bpf_probe_read_user_str(dst, sz, (const void *)__builtin_preserve_access_index(src))
#define ___concat(a, b) a ## b
#define ___apply(fn, n) ___concat(fn, n)
#define ___nth(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, __11, N, ...) N
/*
* return number of provided arguments; used for switch-based variadic macro
* definitions (see ___last, ___arrow, etc below)
*/
#define ___narg(...) ___nth(_, ##__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
/*
* return 0 if no arguments are passed, N - otherwise; used for
* recursively-defined macros to specify termination (0) case, and generic
* (N) case (e.g., ___read_ptrs, ___core_read)
*/
#define ___empty(...) ___nth(_, ##__VA_ARGS__, N, N, N, N, N, N, N, N, N, N, 0)
#define ___last1(x) x
#define ___last2(a, x) x
#define ___last3(a, b, x) x
#define ___last4(a, b, c, x) x
#define ___last5(a, b, c, d, x) x
#define ___last6(a, b, c, d, e, x) x
#define ___last7(a, b, c, d, e, f, x) x
#define ___last8(a, b, c, d, e, f, g, x) x
#define ___last9(a, b, c, d, e, f, g, h, x) x
#define ___last10(a, b, c, d, e, f, g, h, i, x) x
#define ___last(...) ___apply(___last, ___narg(__VA_ARGS__))(__VA_ARGS__)
#define ___nolast2(a, _) a
#define ___nolast3(a, b, _) a, b
#define ___nolast4(a, b, c, _) a, b, c
#define ___nolast5(a, b, c, d, _) a, b, c, d
#define ___nolast6(a, b, c, d, e, _) a, b, c, d, e
#define ___nolast7(a, b, c, d, e, f, _) a, b, c, d, e, f
#define ___nolast8(a, b, c, d, e, f, g, _) a, b, c, d, e, f, g
#define ___nolast9(a, b, c, d, e, f, g, h, _) a, b, c, d, e, f, g, h
#define ___nolast10(a, b, c, d, e, f, g, h, i, _) a, b, c, d, e, f, g, h, i
#define ___nolast(...) ___apply(___nolast, ___narg(__VA_ARGS__))(__VA_ARGS__)
#define ___arrow1(a) a
#define ___arrow2(a, b) a->b
#define ___arrow3(a, b, c) a->b->c
#define ___arrow4(a, b, c, d) a->b->c->d
#define ___arrow5(a, b, c, d, e) a->b->c->d->e
#define ___arrow6(a, b, c, d, e, f) a->b->c->d->e->f
#define ___arrow7(a, b, c, d, e, f, g) a->b->c->d->e->f->g
#define ___arrow8(a, b, c, d, e, f, g, h) a->b->c->d->e->f->g->h
#define ___arrow9(a, b, c, d, e, f, g, h, i) a->b->c->d->e->f->g->h->i
#define ___arrow10(a, b, c, d, e, f, g, h, i, j) a->b->c->d->e->f->g->h->i->j
#define ___arrow(...) ___apply(___arrow, ___narg(__VA_ARGS__))(__VA_ARGS__)
#define ___type(...) typeof(___arrow(__VA_ARGS__))
#define ___read(read_fn, dst, src_type, src, accessor) \
read_fn((void *)(dst), sizeof(*(dst)), &((src_type)(src))->accessor)
/* "recursively" read a sequence of inner pointers using local __t var */
#define ___rd_first(fn, src, a) ___read(fn, &__t, ___type(src), src, a);
#define ___rd_last(fn, ...) \
___read(fn, &__t, ___type(___nolast(__VA_ARGS__)), __t, ___last(__VA_ARGS__));
#define ___rd_p1(fn, ...) const void *__t; ___rd_first(fn, __VA_ARGS__)
#define ___rd_p2(fn, ...) ___rd_p1(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p3(fn, ...) ___rd_p2(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p4(fn, ...) ___rd_p3(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p5(fn, ...) ___rd_p4(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p6(fn, ...) ___rd_p5(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p7(fn, ...) ___rd_p6(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p8(fn, ...) ___rd_p7(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___rd_p9(fn, ...) ___rd_p8(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
#define ___read_ptrs(fn, src, ...) \
___apply(___rd_p, ___narg(__VA_ARGS__))(fn, src, __VA_ARGS__)
#define ___core_read0(fn, fn_ptr, dst, src, a) \
___read(fn, dst, ___type(src), src, a);
#define ___core_readN(fn, fn_ptr, dst, src, ...) \
___read_ptrs(fn_ptr, src, ___nolast(__VA_ARGS__)) \
___read(fn, dst, ___type(src, ___nolast(__VA_ARGS__)), __t, \
___last(__VA_ARGS__));
#define ___core_read(fn, fn_ptr, dst, src, a, ...) \
___apply(___core_read, ___empty(__VA_ARGS__))(fn, fn_ptr, dst, \
src, a, ##__VA_ARGS__)
/*
* BPF_CORE_READ_INTO() is a more performance-conscious variant of
* BPF_CORE_READ(), in which final field is read into user-provided storage.
* See BPF_CORE_READ() below for more details on general usage.
*/
#define BPF_CORE_READ_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read, bpf_core_read, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* Variant of BPF_CORE_READ_INTO() for reading from user-space memory.
*
* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use.
*/
#define BPF_CORE_READ_USER_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read_user, bpf_core_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/* Non-CO-RE variant of BPF_CORE_READ_INTO() */
#define BPF_PROBE_READ_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read_kernel, bpf_probe_read_kernel, \
dst, (src), a, ##__VA_ARGS__) \
})
/* Non-CO-RE variant of BPF_CORE_READ_USER_INTO().
*
* As no CO-RE relocations are emitted, source types can be arbitrary and are
* not restricted to kernel types only.
*/
#define BPF_PROBE_READ_USER_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read_user, bpf_probe_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* BPF_CORE_READ_STR_INTO() does same "pointer chasing" as
* BPF_CORE_READ() for intermediate pointers, but then executes (and returns
* corresponding error code) bpf_core_read_str() for final string read.
*/
#define BPF_CORE_READ_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read_str, bpf_core_read, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* Variant of BPF_CORE_READ_STR_INTO() for reading from user-space memory.
*
* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use.
*/
#define BPF_CORE_READ_USER_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_core_read_user_str, bpf_core_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/* Non-CO-RE variant of BPF_CORE_READ_STR_INTO() */
#define BPF_PROBE_READ_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read_kernel_str, bpf_probe_read_kernel, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* Non-CO-RE variant of BPF_CORE_READ_USER_STR_INTO().
*
* As no CO-RE relocations are emitted, source types can be arbitrary and are
* not restricted to kernel types only.
*/
#define BPF_PROBE_READ_USER_STR_INTO(dst, src, a, ...) ({ \
___core_read(bpf_probe_read_user_str, bpf_probe_read_user, \
dst, (src), a, ##__VA_ARGS__) \
})
/*
* BPF_CORE_READ() is used to simplify BPF CO-RE relocatable read, especially
* when there are few pointer chasing steps.
* E.g., what in non-BPF world (or in BPF w/ BCC) would be something like:
* int x = s->a.b.c->d.e->f->g;
* can be succinctly achieved using BPF_CORE_READ as:
* int x = BPF_CORE_READ(s, a.b.c, d.e, f, g);
*
* BPF_CORE_READ will decompose above statement into 4 bpf_core_read (BPF
* CO-RE relocatable bpf_probe_read_kernel() wrapper) calls, logically
* equivalent to:
* 1. const void *__t = s->a.b.c;
* 2. __t = __t->d.e;
* 3. __t = __t->f;
* 4. return __t->g;
*
* Equivalence is logical, because there is a heavy type casting/preservation
* involved, as well as all the reads are happening through
* bpf_probe_read_kernel() calls using __builtin_preserve_access_index() to
* emit CO-RE relocations.
*
* N.B. Only up to 9 "field accessors" are supported, which should be more
* than enough for any practical purpose.
*/
#define BPF_CORE_READ(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_CORE_READ_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
/*
* Variant of BPF_CORE_READ() for reading from user-space memory.
*
* NOTE: all the source types involved are still *kernel types* and need to
* exist in kernel (or kernel module) BTF, otherwise CO-RE relocation will
* fail. Custom user types are not relocatable with CO-RE.
* The typical situation in which BPF_CORE_READ_USER() might be used is to
* read kernel UAPI types from the user-space memory passed in as a syscall
* input argument.
*/
#define BPF_CORE_READ_USER(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_CORE_READ_USER_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
/* Non-CO-RE variant of BPF_CORE_READ() */
#define BPF_PROBE_READ(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_PROBE_READ_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
/*
* Non-CO-RE variant of BPF_CORE_READ_USER().
*
* As no CO-RE relocations are emitted, source types can be arbitrary and are
* not restricted to kernel types only.
*/
#define BPF_PROBE_READ_USER(src, a, ...) ({ \
___type((src), a, ##__VA_ARGS__) __r; \
BPF_PROBE_READ_USER_INTO(&__r, (src), a, ##__VA_ARGS__); \
__r; \
})
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,397 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_HELPERS__
#define __BPF_HELPERS__
/*
* Note that bpf programs need to include either
* vmlinux.h (auto-generated from BTF) or linux/types.h
* in advance since bpf_helper_defs.h uses such types
* as __u64.
*/
#include "bpf_helper_defs.h"
#define __uint(name, val) int (*name)[val]
#define __type(name, val) typeof(val) *name
#define __array(name, val) typeof(val) *name[]
/*
* Helper macro to place programs, maps, license in
* different sections in elf_bpf file. Section names
* are interpreted by libbpf depending on the context (BPF programs, BPF maps,
* extern variables, etc).
* To allow use of SEC() with externs (e.g., for extern .maps declarations),
* make sure __attribute__((unused)) doesn't trigger compilation warning.
*/
#if __GNUC__ && !__clang__
/*
* Pragma macros are broken on GCC
* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55578
* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90400
*/
#define SEC(name) __attribute__((section(name), used))
#else
#define SEC(name) \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wignored-attributes\"") \
__attribute__((section(name), used)) \
_Pragma("GCC diagnostic pop") \
#endif
/* Avoid 'linux/stddef.h' definition of '__always_inline'. */
#undef __always_inline
#define __always_inline inline __attribute__((always_inline))
#ifndef __noinline
#define __noinline __attribute__((noinline))
#endif
#ifndef __weak
#define __weak __attribute__((weak))
#endif
/*
* Use __hidden attribute to mark a non-static BPF subprogram effectively
* static for BPF verifier's verification algorithm purposes, allowing more
* extensive and permissive BPF verification process, taking into account
* subprogram's caller context.
*/
#define __hidden __attribute__((visibility("hidden")))
/* When utilizing vmlinux.h with BPF CO-RE, user BPF programs can't include
* any system-level headers (such as stddef.h, linux/version.h, etc), and
* commonly-used macros like NULL and KERNEL_VERSION aren't available through
* vmlinux.h. This just adds unnecessary hurdles and forces users to re-define
* them on their own. So as a convenience, provide such definitions here.
*/
#ifndef NULL
#define NULL ((void *)0)
#endif
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + ((c) > 255 ? 255 : (c)))
#endif
/*
* Helper macros to manipulate data structures
*/
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((unsigned long)&((TYPE *)0)->MEMBER)
#endif
#ifndef container_of
#define container_of(ptr, type, member) \
({ \
void *__mptr = (void *)(ptr); \
((type *)(__mptr - offsetof(type, member))); \
})
#endif
/*
* Compiler (optimization) barrier.
*/
#ifndef barrier
#define barrier() asm volatile("" ::: "memory")
#endif
/* Variable-specific compiler (optimization) barrier. It's a no-op which makes
* compiler believe that there is some black box modification of a given
* variable and thus prevents compiler from making extra assumption about its
* value and potential simplifications and optimizations on this variable.
*
* E.g., compiler might often delay or even omit 32-bit to 64-bit casting of
* a variable, making some code patterns unverifiable. Putting barrier_var()
* in place will ensure that cast is performed before the barrier_var()
* invocation, because compiler has to pessimistically assume that embedded
* asm section might perform some extra operations on that variable.
*
* This is a variable-specific variant of more global barrier().
*/
#ifndef barrier_var
#define barrier_var(var) asm volatile("" : "+r"(var))
#endif
/*
* Helper macro to throw a compilation error if __bpf_unreachable() gets
* built into the resulting code. This works given BPF back end does not
* implement __builtin_trap(). This is useful to assert that certain paths
* of the program code are never used and hence eliminated by the compiler.
*
* For example, consider a switch statement that covers known cases used by
* the program. __bpf_unreachable() can then reside in the default case. If
* the program gets extended such that a case is not covered in the switch
* statement, then it will throw a build error due to the default case not
* being compiled out.
*/
#ifndef __bpf_unreachable
# define __bpf_unreachable() __builtin_trap()
#endif
/*
* Helper function to perform a tail call with a constant/immediate map slot.
*/
#if __clang_major__ >= 8 && defined(__bpf__)
static __always_inline void
bpf_tail_call_static(void *ctx, const void *map, const __u32 slot)
{
if (!__builtin_constant_p(slot))
__bpf_unreachable();
/*
* Provide a hard guarantee that LLVM won't optimize setting r2 (map
* pointer) and r3 (constant map index) from _different paths_ ending
* up at the _same_ call insn as otherwise we won't be able to use the
* jmpq/nopl retpoline-free patching by the x86-64 JIT in the kernel
* given they mismatch. See also d2e4c1e6c294 ("bpf: Constant map key
* tracking for prog array pokes") for details on verifier tracking.
*
* Note on clobber list: we need to stay in-line with BPF calling
* convention, so even if we don't end up using r0, r4, r5, we need
* to mark them as clobber so that LLVM doesn't end up using them
* before / after the call.
*/
asm volatile("r1 = %[ctx]\n\t"
"r2 = %[map]\n\t"
"r3 = %[slot]\n\t"
"call 12"
:: [ctx]"r"(ctx), [map]"r"(map), [slot]"i"(slot)
: "r0", "r1", "r2", "r3", "r4", "r5");
}
#endif
enum libbpf_pin_type {
LIBBPF_PIN_NONE,
/* PIN_BY_NAME: pin maps by name (in /sys/fs/bpf by default) */
LIBBPF_PIN_BY_NAME,
};
enum libbpf_tristate {
TRI_NO = 0,
TRI_YES = 1,
TRI_MODULE = 2,
};
#define __kconfig __attribute__((section(".kconfig")))
#define __ksym __attribute__((section(".ksyms")))
#define __kptr_untrusted __attribute__((btf_type_tag("kptr_untrusted")))
#define __kptr __attribute__((btf_type_tag("kptr")))
#define bpf_ksym_exists(sym) ({ \
_Static_assert(!__builtin_constant_p(!!sym), #sym " should be marked as __weak"); \
!!sym; \
})
#ifndef ___bpf_concat
#define ___bpf_concat(a, b) a ## b
#endif
#ifndef ___bpf_apply
#define ___bpf_apply(fn, n) ___bpf_concat(fn, n)
#endif
#ifndef ___bpf_nth
#define ___bpf_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, N, ...) N
#endif
#ifndef ___bpf_narg
#define ___bpf_narg(...) \
___bpf_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#endif
#define ___bpf_fill0(arr, p, x) do {} while (0)
#define ___bpf_fill1(arr, p, x) arr[p] = x
#define ___bpf_fill2(arr, p, x, args...) arr[p] = x; ___bpf_fill1(arr, p + 1, args)
#define ___bpf_fill3(arr, p, x, args...) arr[p] = x; ___bpf_fill2(arr, p + 1, args)
#define ___bpf_fill4(arr, p, x, args...) arr[p] = x; ___bpf_fill3(arr, p + 1, args)
#define ___bpf_fill5(arr, p, x, args...) arr[p] = x; ___bpf_fill4(arr, p + 1, args)
#define ___bpf_fill6(arr, p, x, args...) arr[p] = x; ___bpf_fill5(arr, p + 1, args)
#define ___bpf_fill7(arr, p, x, args...) arr[p] = x; ___bpf_fill6(arr, p + 1, args)
#define ___bpf_fill8(arr, p, x, args...) arr[p] = x; ___bpf_fill7(arr, p + 1, args)
#define ___bpf_fill9(arr, p, x, args...) arr[p] = x; ___bpf_fill8(arr, p + 1, args)
#define ___bpf_fill10(arr, p, x, args...) arr[p] = x; ___bpf_fill9(arr, p + 1, args)
#define ___bpf_fill11(arr, p, x, args...) arr[p] = x; ___bpf_fill10(arr, p + 1, args)
#define ___bpf_fill12(arr, p, x, args...) arr[p] = x; ___bpf_fill11(arr, p + 1, args)
#define ___bpf_fill(arr, args...) \
___bpf_apply(___bpf_fill, ___bpf_narg(args))(arr, 0, args)
/*
* BPF_SEQ_PRINTF to wrap bpf_seq_printf to-be-printed values
* in a structure.
*/
#define BPF_SEQ_PRINTF(seq, fmt, args...) \
({ \
static const char ___fmt[] = fmt; \
unsigned long long ___param[___bpf_narg(args)]; \
\
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
___bpf_fill(___param, args); \
_Pragma("GCC diagnostic pop") \
\
bpf_seq_printf(seq, ___fmt, sizeof(___fmt), \
___param, sizeof(___param)); \
})
/*
* BPF_SNPRINTF wraps the bpf_snprintf helper with variadic arguments instead of
* an array of u64.
*/
#define BPF_SNPRINTF(out, out_size, fmt, args...) \
({ \
static const char ___fmt[] = fmt; \
unsigned long long ___param[___bpf_narg(args)]; \
\
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
___bpf_fill(___param, args); \
_Pragma("GCC diagnostic pop") \
\
bpf_snprintf(out, out_size, ___fmt, \
___param, sizeof(___param)); \
})
#ifdef BPF_NO_GLOBAL_DATA
#define BPF_PRINTK_FMT_MOD
#else
#define BPF_PRINTK_FMT_MOD static const
#endif
#define __bpf_printk(fmt, ...) \
({ \
BPF_PRINTK_FMT_MOD char ____fmt[] = fmt; \
bpf_trace_printk(____fmt, sizeof(____fmt), \
##__VA_ARGS__); \
})
/*
* __bpf_vprintk wraps the bpf_trace_vprintk helper with variadic arguments
* instead of an array of u64.
*/
#define __bpf_vprintk(fmt, args...) \
({ \
static const char ___fmt[] = fmt; \
unsigned long long ___param[___bpf_narg(args)]; \
\
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
___bpf_fill(___param, args); \
_Pragma("GCC diagnostic pop") \
\
bpf_trace_vprintk(___fmt, sizeof(___fmt), \
___param, sizeof(___param)); \
})
/* Use __bpf_printk when bpf_printk call has 3 or fewer fmt args
* Otherwise use __bpf_vprintk
*/
#define ___bpf_pick_printk(...) \
___bpf_nth(_, ##__VA_ARGS__, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \
__bpf_vprintk, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \
__bpf_vprintk, __bpf_vprintk, __bpf_printk /*3*/, __bpf_printk /*2*/,\
__bpf_printk /*1*/, __bpf_printk /*0*/)
/* Helper macro to print out debug messages */
#define bpf_printk(fmt, args...) ___bpf_pick_printk(args)(fmt, ##args)
struct bpf_iter_num;
extern int bpf_iter_num_new(struct bpf_iter_num *it, int start, int end) __weak __ksym;
extern int *bpf_iter_num_next(struct bpf_iter_num *it) __weak __ksym;
extern void bpf_iter_num_destroy(struct bpf_iter_num *it) __weak __ksym;
#ifndef bpf_for_each
/* bpf_for_each(iter_type, cur_elem, args...) provides generic construct for
* using BPF open-coded iterators without having to write mundane explicit
* low-level loop logic. Instead, it provides for()-like generic construct
* that can be used pretty naturally. E.g., for some hypothetical cgroup
* iterator, you'd write:
*
* struct cgroup *cg, *parent_cg = <...>;
*
* bpf_for_each(cgroup, cg, parent_cg, CG_ITER_CHILDREN) {
* bpf_printk("Child cgroup id = %d", cg->cgroup_id);
* if (cg->cgroup_id == 123)
* break;
* }
*
* I.e., it looks almost like high-level for each loop in other languages,
* supports continue/break, and is verifiable by BPF verifier.
*
* For iterating integers, the difference betwen bpf_for_each(num, i, N, M)
* and bpf_for(i, N, M) is in that bpf_for() provides additional proof to
* verifier that i is in [N, M) range, and in bpf_for_each() case i is `int
* *`, not just `int`. So for integers bpf_for() is more convenient.
*
* Note: this macro relies on C99 feature of allowing to declare variables
* inside for() loop, bound to for() loop lifetime. It also utilizes GCC
* extension: __attribute__((cleanup(<func>))), supported by both GCC and
* Clang.
*/
#define bpf_for_each(type, cur, args...) for ( \
/* initialize and define destructor */ \
struct bpf_iter_##type ___it __attribute__((aligned(8), /* enforce, just in case */, \
cleanup(bpf_iter_##type##_destroy))), \
/* ___p pointer is just to call bpf_iter_##type##_new() *once* to init ___it */ \
*___p __attribute__((unused)) = ( \
bpf_iter_##type##_new(&___it, ##args), \
/* this is a workaround for Clang bug: it currently doesn't emit BTF */ \
/* for bpf_iter_##type##_destroy() when used from cleanup() attribute */ \
(void)bpf_iter_##type##_destroy, (void *)0); \
/* iteration and termination check */ \
(((cur) = bpf_iter_##type##_next(&___it))); \
)
#endif /* bpf_for_each */
#ifndef bpf_for
/* bpf_for(i, start, end) implements a for()-like looping construct that sets
* provided integer variable *i* to values starting from *start* through,
* but not including, *end*. It also proves to BPF verifier that *i* belongs
* to range [start, end), so this can be used for accessing arrays without
* extra checks.
*
* Note: *start* and *end* are assumed to be expressions with no side effects
* and whose values do not change throughout bpf_for() loop execution. They do
* not have to be statically known or constant, though.
*
* Note: similarly to bpf_for_each(), it relies on C99 feature of declaring for()
* loop bound variables and cleanup attribute, supported by GCC and Clang.
*/
#define bpf_for(i, start, end) for ( \
/* initialize and define destructor */ \
struct bpf_iter_num ___it __attribute__((aligned(8), /* enforce, just in case */ \
cleanup(bpf_iter_num_destroy))), \
/* ___p pointer is necessary to call bpf_iter_num_new() *once* to init ___it */ \
*___p __attribute__((unused)) = ( \
bpf_iter_num_new(&___it, (start), (end)), \
/* this is a workaround for Clang bug: it currently doesn't emit BTF */ \
/* for bpf_iter_num_destroy() when used from cleanup() attribute */ \
(void)bpf_iter_num_destroy, (void *)0); \
({ \
/* iteration step */ \
int *___t = bpf_iter_num_next(&___it); \
/* termination and bounds check */ \
(___t && ((i) = *___t, (i) >= (start) && (i) < (end))); \
}); \
)
#endif /* bpf_for */
#ifndef bpf_repeat
/* bpf_repeat(N) performs N iterations without exposing iteration number
*
* Note: similarly to bpf_for_each(), it relies on C99 feature of declaring for()
* loop bound variables and cleanup attribute, supported by GCC and Clang.
*/
#define bpf_repeat(N) for ( \
/* initialize and define destructor */ \
struct bpf_iter_num ___it __attribute__((aligned(8), /* enforce, just in case */ \
cleanup(bpf_iter_num_destroy))), \
/* ___p pointer is necessary to call bpf_iter_num_new() *once* to init ___it */ \
*___p __attribute__((unused)) = ( \
bpf_iter_num_new(&___it, 0, (N)), \
/* this is a workaround for Clang bug: it currently doesn't emit BTF */ \
/* for bpf_iter_num_destroy() when used from cleanup() attribute */ \
(void)bpf_iter_num_destroy, (void *)0); \
bpf_iter_num_next(&___it); \
/* nothing here */ \
)
#endif /* bpf_repeat */
#endif

View File

@@ -0,0 +1,924 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __BPF_TRACING_H__
#define __BPF_TRACING_H__
// #include <bpf/bpf_helpers.h>
#include "bpf_helpers.h"
/* Scan the ARCH passed in from ARCH env variable (see Makefile) */
#if defined(__TARGET_ARCH_x86)
#define bpf_target_x86
#define bpf_target_defined
#elif defined(__TARGET_ARCH_s390)
#define bpf_target_s390
#define bpf_target_defined
#elif defined(__TARGET_ARCH_arm)
#define bpf_target_arm
#define bpf_target_defined
#elif defined(__TARGET_ARCH_arm64)
#define bpf_target_arm64
#define bpf_target_defined
#elif defined(__TARGET_ARCH_mips)
#define bpf_target_mips
#define bpf_target_defined
#elif defined(__TARGET_ARCH_powerpc)
#define bpf_target_powerpc
#define bpf_target_defined
#elif defined(__TARGET_ARCH_sparc)
#define bpf_target_sparc
#define bpf_target_defined
#elif defined(__TARGET_ARCH_riscv)
#define bpf_target_riscv
#define bpf_target_defined
#elif defined(__TARGET_ARCH_arc)
#define bpf_target_arc
#define bpf_target_defined
#elif defined(__TARGET_ARCH_loongarch)
#define bpf_target_loongarch
#define bpf_target_defined
#else
/* Fall back to what the compiler says */
#if defined(__x86_64__)
#define bpf_target_x86
#define bpf_target_defined
#elif defined(__s390__)
#define bpf_target_s390
#define bpf_target_defined
#elif defined(__arm__)
#define bpf_target_arm
#define bpf_target_defined
#elif defined(__aarch64__)
#define bpf_target_arm64
#define bpf_target_defined
#elif defined(__mips__)
#define bpf_target_mips
#define bpf_target_defined
#elif defined(__powerpc__)
#define bpf_target_powerpc
#define bpf_target_defined
#elif defined(__sparc__)
#define bpf_target_sparc
#define bpf_target_defined
#elif defined(__riscv) && __riscv_xlen == 64
#define bpf_target_riscv
#define bpf_target_defined
#elif defined(__arc__)
#define bpf_target_arc
#define bpf_target_defined
#elif defined(__loongarch__)
#define bpf_target_loongarch
#define bpf_target_defined
#endif /* no compiler target */
#endif
#ifndef __BPF_TARGET_MISSING
#define __BPF_TARGET_MISSING "GCC error \"Must specify a BPF target arch via __TARGET_ARCH_xxx\""
#endif
#if defined(bpf_target_x86)
/*
* https://en.wikipedia.org/wiki/X86_calling_conventions#System_V_AMD64_ABI
*/
#if defined(__KERNEL__) || defined(__VMLINUX_H__)
#define __PT_PARM1_REG di
#define __PT_PARM2_REG si
#define __PT_PARM3_REG dx
#define __PT_PARM4_REG cx
#define __PT_PARM5_REG r8
#define __PT_PARM6_REG r9
/*
* Syscall uses r10 for PARM4. See arch/x86/entry/entry_64.S:entry_SYSCALL_64
* comments in Linux sources. And refer to syscall(2) manpage.
*/
#define __PT_PARM1_SYSCALL_REG __PT_PARM1_REG
#define __PT_PARM2_SYSCALL_REG __PT_PARM2_REG
#define __PT_PARM3_SYSCALL_REG __PT_PARM3_REG
#define __PT_PARM4_SYSCALL_REG r10
#define __PT_PARM5_SYSCALL_REG __PT_PARM5_REG
#define __PT_PARM6_SYSCALL_REG __PT_PARM6_REG
#define __PT_RET_REG sp
#define __PT_FP_REG bp
#define __PT_RC_REG ax
#define __PT_SP_REG sp
#define __PT_IP_REG ip
#else
#ifdef __i386__
/* i386 kernel is built with -mregparm=3 */
#define __PT_PARM1_REG eax
#define __PT_PARM2_REG edx
#define __PT_PARM3_REG ecx
/* i386 syscall ABI is very different, refer to syscall(2) manpage */
#define __PT_PARM1_SYSCALL_REG ebx
#define __PT_PARM2_SYSCALL_REG ecx
#define __PT_PARM3_SYSCALL_REG edx
#define __PT_PARM4_SYSCALL_REG esi
#define __PT_PARM5_SYSCALL_REG edi
#define __PT_PARM6_SYSCALL_REG ebp
#define __PT_RET_REG esp
#define __PT_FP_REG ebp
#define __PT_RC_REG eax
#define __PT_SP_REG esp
#define __PT_IP_REG eip
#else /* __i386__ */
#define __PT_PARM1_REG rdi
#define __PT_PARM2_REG rsi
#define __PT_PARM3_REG rdx
#define __PT_PARM4_REG rcx
#define __PT_PARM5_REG r8
#define __PT_PARM6_REG r9
#define __PT_PARM1_SYSCALL_REG __PT_PARM1_REG
#define __PT_PARM2_SYSCALL_REG __PT_PARM2_REG
#define __PT_PARM3_SYSCALL_REG __PT_PARM3_REG
#define __PT_PARM4_SYSCALL_REG r10
#define __PT_PARM5_SYSCALL_REG __PT_PARM5_REG
#define __PT_PARM6_SYSCALL_REG __PT_PARM6_REG
#define __PT_RET_REG rsp
#define __PT_FP_REG rbp
#define __PT_RC_REG rax
#define __PT_SP_REG rsp
#define __PT_IP_REG rip
#endif /* __i386__ */
#endif /* __KERNEL__ || __VMLINUX_H__ */
#elif defined(bpf_target_s390)
/*
* https://github.com/IBM/s390x-abi/releases/download/v1.6/lzsabi_s390x.pdf
*/
struct pt_regs___s390 {
unsigned long orig_gpr2;
};
/* s390 provides user_pt_regs instead of struct pt_regs to userspace */
#define __PT_REGS_CAST(x) ((const user_pt_regs *)(x))
#define __PT_PARM1_REG gprs[2]
#define __PT_PARM2_REG gprs[3]
#define __PT_PARM3_REG gprs[4]
#define __PT_PARM4_REG gprs[5]
#define __PT_PARM5_REG gprs[6]
#define __PT_PARM1_SYSCALL_REG orig_gpr2
#define __PT_PARM2_SYSCALL_REG __PT_PARM2_REG
#define __PT_PARM3_SYSCALL_REG __PT_PARM3_REG
#define __PT_PARM4_SYSCALL_REG __PT_PARM4_REG
#define __PT_PARM5_SYSCALL_REG __PT_PARM5_REG
#define __PT_PARM6_SYSCALL_REG gprs[7]
#define PT_REGS_PARM1_SYSCALL(x) PT_REGS_PARM1_CORE_SYSCALL(x)
#define PT_REGS_PARM1_CORE_SYSCALL(x) \
BPF_CORE_READ((const struct pt_regs___s390 *)(x), __PT_PARM1_SYSCALL_REG)
#define __PT_RET_REG gprs[14]
#define __PT_FP_REG gprs[11] /* Works only with CONFIG_FRAME_POINTER */
#define __PT_RC_REG gprs[2]
#define __PT_SP_REG gprs[15]
#define __PT_IP_REG psw.addr
#elif defined(bpf_target_arm)
/*
* https://github.com/ARM-software/abi-aa/blob/main/aapcs32/aapcs32.rst#machine-registers
*/
#define __PT_PARM1_REG uregs[0]
#define __PT_PARM2_REG uregs[1]
#define __PT_PARM3_REG uregs[2]
#define __PT_PARM4_REG uregs[3]
#define __PT_PARM1_SYSCALL_REG __PT_PARM1_REG
#define __PT_PARM2_SYSCALL_REG __PT_PARM2_REG
#define __PT_PARM3_SYSCALL_REG __PT_PARM3_REG
#define __PT_PARM4_SYSCALL_REG __PT_PARM4_REG
#define __PT_PARM5_SYSCALL_REG uregs[4]
#define __PT_PARM6_SYSCALL_REG uregs[5]
#define __PT_PARM7_SYSCALL_REG uregs[6]
#define __PT_RET_REG uregs[14]
#define __PT_FP_REG uregs[11] /* Works only with CONFIG_FRAME_POINTER */
#define __PT_RC_REG uregs[0]
#define __PT_SP_REG uregs[13]
#define __PT_IP_REG uregs[12]
#elif defined(bpf_target_arm64)
/*
* https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst#machine-registers
*/
struct pt_regs___arm64 {
unsigned long orig_x0;
};
/* arm64 provides struct user_pt_regs instead of struct pt_regs to userspace */
#define __PT_REGS_CAST(x) ((const struct user_pt_regs *)(x))
#define __PT_PARM1_REG regs[0]
#define __PT_PARM2_REG regs[1]
#define __PT_PARM3_REG regs[2]
#define __PT_PARM4_REG regs[3]
#define __PT_PARM5_REG regs[4]
#define __PT_PARM6_REG regs[5]
#define __PT_PARM7_REG regs[6]
#define __PT_PARM8_REG regs[7]
#define __PT_PARM1_SYSCALL_REG orig_x0
#define __PT_PARM2_SYSCALL_REG __PT_PARM2_REG
#define __PT_PARM3_SYSCALL_REG __PT_PARM3_REG
#define __PT_PARM4_SYSCALL_REG __PT_PARM4_REG
#define __PT_PARM5_SYSCALL_REG __PT_PARM5_REG
#define __PT_PARM6_SYSCALL_REG __PT_PARM6_REG
#define PT_REGS_PARM1_SYSCALL(x) PT_REGS_PARM1_CORE_SYSCALL(x)
#define PT_REGS_PARM1_CORE_SYSCALL(x) \
BPF_CORE_READ((const struct pt_regs___arm64 *)(x), __PT_PARM1_SYSCALL_REG)
#define __PT_RET_REG regs[30]
#define __PT_FP_REG regs[29] /* Works only with CONFIG_FRAME_POINTER */
#define __PT_RC_REG regs[0]
#define __PT_SP_REG sp
#define __PT_IP_REG pc
#elif defined(bpf_target_mips)
/*
* N64 ABI is assumed right now.
* https://en.wikipedia.org/wiki/MIPS_architecture#Calling_conventions
*/
#define __PT_PARM1_REG regs[4]
#define __PT_PARM2_REG regs[5]
#define __PT_PARM3_REG regs[6]
#define __PT_PARM4_REG regs[7]
#define __PT_PARM5_REG regs[8]
#define __PT_PARM6_REG regs[9]
#define __PT_PARM7_REG regs[10]
#define __PT_PARM8_REG regs[11]
#define __PT_PARM1_SYSCALL_REG __PT_PARM1_REG
#define __PT_PARM2_SYSCALL_REG __PT_PARM2_REG
#define __PT_PARM3_SYSCALL_REG __PT_PARM3_REG
#define __PT_PARM4_SYSCALL_REG __PT_PARM4_REG
#define __PT_PARM5_SYSCALL_REG __PT_PARM5_REG /* only N32/N64 */
#define __PT_PARM6_SYSCALL_REG __PT_PARM6_REG /* only N32/N64 */
#define __PT_RET_REG regs[31]
#define __PT_FP_REG regs[30] /* Works only with CONFIG_FRAME_POINTER */
#define __PT_RC_REG regs[2]
#define __PT_SP_REG regs[29]
#define __PT_IP_REG cp0_epc
#elif defined(bpf_target_powerpc)
/*
* http://refspecs.linux-foundation.org/elf/elfspec_ppc.pdf (page 3-14,
* section "Function Calling Sequence")
*/
#define __PT_PARM1_REG gpr[3]
#define __PT_PARM2_REG gpr[4]
#define __PT_PARM3_REG gpr[5]
#define __PT_PARM4_REG gpr[6]
#define __PT_PARM5_REG gpr[7]
#define __PT_PARM6_REG gpr[8]
#define __PT_PARM7_REG gpr[9]
#define __PT_PARM8_REG gpr[10]
/* powerpc does not select ARCH_HAS_SYSCALL_WRAPPER. */
#define PT_REGS_SYSCALL_REGS(ctx) ctx
#define __PT_PARM1_SYSCALL_REG orig_gpr3
#define __PT_PARM2_SYSCALL_REG __PT_PARM2_REG
#define __PT_PARM3_SYSCALL_REG __PT_PARM3_REG
#define __PT_PARM4_SYSCALL_REG __PT_PARM4_REG
#define __PT_PARM5_SYSCALL_REG __PT_PARM5_REG
#define __PT_PARM6_SYSCALL_REG __PT_PARM6_REG
#if !defined(__arch64__)
#define __PT_PARM7_SYSCALL_REG __PT_PARM7_REG /* only powerpc (not powerpc64) */
#endif
#define __PT_RET_REG regs[31]
#define __PT_FP_REG __unsupported__
#define __PT_RC_REG gpr[3]
#define __PT_SP_REG sp
#define __PT_IP_REG nip
#elif defined(bpf_target_sparc)
/*
* https://en.wikipedia.org/wiki/Calling_convention#SPARC
*/
#define __PT_PARM1_REG u_regs[UREG_I0]
#define __PT_PARM2_REG u_regs[UREG_I1]
#define __PT_PARM3_REG u_regs[UREG_I2]
#define __PT_PARM4_REG u_regs[UREG_I3]
#define __PT_PARM5_REG u_regs[UREG_I4]
#define __PT_PARM6_REG u_regs[UREG_I5]
#define __PT_PARM1_SYSCALL_REG __PT_PARM1_REG
#define __PT_PARM2_SYSCALL_REG __PT_PARM2_REG
#define __PT_PARM3_SYSCALL_REG __PT_PARM3_REG
#define __PT_PARM4_SYSCALL_REG __PT_PARM4_REG
#define __PT_PARM5_SYSCALL_REG __PT_PARM5_REG
#define __PT_PARM6_SYSCALL_REG __PT_PARM6_REG
#define __PT_RET_REG u_regs[UREG_I7]
#define __PT_FP_REG __unsupported__
#define __PT_RC_REG u_regs[UREG_I0]
#define __PT_SP_REG u_regs[UREG_FP]
/* Should this also be a bpf_target check for the sparc case? */
#if defined(__arch64__)
#define __PT_IP_REG tpc
#else
#define __PT_IP_REG pc
#endif
#elif defined(bpf_target_riscv)
/*
* https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc#risc-v-calling-conventions
*/
#define __PT_REGS_CAST(x) ((const struct user_regs_struct *)(x))
#define __PT_PARM1_REG a0
#define __PT_PARM2_REG a1
#define __PT_PARM3_REG a2
#define __PT_PARM4_REG a3
#define __PT_PARM5_REG a4
#define __PT_PARM6_REG a5
#define __PT_PARM7_REG a6
#define __PT_PARM8_REG a7
/* riscv does not select ARCH_HAS_SYSCALL_WRAPPER. */
#define PT_REGS_SYSCALL_REGS(ctx) ctx
#define __PT_PARM1_SYSCALL_REG __PT_PARM1_REG
#define __PT_PARM2_SYSCALL_REG __PT_PARM2_REG
#define __PT_PARM3_SYSCALL_REG __PT_PARM3_REG
#define __PT_PARM4_SYSCALL_REG __PT_PARM4_REG
#define __PT_PARM5_SYSCALL_REG __PT_PARM5_REG
#define __PT_PARM6_SYSCALL_REG __PT_PARM6_REG
#define __PT_RET_REG ra
#define __PT_FP_REG s0
#define __PT_RC_REG a0
#define __PT_SP_REG sp
#define __PT_IP_REG pc
#elif defined(bpf_target_arc)
/*
* Section "Function Calling Sequence" (page 24):
* https://raw.githubusercontent.com/wiki/foss-for-synopsys-dwc-arc-processors/toolchain/files/ARCv2_ABI.pdf
*/
/* arc provides struct user_pt_regs instead of struct pt_regs to userspace */
#define __PT_REGS_CAST(x) ((const struct user_regs_struct *)(x))
#define __PT_PARM1_REG scratch.r0
#define __PT_PARM2_REG scratch.r1
#define __PT_PARM3_REG scratch.r2
#define __PT_PARM4_REG scratch.r3
#define __PT_PARM5_REG scratch.r4
#define __PT_PARM6_REG scratch.r5
#define __PT_PARM7_REG scratch.r6
#define __PT_PARM8_REG scratch.r7
/* arc does not select ARCH_HAS_SYSCALL_WRAPPER. */
#define PT_REGS_SYSCALL_REGS(ctx) ctx
#define __PT_PARM1_SYSCALL_REG __PT_PARM1_REG
#define __PT_PARM2_SYSCALL_REG __PT_PARM2_REG
#define __PT_PARM3_SYSCALL_REG __PT_PARM3_REG
#define __PT_PARM4_SYSCALL_REG __PT_PARM4_REG
#define __PT_PARM5_SYSCALL_REG __PT_PARM5_REG
#define __PT_PARM6_SYSCALL_REG __PT_PARM6_REG
#define __PT_RET_REG scratch.blink
#define __PT_FP_REG scratch.fp
#define __PT_RC_REG scratch.r0
#define __PT_SP_REG scratch.sp
#define __PT_IP_REG scratch.ret
#elif defined(bpf_target_loongarch)
/*
* https://docs.kernel.org/loongarch/introduction.html
* https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html
*/
/* loongarch provides struct user_pt_regs instead of struct pt_regs to userspace */
#define __PT_REGS_CAST(x) ((const struct user_pt_regs *)(x))
#define __PT_PARM1_REG regs[4]
#define __PT_PARM2_REG regs[5]
#define __PT_PARM3_REG regs[6]
#define __PT_PARM4_REG regs[7]
#define __PT_PARM5_REG regs[8]
#define __PT_PARM6_REG regs[9]
#define __PT_PARM7_REG regs[10]
#define __PT_PARM8_REG regs[11]
/* loongarch does not select ARCH_HAS_SYSCALL_WRAPPER. */
#define PT_REGS_SYSCALL_REGS(ctx) ctx
#define __PT_PARM1_SYSCALL_REG __PT_PARM1_REG
#define __PT_PARM2_SYSCALL_REG __PT_PARM2_REG
#define __PT_PARM3_SYSCALL_REG __PT_PARM3_REG
#define __PT_PARM4_SYSCALL_REG __PT_PARM4_REG
#define __PT_PARM5_SYSCALL_REG __PT_PARM5_REG
#define __PT_PARM6_SYSCALL_REG __PT_PARM6_REG
#define __PT_RET_REG regs[1]
#define __PT_FP_REG regs[22]
#define __PT_RC_REG regs[4]
#define __PT_SP_REG regs[3]
#define __PT_IP_REG csr_era
#endif
#if defined(bpf_target_defined)
struct pt_regs;
/* allow some architectures to override `struct pt_regs` */
#ifndef __PT_REGS_CAST
#define __PT_REGS_CAST(x) (x)
#endif
/*
* Different architectures support different number of arguments passed
* through registers. i386 supports just 3, some arches support up to 8.
*/
#ifndef __PT_PARM4_REG
#define __PT_PARM4_REG __unsupported__
#endif
#ifndef __PT_PARM5_REG
#define __PT_PARM5_REG __unsupported__
#endif
#ifndef __PT_PARM6_REG
#define __PT_PARM6_REG __unsupported__
#endif
#ifndef __PT_PARM7_REG
#define __PT_PARM7_REG __unsupported__
#endif
#ifndef __PT_PARM8_REG
#define __PT_PARM8_REG __unsupported__
#endif
/*
* Similarly, syscall-specific conventions might differ between function call
* conventions within each architecutre. All supported architectures pass
* either 6 or 7 syscall arguments in registers.
*
* See syscall(2) manpage for succinct table with information on each arch.
*/
#ifndef __PT_PARM7_SYSCALL_REG
#define __PT_PARM7_SYSCALL_REG __unsupported__
#endif
#define PT_REGS_PARM1(x) (__PT_REGS_CAST(x)->__PT_PARM1_REG)
#define PT_REGS_PARM2(x) (__PT_REGS_CAST(x)->__PT_PARM2_REG)
#define PT_REGS_PARM3(x) (__PT_REGS_CAST(x)->__PT_PARM3_REG)
#define PT_REGS_PARM4(x) (__PT_REGS_CAST(x)->__PT_PARM4_REG)
#define PT_REGS_PARM5(x) (__PT_REGS_CAST(x)->__PT_PARM5_REG)
#define PT_REGS_PARM6(x) (__PT_REGS_CAST(x)->__PT_PARM6_REG)
#define PT_REGS_PARM7(x) (__PT_REGS_CAST(x)->__PT_PARM7_REG)
#define PT_REGS_PARM8(x) (__PT_REGS_CAST(x)->__PT_PARM8_REG)
#define PT_REGS_RET(x) (__PT_REGS_CAST(x)->__PT_RET_REG)
#define PT_REGS_FP(x) (__PT_REGS_CAST(x)->__PT_FP_REG)
#define PT_REGS_RC(x) (__PT_REGS_CAST(x)->__PT_RC_REG)
#define PT_REGS_SP(x) (__PT_REGS_CAST(x)->__PT_SP_REG)
#define PT_REGS_IP(x) (__PT_REGS_CAST(x)->__PT_IP_REG)
#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM1_REG)
#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM2_REG)
#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM3_REG)
#define PT_REGS_PARM4_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM4_REG)
#define PT_REGS_PARM5_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM5_REG)
#define PT_REGS_PARM6_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM6_REG)
#define PT_REGS_PARM7_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM7_REG)
#define PT_REGS_PARM8_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM8_REG)
#define PT_REGS_RET_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_RET_REG)
#define PT_REGS_FP_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_FP_REG)
#define PT_REGS_RC_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_RC_REG)
#define PT_REGS_SP_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_SP_REG)
#define PT_REGS_IP_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_IP_REG)
#if defined(bpf_target_powerpc)
#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = (ctx)->link; })
#define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP
#elif defined(bpf_target_sparc)
#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = PT_REGS_RET(ctx); })
#define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP
#else
#define BPF_KPROBE_READ_RET_IP(ip, ctx) \
({ bpf_probe_read_kernel(&(ip), sizeof(ip), (void *)PT_REGS_RET(ctx)); })
#define BPF_KRETPROBE_READ_RET_IP(ip, ctx) \
({ bpf_probe_read_kernel(&(ip), sizeof(ip), (void *)(PT_REGS_FP(ctx) + sizeof(ip))); })
#endif
#ifndef PT_REGS_PARM1_SYSCALL
#define PT_REGS_PARM1_SYSCALL(x) (__PT_REGS_CAST(x)->__PT_PARM1_SYSCALL_REG)
#define PT_REGS_PARM1_CORE_SYSCALL(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM1_SYSCALL_REG)
#endif
#ifndef PT_REGS_PARM2_SYSCALL
#define PT_REGS_PARM2_SYSCALL(x) (__PT_REGS_CAST(x)->__PT_PARM2_SYSCALL_REG)
#define PT_REGS_PARM2_CORE_SYSCALL(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM2_SYSCALL_REG)
#endif
#ifndef PT_REGS_PARM3_SYSCALL
#define PT_REGS_PARM3_SYSCALL(x) (__PT_REGS_CAST(x)->__PT_PARM3_SYSCALL_REG)
#define PT_REGS_PARM3_CORE_SYSCALL(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM3_SYSCALL_REG)
#endif
#ifndef PT_REGS_PARM4_SYSCALL
#define PT_REGS_PARM4_SYSCALL(x) (__PT_REGS_CAST(x)->__PT_PARM4_SYSCALL_REG)
#define PT_REGS_PARM4_CORE_SYSCALL(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM4_SYSCALL_REG)
#endif
#ifndef PT_REGS_PARM5_SYSCALL
#define PT_REGS_PARM5_SYSCALL(x) (__PT_REGS_CAST(x)->__PT_PARM5_SYSCALL_REG)
#define PT_REGS_PARM5_CORE_SYSCALL(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM5_SYSCALL_REG)
#endif
#ifndef PT_REGS_PARM6_SYSCALL
#define PT_REGS_PARM6_SYSCALL(x) (__PT_REGS_CAST(x)->__PT_PARM6_SYSCALL_REG)
#define PT_REGS_PARM6_CORE_SYSCALL(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM6_SYSCALL_REG)
#endif
#ifndef PT_REGS_PARM7_SYSCALL
#define PT_REGS_PARM7_SYSCALL(x) (__PT_REGS_CAST(x)->__PT_PARM7_SYSCALL_REG)
#define PT_REGS_PARM7_CORE_SYSCALL(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM7_SYSCALL_REG)
#endif
#else /* defined(bpf_target_defined) */
#define PT_REGS_PARM1(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM2(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM3(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM4(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM5(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM6(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM7(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM8(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_RET(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_FP(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_RC(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_SP(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_IP(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM1_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM2_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM3_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM4_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM5_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM6_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM7_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM8_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_RET_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_FP_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_RC_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_SP_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_IP_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define BPF_KRETPROBE_READ_RET_IP(ip, ctx) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM1_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM2_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM3_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM4_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM5_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM6_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM7_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM1_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM2_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM3_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM4_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM5_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM6_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#define PT_REGS_PARM7_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
#endif /* defined(bpf_target_defined) */
/*
* When invoked from a syscall handler kprobe, returns a pointer to a
* struct pt_regs containing syscall arguments and suitable for passing to
* PT_REGS_PARMn_SYSCALL() and PT_REGS_PARMn_CORE_SYSCALL().
*/
#ifndef PT_REGS_SYSCALL_REGS
/* By default, assume that the arch selects ARCH_HAS_SYSCALL_WRAPPER. */
#define PT_REGS_SYSCALL_REGS(ctx) ((struct pt_regs *)PT_REGS_PARM1(ctx))
#endif
#ifndef ___bpf_concat
#define ___bpf_concat(a, b) a ## b
#endif
#ifndef ___bpf_apply
#define ___bpf_apply(fn, n) ___bpf_concat(fn, n)
#endif
#ifndef ___bpf_nth
#define ___bpf_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, N, ...) N
#endif
#ifndef ___bpf_narg
#define ___bpf_narg(...) ___bpf_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#endif
#define ___bpf_ctx_cast0() ctx
#define ___bpf_ctx_cast1(x) ___bpf_ctx_cast0(), (void *)ctx[0]
#define ___bpf_ctx_cast2(x, args...) ___bpf_ctx_cast1(args), (void *)ctx[1]
#define ___bpf_ctx_cast3(x, args...) ___bpf_ctx_cast2(args), (void *)ctx[2]
#define ___bpf_ctx_cast4(x, args...) ___bpf_ctx_cast3(args), (void *)ctx[3]
#define ___bpf_ctx_cast5(x, args...) ___bpf_ctx_cast4(args), (void *)ctx[4]
#define ___bpf_ctx_cast6(x, args...) ___bpf_ctx_cast5(args), (void *)ctx[5]
#define ___bpf_ctx_cast7(x, args...) ___bpf_ctx_cast6(args), (void *)ctx[6]
#define ___bpf_ctx_cast8(x, args...) ___bpf_ctx_cast7(args), (void *)ctx[7]
#define ___bpf_ctx_cast9(x, args...) ___bpf_ctx_cast8(args), (void *)ctx[8]
#define ___bpf_ctx_cast10(x, args...) ___bpf_ctx_cast9(args), (void *)ctx[9]
#define ___bpf_ctx_cast11(x, args...) ___bpf_ctx_cast10(args), (void *)ctx[10]
#define ___bpf_ctx_cast12(x, args...) ___bpf_ctx_cast11(args), (void *)ctx[11]
#define ___bpf_ctx_cast(args...) ___bpf_apply(___bpf_ctx_cast, ___bpf_narg(args))(args)
/*
* BPF_PROG is a convenience wrapper for generic tp_btf/fentry/fexit and
* similar kinds of BPF programs, that accept input arguments as a single
* pointer to untyped u64 array, where each u64 can actually be a typed
* pointer or integer of different size. Instead of requring user to write
* manual casts and work with array elements by index, BPF_PROG macro
* allows user to declare a list of named and typed input arguments in the
* same syntax as for normal C function. All the casting is hidden and
* performed transparently, while user code can just assume working with
* function arguments of specified type and name.
*
* Original raw context argument is preserved as well as 'ctx' argument.
* This is useful when using BPF helpers that expect original context
* as one of the parameters (e.g., for bpf_perf_event_output()).
*/
#define BPF_PROG(name, args...) \
name(unsigned long long *ctx); \
static __always_inline typeof(name(0)) \
____##name(unsigned long long *ctx, ##args); \
typeof(name(0)) name(unsigned long long *ctx) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
return ____##name(___bpf_ctx_cast(args)); \
_Pragma("GCC diagnostic pop") \
} \
static __always_inline typeof(name(0)) \
____##name(unsigned long long *ctx, ##args)
#ifndef ___bpf_nth2
#define ___bpf_nth2(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \
_14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, N, ...) N
#endif
#ifndef ___bpf_narg2
#define ___bpf_narg2(...) \
___bpf_nth2(_, ##__VA_ARGS__, 12, 12, 11, 11, 10, 10, 9, 9, 8, 8, 7, 7, \
6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0)
#endif
#define ___bpf_treg_cnt(t) \
__builtin_choose_expr(sizeof(t) == 1, 1, \
__builtin_choose_expr(sizeof(t) == 2, 1, \
__builtin_choose_expr(sizeof(t) == 4, 1, \
__builtin_choose_expr(sizeof(t) == 8, 1, \
__builtin_choose_expr(sizeof(t) == 16, 2, \
(void)0)))))
#define ___bpf_reg_cnt0() (0)
#define ___bpf_reg_cnt1(t, x) (___bpf_reg_cnt0() + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt2(t, x, args...) (___bpf_reg_cnt1(args) + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt3(t, x, args...) (___bpf_reg_cnt2(args) + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt4(t, x, args...) (___bpf_reg_cnt3(args) + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt5(t, x, args...) (___bpf_reg_cnt4(args) + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt6(t, x, args...) (___bpf_reg_cnt5(args) + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt7(t, x, args...) (___bpf_reg_cnt6(args) + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt8(t, x, args...) (___bpf_reg_cnt7(args) + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt9(t, x, args...) (___bpf_reg_cnt8(args) + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt10(t, x, args...) (___bpf_reg_cnt9(args) + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt11(t, x, args...) (___bpf_reg_cnt10(args) + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt12(t, x, args...) (___bpf_reg_cnt11(args) + ___bpf_treg_cnt(t))
#define ___bpf_reg_cnt(args...) ___bpf_apply(___bpf_reg_cnt, ___bpf_narg2(args))(args)
#define ___bpf_union_arg(t, x, n) \
__builtin_choose_expr(sizeof(t) == 1, ({ union { __u8 z[1]; t x; } ___t = { .z = {ctx[n]}}; ___t.x; }), \
__builtin_choose_expr(sizeof(t) == 2, ({ union { __u16 z[1]; t x; } ___t = { .z = {ctx[n]} }; ___t.x; }), \
__builtin_choose_expr(sizeof(t) == 4, ({ union { __u32 z[1]; t x; } ___t = { .z = {ctx[n]} }; ___t.x; }), \
__builtin_choose_expr(sizeof(t) == 8, ({ union { __u64 z[1]; t x; } ___t = {.z = {ctx[n]} }; ___t.x; }), \
__builtin_choose_expr(sizeof(t) == 16, ({ union { __u64 z[2]; t x; } ___t = {.z = {ctx[n], ctx[n + 1]} }; ___t.x; }), \
(void)0)))))
#define ___bpf_ctx_arg0(n, args...)
#define ___bpf_ctx_arg1(n, t, x) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt1(t, x))
#define ___bpf_ctx_arg2(n, t, x, args...) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt2(t, x, args)) ___bpf_ctx_arg1(n, args)
#define ___bpf_ctx_arg3(n, t, x, args...) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt3(t, x, args)) ___bpf_ctx_arg2(n, args)
#define ___bpf_ctx_arg4(n, t, x, args...) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt4(t, x, args)) ___bpf_ctx_arg3(n, args)
#define ___bpf_ctx_arg5(n, t, x, args...) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt5(t, x, args)) ___bpf_ctx_arg4(n, args)
#define ___bpf_ctx_arg6(n, t, x, args...) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt6(t, x, args)) ___bpf_ctx_arg5(n, args)
#define ___bpf_ctx_arg7(n, t, x, args...) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt7(t, x, args)) ___bpf_ctx_arg6(n, args)
#define ___bpf_ctx_arg8(n, t, x, args...) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt8(t, x, args)) ___bpf_ctx_arg7(n, args)
#define ___bpf_ctx_arg9(n, t, x, args...) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt9(t, x, args)) ___bpf_ctx_arg8(n, args)
#define ___bpf_ctx_arg10(n, t, x, args...) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt10(t, x, args)) ___bpf_ctx_arg9(n, args)
#define ___bpf_ctx_arg11(n, t, x, args...) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt11(t, x, args)) ___bpf_ctx_arg10(n, args)
#define ___bpf_ctx_arg12(n, t, x, args...) , ___bpf_union_arg(t, x, n - ___bpf_reg_cnt12(t, x, args)) ___bpf_ctx_arg11(n, args)
#define ___bpf_ctx_arg(args...) ___bpf_apply(___bpf_ctx_arg, ___bpf_narg2(args))(___bpf_reg_cnt(args), args)
#define ___bpf_ctx_decl0()
#define ___bpf_ctx_decl1(t, x) , t x
#define ___bpf_ctx_decl2(t, x, args...) , t x ___bpf_ctx_decl1(args)
#define ___bpf_ctx_decl3(t, x, args...) , t x ___bpf_ctx_decl2(args)
#define ___bpf_ctx_decl4(t, x, args...) , t x ___bpf_ctx_decl3(args)
#define ___bpf_ctx_decl5(t, x, args...) , t x ___bpf_ctx_decl4(args)
#define ___bpf_ctx_decl6(t, x, args...) , t x ___bpf_ctx_decl5(args)
#define ___bpf_ctx_decl7(t, x, args...) , t x ___bpf_ctx_decl6(args)
#define ___bpf_ctx_decl8(t, x, args...) , t x ___bpf_ctx_decl7(args)
#define ___bpf_ctx_decl9(t, x, args...) , t x ___bpf_ctx_decl8(args)
#define ___bpf_ctx_decl10(t, x, args...) , t x ___bpf_ctx_decl9(args)
#define ___bpf_ctx_decl11(t, x, args...) , t x ___bpf_ctx_decl10(args)
#define ___bpf_ctx_decl12(t, x, args...) , t x ___bpf_ctx_decl11(args)
#define ___bpf_ctx_decl(args...) ___bpf_apply(___bpf_ctx_decl, ___bpf_narg2(args))(args)
/*
* BPF_PROG2 is an enhanced version of BPF_PROG in order to handle struct
* arguments. Since each struct argument might take one or two u64 values
* in the trampoline stack, argument type size is needed to place proper number
* of u64 values for each argument. Therefore, BPF_PROG2 has different
* syntax from BPF_PROG. For example, for the following BPF_PROG syntax:
*
* int BPF_PROG(test2, int a, int b) { ... }
*
* the corresponding BPF_PROG2 syntax is:
*
* int BPF_PROG2(test2, int, a, int, b) { ... }
*
* where type and the corresponding argument name are separated by comma.
*
* Use BPF_PROG2 macro if one of the arguments might be a struct/union larger
* than 8 bytes:
*
* int BPF_PROG2(test_struct_arg, struct bpf_testmod_struct_arg_1, a, int, b,
* int, c, int, d, struct bpf_testmod_struct_arg_2, e, int, ret)
* {
* // access a, b, c, d, e, and ret directly
* ...
* }
*/
#define BPF_PROG2(name, args...) \
name(unsigned long long *ctx); \
static __always_inline typeof(name(0)) \
____##name(unsigned long long *ctx ___bpf_ctx_decl(args)); \
typeof(name(0)) name(unsigned long long *ctx) \
{ \
return ____##name(ctx ___bpf_ctx_arg(args)); \
} \
static __always_inline typeof(name(0)) \
____##name(unsigned long long *ctx ___bpf_ctx_decl(args))
struct pt_regs;
#define ___bpf_kprobe_args0() ctx
#define ___bpf_kprobe_args1(x) ___bpf_kprobe_args0(), (void *)PT_REGS_PARM1(ctx)
#define ___bpf_kprobe_args2(x, args...) ___bpf_kprobe_args1(args), (void *)PT_REGS_PARM2(ctx)
#define ___bpf_kprobe_args3(x, args...) ___bpf_kprobe_args2(args), (void *)PT_REGS_PARM3(ctx)
#define ___bpf_kprobe_args4(x, args...) ___bpf_kprobe_args3(args), (void *)PT_REGS_PARM4(ctx)
#define ___bpf_kprobe_args5(x, args...) ___bpf_kprobe_args4(args), (void *)PT_REGS_PARM5(ctx)
#define ___bpf_kprobe_args6(x, args...) ___bpf_kprobe_args5(args), (void *)PT_REGS_PARM6(ctx)
#define ___bpf_kprobe_args7(x, args...) ___bpf_kprobe_args6(args), (void *)PT_REGS_PARM7(ctx)
#define ___bpf_kprobe_args8(x, args...) ___bpf_kprobe_args7(args), (void *)PT_REGS_PARM8(ctx)
#define ___bpf_kprobe_args(args...) ___bpf_apply(___bpf_kprobe_args, ___bpf_narg(args))(args)
/*
* BPF_KPROBE serves the same purpose for kprobes as BPF_PROG for
* tp_btf/fentry/fexit BPF programs. It hides the underlying platform-specific
* low-level way of getting kprobe input arguments from struct pt_regs, and
* provides a familiar typed and named function arguments syntax and
* semantics of accessing kprobe input paremeters.
*
* Original struct pt_regs* context is preserved as 'ctx' argument. This might
* be necessary when using BPF helpers like bpf_perf_event_output().
*/
#define BPF_KPROBE(name, args...) \
name(struct pt_regs *ctx); \
static __always_inline typeof(name(0)) \
____##name(struct pt_regs *ctx, ##args); \
typeof(name(0)) name(struct pt_regs *ctx) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
return ____##name(___bpf_kprobe_args(args)); \
_Pragma("GCC diagnostic pop") \
} \
static __always_inline typeof(name(0)) \
____##name(struct pt_regs *ctx, ##args)
#define ___bpf_kretprobe_args0() ctx
#define ___bpf_kretprobe_args1(x) ___bpf_kretprobe_args0(), (void *)PT_REGS_RC(ctx)
#define ___bpf_kretprobe_args(args...) ___bpf_apply(___bpf_kretprobe_args, ___bpf_narg(args))(args)
/*
* BPF_KRETPROBE is similar to BPF_KPROBE, except, it only provides optional
* return value (in addition to `struct pt_regs *ctx`), but no input
* arguments, because they will be clobbered by the time probed function
* returns.
*/
#define BPF_KRETPROBE(name, args...) \
name(struct pt_regs *ctx); \
static __always_inline typeof(name(0)) \
____##name(struct pt_regs *ctx, ##args); \
typeof(name(0)) name(struct pt_regs *ctx) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
return ____##name(___bpf_kretprobe_args(args)); \
_Pragma("GCC diagnostic pop") \
} \
static __always_inline typeof(name(0)) ____##name(struct pt_regs *ctx, ##args)
/* If kernel has CONFIG_ARCH_HAS_SYSCALL_WRAPPER, read pt_regs directly */
#define ___bpf_syscall_args0() ctx
#define ___bpf_syscall_args1(x) ___bpf_syscall_args0(), (void *)PT_REGS_PARM1_SYSCALL(regs)
#define ___bpf_syscall_args2(x, args...) ___bpf_syscall_args1(args), (void *)PT_REGS_PARM2_SYSCALL(regs)
#define ___bpf_syscall_args3(x, args...) ___bpf_syscall_args2(args), (void *)PT_REGS_PARM3_SYSCALL(regs)
#define ___bpf_syscall_args4(x, args...) ___bpf_syscall_args3(args), (void *)PT_REGS_PARM4_SYSCALL(regs)
#define ___bpf_syscall_args5(x, args...) ___bpf_syscall_args4(args), (void *)PT_REGS_PARM5_SYSCALL(regs)
#define ___bpf_syscall_args6(x, args...) ___bpf_syscall_args5(args), (void *)PT_REGS_PARM6_SYSCALL(regs)
#define ___bpf_syscall_args7(x, args...) ___bpf_syscall_args6(args), (void *)PT_REGS_PARM7_SYSCALL(regs)
#define ___bpf_syscall_args(args...) ___bpf_apply(___bpf_syscall_args, ___bpf_narg(args))(args)
/* If kernel doesn't have CONFIG_ARCH_HAS_SYSCALL_WRAPPER, we have to BPF_CORE_READ from pt_regs */
#define ___bpf_syswrap_args0() ctx
#define ___bpf_syswrap_args1(x) ___bpf_syswrap_args0(), (void *)PT_REGS_PARM1_CORE_SYSCALL(regs)
#define ___bpf_syswrap_args2(x, args...) ___bpf_syswrap_args1(args), (void *)PT_REGS_PARM2_CORE_SYSCALL(regs)
#define ___bpf_syswrap_args3(x, args...) ___bpf_syswrap_args2(args), (void *)PT_REGS_PARM3_CORE_SYSCALL(regs)
#define ___bpf_syswrap_args4(x, args...) ___bpf_syswrap_args3(args), (void *)PT_REGS_PARM4_CORE_SYSCALL(regs)
#define ___bpf_syswrap_args5(x, args...) ___bpf_syswrap_args4(args), (void *)PT_REGS_PARM5_CORE_SYSCALL(regs)
#define ___bpf_syswrap_args6(x, args...) ___bpf_syswrap_args5(args), (void *)PT_REGS_PARM6_CORE_SYSCALL(regs)
#define ___bpf_syswrap_args7(x, args...) ___bpf_syswrap_args6(args), (void *)PT_REGS_PARM7_CORE_SYSCALL(regs)
#define ___bpf_syswrap_args(args...) ___bpf_apply(___bpf_syswrap_args, ___bpf_narg(args))(args)
/*
* BPF_KSYSCALL is a variant of BPF_KPROBE, which is intended for
* tracing syscall functions, like __x64_sys_close. It hides the underlying
* platform-specific low-level way of getting syscall input arguments from
* struct pt_regs, and provides a familiar typed and named function arguments
* syntax and semantics of accessing syscall input parameters.
*
* Original struct pt_regs * context is preserved as 'ctx' argument. This might
* be necessary when using BPF helpers like bpf_perf_event_output().
*
* At the moment BPF_KSYSCALL does not transparently handle all the calling
* convention quirks for the following syscalls:
*
* - mmap(): __ARCH_WANT_SYS_OLD_MMAP.
* - clone(): CONFIG_CLONE_BACKWARDS, CONFIG_CLONE_BACKWARDS2 and
* CONFIG_CLONE_BACKWARDS3.
* - socket-related syscalls: __ARCH_WANT_SYS_SOCKETCALL.
* - compat syscalls.
*
* This may or may not change in the future. User needs to take extra measures
* to handle such quirks explicitly, if necessary.
*
* This macro relies on BPF CO-RE support and virtual __kconfig externs.
*/
#define BPF_KSYSCALL(name, args...) \
name(struct pt_regs *ctx); \
extern _Bool LINUX_HAS_SYSCALL_WRAPPER __kconfig; \
static __always_inline typeof(name(0)) \
____##name(struct pt_regs *ctx, ##args); \
typeof(name(0)) name(struct pt_regs *ctx) \
{ \
struct pt_regs *regs = LINUX_HAS_SYSCALL_WRAPPER \
? (struct pt_regs *)PT_REGS_PARM1(ctx) \
: ctx; \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \
if (LINUX_HAS_SYSCALL_WRAPPER) \
return ____##name(___bpf_syswrap_args(args)); \
else \
return ____##name(___bpf_syscall_args(args)); \
_Pragma("GCC diagnostic pop") \
} \
static __always_inline typeof(name(0)) \
____##name(struct pt_regs *ctx, ##args)
#define BPF_KPROBE_SYSCALL BPF_KSYSCALL
/* BPF_UPROBE and BPF_URETPROBE are identical to BPF_KPROBE and BPF_KRETPROBE,
* but are named way less confusingly for SEC("uprobe") and SEC("uretprobe")
* use cases.
*/
#define BPF_UPROBE(name, args...) BPF_KPROBE(name, ##args)
#define BPF_URETPROBE(name, args...) BPF_KRETPROBE(name, ##args)
#endif

View File

@@ -0,0 +1,182 @@
#include "vmlinux-x86.h"
#include "bpf/bpf_helpers.h"
#include "bpf/bpf_tracing.h"
// IP Version
#define AF_INET 2
#define AF_INET6 10
// Protocols
#define TCP 6
#define UDP 17
#define UDPLite 136
#define OUTBOUND 0
#define INBOUND 1
char __license[] SEC("license") = "GPL";
// Ring buffer for all connection events
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} 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 {
u32 saddr[4];
u32 daddr[4];
u16 sport;
u16 dport;
u32 pid;
u8 ipVersion;
u8 protocol;
u8 direction;
};
struct Event *unused __attribute__((unused));
// Fentry of tcp_connect will be executed when equivalent kernel function is called.
// In the kernel all IP address and ports should be set before tcp_connect is called. [this-function] -> tcp_connect
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(&pm_connection_events, sizeof(struct Event), 0);
if (!tcp_info) {
return 0;
}
// Read PID
tcp_info->pid = __builtin_bswap32((u32)bpf_get_current_pid_tgid());
// Set protocol
tcp_info->protocol = TCP;
// Set direction
tcp_info->direction = OUTBOUND;
// Set src and dist ports
tcp_info->sport = __builtin_bswap16(sk->__sk_common.skc_num);
tcp_info->dport = sk->__sk_common.skc_dport;
// Set src and dist IPs
if (sk->__sk_common.skc_family == AF_INET) {
tcp_info->saddr[0] = __builtin_bswap32(sk->__sk_common.skc_rcv_saddr);
tcp_info->daddr[0] = __builtin_bswap32(sk->__sk_common.skc_daddr);
// Set IP version
tcp_info->ipVersion = 4;
} else if (sk->__sk_common.skc_family == AF_INET6) {
for(int i = 0; i < 4; i++) {
tcp_info->saddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32[i]);
}
for(int i = 0; i < 4; i++) {
tcp_info->daddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32[i]);
}
// Set IP version
tcp_info->ipVersion = 6;
}
// Send event
bpf_ringbuf_submit(tcp_info, 0);
return 0;
};
// Fexit(function exit) of udp_v4_connect will be executed after the ip4_datagram_connect kernel function is called.
// ip4_datagram_connect -> udp_v4_connect
SEC("fexit/ip4_datagram_connect")
int BPF_PROG(udp_v4_connect, struct sock *sk) {
// Ignore everything else then IPv4
if (sk->__sk_common.skc_family != AF_INET) {
return 0;
}
// ip4_datagram_connect return error
if (sk->__sk_common.skc_dport == 0) {
return 0;
}
// Allocate space for the event.
struct Event *udp_info;
udp_info = bpf_ringbuf_reserve(&pm_connection_events, sizeof(struct Event), 0);
if (!udp_info) {
return 0;
}
// Read PID
udp_info->pid = __builtin_bswap32((u32)bpf_get_current_pid_tgid());
// Set src and dist ports
udp_info->sport = __builtin_bswap16(sk->__sk_common.skc_num);
udp_info->dport = sk->__sk_common.skc_dport;
// Set src and dist IPs
udp_info->saddr[0] = __builtin_bswap32(sk->__sk_common.skc_rcv_saddr);
udp_info->daddr[0] = __builtin_bswap32(sk->__sk_common.skc_daddr);
// Set IP version
udp_info->ipVersion = 4;
// Set protocol. No way to detect udplite for ipv4
udp_info->protocol = UDP;
// Send event
bpf_ringbuf_submit(udp_info, 0);
return 0;
}
// Fentry(function enter) of udp_v6_connect will be executed after the ip6_datagram_connect kernel function is called.
// ip6_datagram_connect -> udp_v6_connect
SEC("fexit/ip6_datagram_connect")
int BPF_PROG(udp_v6_connect, struct sock *sk) {
// Ignore everything else then IPv6
if (sk->__sk_common.skc_family != AF_INET6) {
return 0;
}
// ip6_datagram_connect return error
if (sk->__sk_common.skc_dport == 0) {
return 0;
}
// Make sure its udp6 socket
struct udp6_sock *us = bpf_skc_to_udp6_sock(sk);
if (!us) {
return 0;
}
// Allocate space for the event.
struct Event *udp_info;
udp_info = bpf_ringbuf_reserve(&pm_connection_events, sizeof(struct Event), 0);
if (!udp_info) {
return 0;
}
// Read PID
udp_info->pid = __builtin_bswap32((u32)bpf_get_current_pid_tgid());
// Set src and dist ports
udp_info->sport = __builtin_bswap16(sk->__sk_common.skc_num);
udp_info->dport = sk->__sk_common.skc_dport;
// Set src and dist IPs
for(int i = 0; i < 4; i++) {
udp_info->saddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32[i]);
}
for(int i = 0; i < 4; i++) {
udp_info->daddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32[i]);
}
// IP version
udp_info->ipVersion = 6;
// Set protocol for UDPLite
if(us->udp.pcflag == 0) {
udp_info->protocol = UDP;
} else {
udp_info->protocol = UDPLite;
}
// Send event
bpf_ringbuf_submit(udp_info, 0);
return 0;
}

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Version of libbpf to fetch headers from
LIBBPF_VERSION=1.2.0
# The headers we want
prefix=libbpf-"$LIBBPF_VERSION"
headers=(
"$prefix"/src/bpf_core_read.h
"$prefix"/src/bpf_helper_defs.h
"$prefix"/src/bpf_helpers.h
"$prefix"/src/bpf_tracing.h
)
# Fetch libbpf release and extract the desired headers
curl -sL "https://github.com/libbpf/libbpf/archive/refs/tags/v${LIBBPF_VERSION}.tar.gz" | \
tar -xz --xform='s#.*/#bpf/#' "${headers[@]}"

File diff suppressed because it is too large Load Diff

View File

@@ -9,13 +9,13 @@ import (
)
// start starts the interception.
func start(_ chan packet.Packet) error {
func startInterception(_ chan packet.Packet) error {
log.Critical("interception: this platform has no support for packet interception - a lot of functionality will be broken")
return nil
}
// stop starts the interception.
func stop() error {
func stopInterception() error {
return nil
}

View File

@@ -1,18 +1,39 @@
package interception
import (
"context"
"time"
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"
)
// start starts the interception.
func start(ch chan packet.Packet) error {
return StartNfqueueInterception(ch)
func startInterception(packets chan packet.Packet) error {
// Start packet interception via nfqueue.
err := StartNfqueueInterception(packets)
if err != nil {
return err
}
// Start ebpf new connection listener.
module.StartServiceWorker("ebpf connection listener", 0, func(ctx context.Context) error {
return conn_listener.ConnectionListenerWorker(ctx, packets)
})
// Start ebpf bandwidth stats monitor.
module.StartServiceWorker("ebpf bandwidth stats monitor", 0, func(ctx context.Context) error {
return bandwidth.BandwidthStatsWorker(ctx, 1*time.Second, BandwidthUpdates)
})
return nil
}
// stop starts the interception.
func stop() error {
func stopInterception() error {
return StopNfqueueInterception()
}

View File

@@ -1,7 +1,9 @@
package interception
import (
"context"
"fmt"
"time"
"github.com/safing/portmaster/firewall/interception/windowskext"
"github.com/safing/portmaster/network"
@@ -10,7 +12,7 @@ import (
)
// start starts the interception.
func start(ch chan packet.Packet) error {
func startInterception(packets chan packet.Packet) error {
kextFile, err := updates.GetPlatformFile("kext/portmaster-kext.sys")
if err != nil {
return fmt.Errorf("interception: could not get kext sys: %s", err)
@@ -26,13 +28,22 @@ func start(ch chan packet.Packet) error {
return fmt.Errorf("interception: could not start windows kext: %s", err)
}
go windowskext.Handler(ch)
// Start packet handler.
module.StartServiceWorker("kext packet handler", 0, func(ctx context.Context) error {
windowskext.Handler(ctx, packets)
return nil
})
// Start bandwidth stats monitor.
module.StartServiceWorker("kext bandwidth stats monitor", 0, func(ctx context.Context) error {
return windowskext.BandwidthStatsWorker(ctx, 1*time.Second, BandwidthUpdates)
})
return nil
}
// stop starts the interception.
func stop() error {
func stopInterception() error {
return windowskext.Stop()
}

View File

@@ -4,22 +4,34 @@ import (
"flag"
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portmaster/network/packet"
)
var (
// Packets channel for feeding the firewall.
module *modules.Module
// Packets is a stream of interception network packest.
Packets = make(chan packet.Packet, 1000)
// BandwidthUpdates is a stream of bandwidth usage update for connections.
BandwidthUpdates = make(chan *packet.BandwidthUpdate, 1000)
disableInterception bool
)
func init() {
flag.BoolVar(&disableInterception, "disable-interception", false, "disable packet interception; this breaks a lot of functionality")
module = modules.Register("interception", prep, start, stop, "base", "updates", "network", "notifications", "profiles")
}
func prep() error {
return nil
}
// Start starts the interception.
func Start() error {
func start() error {
if disableInterception {
log.Warning("interception: packet interception is disabled via flag - this breaks a lot of functionality")
return nil
@@ -36,16 +48,16 @@ func Start() error {
}()
}
return start(inputPackets)
return startInterception(inputPackets)
}
// Stop starts the interception.
func Stop() error {
func stop() error {
if disableInterception {
return nil
}
close(metrics.done)
return stop()
return stopInterception()
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/safing/portbase/log"
pmpacket "github.com/safing/portmaster/network/packet"
"github.com/safing/portmaster/process"
)
// Queue wraps a nfqueue.
@@ -175,10 +176,11 @@ func (q *Queue) packetHandler(ctx context.Context) func(nfqueue.Attribute) int {
pkt := &packet{
pktID: *attrs.PacketID,
queue: q,
received: time.Now(),
verdictSet: make(chan struct{}),
verdictPending: abool.New(),
}
pkt.Info().PID = process.UndefinedProcessID
pkt.Info().SeenAt = time.Now()
if attrs.Payload == nil {
// There is not payload.
@@ -194,11 +196,11 @@ func (q *Queue) packetHandler(ctx context.Context) func(nfqueue.Attribute) int {
select {
case q.packets <- pkt:
log.Tracef("nfqueue: queued packet %s (%s -> %s) after %s", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, time.Since(pkt.received))
log.Tracef("nfqueue: queued packet %s (%s -> %s) after %s", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, time.Since(pkt.Info().SeenAt))
case <-ctx.Done():
return 0
case <-time.After(time.Second):
log.Warningf("nfqueue: failed to queue packet (%s since it was handed over by the kernel)", time.Since(pkt.received))
log.Warningf("nfqueue: failed to queue packet (%s since it was handed over by the kernel)", time.Since(pkt.Info().SeenAt))
}
go func() {
@@ -206,7 +208,7 @@ func (q *Queue) packetHandler(ctx context.Context) func(nfqueue.Attribute) int {
case <-pkt.verdictSet:
case <-time.After(20 * time.Second):
log.Warningf("nfqueue: no verdict set for packet %s (%s -> %s) after %s, dropping", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, time.Since(pkt.received))
log.Warningf("nfqueue: no verdict set for packet %s (%s -> %s) after %s, dropping", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, time.Since(pkt.Info().SeenAt))
if err := pkt.Drop(); err != nil {
log.Warningf("nfqueue: failed to apply default-drop to unveridcted packet %s (%s -> %s)", pkt.ID(), pkt.Info().Src, pkt.Info().Dst)
}

View File

@@ -55,7 +55,6 @@ func markToString(mark int) string {
type packet struct {
pmpacket.Base
pktID uint32
received time.Time
queue *Queue
verdictSet chan struct{}
verdictPending *abool.AtomicBool
@@ -118,7 +117,7 @@ func (pkt *packet) setMark(mark int) error {
}
break
}
log.Tracer(pkt.Ctx()).Tracef("nfqueue: marking packet %s (%s -> %s) on queue %d with %s after %s", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, pkt.queue.id, markToString(mark), time.Since(pkt.received))
log.Tracer(pkt.Ctx()).Tracef("nfqueue: marking packet %s (%s -> %s) on queue %d with %s after %s", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, pkt.queue.id, markToString(mark), time.Since(pkt.Info().SeenAt))
return nil
}

View File

@@ -1,6 +1,7 @@
package interception
import (
"context"
"flag"
"fmt"
"sort"
@@ -257,30 +258,30 @@ func StartNfqueueInterception(packets chan<- packet.Packet) (err error) {
err = activateNfqueueFirewall()
if err != nil {
_ = Stop()
_ = StopNfqueueInterception()
return fmt.Errorf("could not initialize nfqueue: %w", err)
}
out4Queue, err = nfq.New(17040, false)
if err != nil {
_ = Stop()
_ = StopNfqueueInterception()
return fmt.Errorf("nfqueue(IPv4, out): %w", err)
}
in4Queue, err = nfq.New(17140, false)
if err != nil {
_ = Stop()
_ = StopNfqueueInterception()
return fmt.Errorf("nfqueue(IPv4, in): %w", err)
}
if netenv.IPv6Enabled() {
out6Queue, err = nfq.New(17060, true)
if err != nil {
_ = Stop()
_ = StopNfqueueInterception()
return fmt.Errorf("nfqueue(IPv6, out): %w", err)
}
in6Queue, err = nfq.New(17160, true)
if err != nil {
_ = Stop()
_ = StopNfqueueInterception()
return fmt.Errorf("nfqueue(IPv6, in): %w", err)
}
} else {
@@ -289,7 +290,9 @@ func StartNfqueueInterception(packets chan<- packet.Packet) (err error) {
in6Queue = &disabledNfQueue{}
}
go handleInterception(packets)
module.StartServiceWorker("nfqueue packet handler", 0, func(_ context.Context) error {
return handleInterception(packets)
})
return nil
}
@@ -318,12 +321,12 @@ func StopNfqueueInterception() error {
return nil
}
func handleInterception(packets chan<- packet.Packet) {
func handleInterception(packets chan<- packet.Packet) error {
for {
var pkt packet.Packet
select {
case <-shutdownSignal:
return
return nil
case pkt = <-out4Queue.PacketChannel():
pkt.SetOutbound()
case pkt = <-in4Queue.PacketChannel():
@@ -337,7 +340,7 @@ func handleInterception(packets chan<- packet.Packet) {
select {
case packets <- pkt:
case <-shutdownSignal:
return
return nil
}
}
}

View File

@@ -0,0 +1,129 @@
//go:build windows
// +build windows
package windowskext
// This file contains example code how to read bandwidth stats from the kext. Its not ment to be used in production.
import (
"context"
"time"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/network/packet"
)
type Rxtxdata struct {
rx uint64
tx uint64
}
type Key struct {
localIP [4]uint32
remoteIP [4]uint32
localPort uint16
remotePort uint16
ipv6 bool
protocol uint8
}
var m = make(map[Key]Rxtxdata)
func BandwidthStatsWorker(ctx context.Context, collectInterval time.Duration, bandwidthUpdates chan *packet.BandwidthUpdate) error {
// Setup ticker.
ticker := time.NewTicker(collectInterval)
defer ticker.Stop()
// Collect bandwidth at every tick.
for {
select {
case <-ticker.C:
err := reportBandwidth(ctx, bandwidthUpdates)
if err != nil {
return err
}
case <-ctx.Done():
return nil
}
}
}
func reportBandwidth(ctx context.Context, bandwidthUpdates chan *packet.BandwidthUpdate) error {
stats, err := GetConnectionsStats()
if err != nil {
return err
}
// Report all statistics.
for _, stat := range stats {
connID := packet.CreateConnectionID(
packet.IPProtocol(stat.protocol),
convertArrayToIP(stat.localIP, stat.ipV6 == 1), stat.localPort,
convertArrayToIP(stat.remoteIP, stat.ipV6 == 1), stat.remotePort,
false,
)
update := &packet.BandwidthUpdate{
ConnID: connID,
RecvBytes: stat.receivedBytes,
SentBytes: stat.transmittedBytes,
Method: packet.Additive,
}
select {
case bandwidthUpdates <- update:
case <-ctx.Done():
return nil
}
}
return nil
}
func StartBandwithConsoleLogger() {
go func() {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for range ticker.C {
conns, err := GetConnectionsStats()
if err != nil {
continue
}
for _, conn := range conns {
if conn.receivedBytes == 0 && conn.transmittedBytes == 0 {
continue
}
key := Key{
localIP: conn.localIP,
remoteIP: conn.remoteIP,
localPort: conn.localPort,
remotePort: conn.remotePort,
ipv6: conn.ipV6 == 1,
protocol: conn.protocol,
}
// First we get a "copy" of the entry
if entry, ok := m[key]; ok {
// Then we modify the copy
entry.rx += conn.receivedBytes
entry.tx += conn.transmittedBytes
// Then we reassign map entry
m[key] = entry
} else {
m[key] = Rxtxdata{
rx: conn.receivedBytes,
tx: conn.transmittedBytes,
}
}
}
log.Debug("----------------------------------")
for key, value := range m {
log.Debugf(
"Conn: %d %s:%d %s:%d rx:%d tx:%d", key.protocol,
convertArrayToIP(key.localIP, key.ipv6), key.localPort,
convertArrayToIP(key.remoteIP, key.ipv6), key.remotePort,
value.rx, value.tx,
)
}
}
}()
}

View File

@@ -4,12 +4,16 @@
package windowskext
import (
"context"
"encoding/binary"
"errors"
"fmt"
"net"
"time"
"unsafe"
"github.com/safing/portmaster/process"
"github.com/tevino/abool"
"github.com/safing/portbase/log"
@@ -27,13 +31,18 @@ const (
// connection that was intercepted on an ALE layer instead of in the network
// stack itself. Thus, no packet data is available.
VerdictRequestFlagSocketAuth = 2
// VerdictRequestFlagExpectSocketAuth indicates that the next verdict
// requests is expected to be an informational socket auth request from
// the ALE layer.
VerdictRequestFlagExpectSocketAuth = 4
)
// Do not change the order of the members! The structure is used to communicate with the kernel extension.
// VerdictRequest is the request structure from the Kext.
type VerdictRequest struct {
id uint32 // ID from RegisterPacket
_ uint64 // Process ID - does not yet work
pid uint64 // Process ID - info only packets
direction uint8
ipV6 uint8 // True: IPv6, False: IPv4
protocol uint8 // Protocol
@@ -65,6 +74,17 @@ type VerdictUpdateInfo struct {
verdict uint8 // New verdict
}
type ConnectionStat struct {
localIP [4]uint32 //Source Address, only srcIP[0] if IPv4
remoteIP [4]uint32 //Destination Address
localPort uint16 //Source Port
remotePort uint16 //Destination port
receivedBytes uint64 //Number of bytes recived on this connection
transmittedBytes uint64 //Number of bytes transsmited from this connection
ipV6 uint8 //True: IPv6, False: IPv4
protocol uint8 //Protocol (UDP, TCP, ...)
}
type VersionInfo struct {
major uint8
minor uint8
@@ -77,9 +97,7 @@ func (v *VersionInfo) String() string {
}
// Handler transforms received packets to the Packet interface.
func Handler(packets chan packet.Packet) {
defer close(packets)
func Handler(ctx context.Context, packets chan packet.Packet) {
for {
packetInfo, err := RecvVerdictRequest()
if err != nil {
@@ -103,45 +121,39 @@ func Handler(packets chan packet.Packet) {
verdictRequest: packetInfo,
verdictSet: abool.NewBool(false),
}
info := new.Info()
info.Inbound = packetInfo.direction > 0
info.InTunnel = false
info.Protocol = packet.IPProtocol(packetInfo.protocol)
info.PID = int(packetInfo.pid)
info.SeenAt = time.Now()
// IP version
// Check PID
if info.PID == 0 {
// Windows does not have zero PIDs.
// Set to UndefinedProcessID.
info.PID = process.UndefinedProcessID
}
// Set IP version
if packetInfo.ipV6 == 1 {
info.Version = packet.IPv6
} else {
info.Version = packet.IPv4
}
// IPs
if info.Version == packet.IPv4 {
// IPv4
if info.Inbound {
// Inbound
info.Src = convertIPv4(packetInfo.remoteIP)
info.Dst = convertIPv4(packetInfo.localIP)
} else {
// Outbound
info.Src = convertIPv4(packetInfo.localIP)
info.Dst = convertIPv4(packetInfo.remoteIP)
}
// Set IPs
if info.Inbound {
// Inbound
info.Src = convertArrayToIP(packetInfo.remoteIP, info.Version == packet.IPv6)
info.Dst = convertArrayToIP(packetInfo.localIP, info.Version == packet.IPv6)
} else {
// IPv6
if info.Inbound {
// Inbound
info.Src = convertIPv6(packetInfo.remoteIP)
info.Dst = convertIPv6(packetInfo.localIP)
} else {
// Outbound
info.Src = convertIPv6(packetInfo.localIP)
info.Dst = convertIPv6(packetInfo.remoteIP)
}
// Outbound
info.Src = convertArrayToIP(packetInfo.localIP, info.Version == packet.IPv6)
info.Dst = convertArrayToIP(packetInfo.remoteIP, info.Version == packet.IPv6)
}
// Ports
// Set Ports
if info.Inbound {
// Inbound
info.SrcPort = packetInfo.remotePort
@@ -156,14 +168,14 @@ func Handler(packets chan packet.Packet) {
}
}
// convertIPv4 as needed for data from the kernel
func convertIPv4(input [4]uint32) net.IP {
addressBuf := make([]byte, 4)
binary.BigEndian.PutUint32(addressBuf, input[0])
return net.IP(addressBuf)
}
// 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.BigEndian.PutUint32(addressBuf, input[0])
return net.IP(addressBuf)
}
func convertIPv6(input [4]uint32) net.IP {
addressBuf := make([]byte, 16)
for i := 0; i < 4; i++ {
binary.BigEndian.PutUint32(addressBuf[i*4:i*4+4], input[i])

View File

@@ -8,7 +8,6 @@ import (
"fmt"
"sync"
"syscall"
"time"
"unsafe"
"github.com/safing/portbase/log"
@@ -127,8 +126,10 @@ func RecvVerdictRequest() (*VerdictRequest, error) {
return nil, ErrKextNotReady
}
timestamp := time.Now()
defer log.Tracef("winkext: getting verdict request took %s", time.Since(timestamp))
// DEBUG:
// timestamp := time.Now()
// defer log.Tracef("winkext: getting verdict request took %s", time.Since(timestamp))
// Initialize struct for the output data
var new VerdictRequest
@@ -147,6 +148,9 @@ func RecvVerdictRequest() (*VerdictRequest, error) {
// SetVerdict sets the verdict for a packet and/or connection.
func SetVerdict(pkt *Packet, verdict network.Verdict) error {
if pkt.verdictRequest.pid != 0 {
return nil // Ignore info only packets
}
if pkt.verdictRequest.id == 0 {
log.Tracer(pkt.Ctx()).Errorf("kext: failed to set verdict %s: no packet ID", verdict)
return ErrNoPacketID
@@ -285,6 +289,29 @@ func GetVersion() (*VersionInfo, error) {
return version, nil
}
var sizeOfConnectionStat = uint32(unsafe.Sizeof(ConnectionStat{}))
func GetConnectionsStats() ([]ConnectionStat, error) {
kextLock.RLock()
defer kextLock.RUnlock()
// Check if driver is initialized
if kextHandle == winInvalidHandleValue {
log.Error("kext: failed to clear the cache: kext not ready")
return nil, ErrKextNotReady
}
var data [100]ConnectionStat
size := len(data)
bytesReturned, err := deviceIOControl(kextHandle, IOCTL_GET_CONNECTIONS_STAT, asByteArray(&size), asByteArray(&data))
if err != nil {
return nil, err
}
return data[:bytesReturned/sizeOfConnectionStat], nil
}
func openDriver(filename string) (windows.Handle, error) {
u16filename, err := syscall.UTF16FromString(filename)
if err != nil {

View File

@@ -1,3 +1,4 @@
//go:build windows
// +build windows
package windowskext
@@ -29,6 +30,17 @@ func (pkt *Packet) FastTrackedByIntegration() bool {
return pkt.verdictRequest.flags&VerdictRequestFlagFastTrackPermitted > 0
}
// InfoOnly returns whether the packet is informational only and does not
// represent an actual packet.
func (pkt *Packet) InfoOnly() bool {
return pkt.verdictRequest.flags&VerdictRequestFlagSocketAuth > 0
}
// ExpectInfo returns whether the next packet is expected to be informational only.
func (pkt *Packet) ExpectInfo() bool {
return pkt.verdictRequest.flags&VerdictRequestFlagExpectSocketAuth > 0
}
// GetPayload returns the full raw packet.
func (pkt *Packet) LoadPacketData() error {
pkt.lock.Lock()

View File

@@ -13,46 +13,47 @@ const (
SIOCTL_TYPE = 40000
)
var (
IOCTL_VERSION = ctlCode(SIOCTL_TYPE, 0x800, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_SHUTDOWN_REQUEST = ctlCode(SIOCTL_TYPE, 0x801, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_RECV_VERDICT_REQ = ctlCode(SIOCTL_TYPE, 0x802, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_SET_VERDICT = ctlCode(SIOCTL_TYPE, 0x803, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_GET_PAYLOAD = ctlCode(SIOCTL_TYPE, 0x804, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_CLEAR_CACHE = ctlCode(SIOCTL_TYPE, 0x805, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_UPDATE_VERDICT = ctlCode(SIOCTL_TYPE, 0x806, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
)
func ctlCode(device_type, function, method, access uint32) uint32 {
return (device_type << 16) | (access << 14) | (function << 2) | method
}
func deviceIOControlAsync(handle windows.Handle, code uint32, inData []byte, outData []byte) (*windows.Overlapped, error) {
var inDataPtr *byte = nil
var inDataSize uint32 = 0
if inData != nil {
inDataPtr = &inData[0]
inDataSize = uint32(len(inData))
}
var outDataPtr *byte = nil
var outDataSize uint32 = 0
if outData != nil {
outDataPtr = &outData[0]
outDataSize = uint32(len(outData))
}
overlapped := &windows.Overlapped{}
err := windows.DeviceIoControl(handle,
code,
inDataPtr, inDataSize,
outDataPtr, outDataSize,
nil, overlapped)
if err != nil {
return nil, err
}
var (
IOCTL_VERSION = ctlCode(SIOCTL_TYPE, 0x800, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_SHUTDOWN_REQUEST = ctlCode(SIOCTL_TYPE, 0x801, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_RECV_VERDICT_REQ = ctlCode(SIOCTL_TYPE, 0x802, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_SET_VERDICT = ctlCode(SIOCTL_TYPE, 0x803, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_GET_PAYLOAD = ctlCode(SIOCTL_TYPE, 0x804, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_CLEAR_CACHE = ctlCode(SIOCTL_TYPE, 0x805, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_UPDATE_VERDICT = ctlCode(SIOCTL_TYPE, 0x806, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
IOCTL_GET_CONNECTIONS_STAT = ctlCode(SIOCTL_TYPE, 0x807, METHOD_BUFFERED, windows.FILE_READ_DATA|windows.FILE_WRITE_DATA)
)
func ctlCode(device_type, function, method, access uint32) uint32 {
return (device_type << 16) | (access << 14) | (function << 2) | method
}
func deviceIOControlAsync(handle windows.Handle, code uint32, inData []byte, outData []byte) (*windows.Overlapped, error) {
var inDataPtr *byte = nil
var inDataSize uint32 = 0
if inData != nil {
inDataPtr = &inData[0]
inDataSize = uint32(len(inData))
}
var outDataPtr *byte = nil
var outDataSize uint32 = 0
if outData != nil {
outDataPtr = &outData[0]
outDataSize = uint32(len(outData))
}
overlapped := &windows.Overlapped{}
err := windows.DeviceIoControl(handle,
code,
inDataPtr, inDataSize,
outDataPtr, outDataSize,
nil, overlapped)
if err != nil {
return nil, err
}
return overlapped, nil

View File

@@ -25,19 +25,6 @@ import (
"github.com/safing/portmaster/profile/endpoints"
)
// Call order:
//
// DNS Query:
// 1. DecideOnConnection
// is called when a DNS query is made, may set verdict to Undeterminable to permit a DNS reply.
// is called with a nil packet.
// 2. DecideOnResolvedDNS
// is called to (possibly) filter out A/AAAA records that the filter would deny later.
//
// Network Connection:
// 3. DecideOnConnection
// is called with the first packet of a network connection.
const noReasonOptionKey = ""
type deciderFn func(context.Context, *network.Connection, *profile.LayeredProfile, packet.Packet) bool

View File

@@ -1,21 +0,0 @@
package firewall
import (
"github.com/safing/portbase/api"
"github.com/safing/portbase/config"
"github.com/safing/portbase/metrics"
)
var packetHandlingHistogram *metrics.Histogram
func registerMetrics() (err error) {
packetHandlingHistogram, err = metrics.NewHistogram(
"firewall/handling/duration/seconds",
nil,
&metrics.Options{
Permission: api.PermitUser,
ExpertiseLevel: config.ExpertiseLevelExpert,
})
return err
}

116
firewall/module.go Normal file
View File

@@ -0,0 +1,116 @@
package firewall
import (
"context"
"github.com/safing/portbase/config"
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portbase/modules/subsystems"
_ "github.com/safing/portmaster/core"
"github.com/safing/portmaster/network"
)
var module *modules.Module
func init() {
module = modules.Register("filter", prep, start, stop, "core", "interception", "intel")
subsystems.Register(
"filter",
"Privacy Filter",
"DNS and Network Filter",
module,
"config:filter/",
&config.Option{
Name: "Privacy Filter Module",
Key: CfgOptionEnableFilterKey,
Description: "Start the Privacy Filter module. If turned off, all privacy filter protections are fully disabled on this device.",
OptType: config.OptTypeBool,
ExpertiseLevel: config.ExpertiseLevelDeveloper,
ReleaseLevel: config.ReleaseLevelStable,
DefaultValue: true,
Annotations: config.Annotations{
config.CategoryAnnotation: "General",
},
},
)
}
const (
configChangeEvent = "config change"
profileConfigChangeEvent = "profile config change"
onSPNConnectEvent = "spn connect"
)
func prep() error {
network.SetDefaultFirewallHandler(verdictHandler)
// Reset connections every time configuration changes
// this will be triggered on spn enable/disable
err := module.RegisterEventHook(
"config",
configChangeEvent,
"reset connection verdicts",
func(ctx context.Context, _ interface{}) error {
resetAllConnectionVerdicts()
return nil
},
)
if err != nil {
log.Errorf("interception: failed registering event hook: %s", err)
}
// Reset connections every time profile changes
err = module.RegisterEventHook(
"profiles",
profileConfigChangeEvent,
"reset connection verdicts",
func(ctx context.Context, _ interface{}) error {
resetAllConnectionVerdicts()
return nil
},
)
if err != nil {
log.Errorf("failed registering event hook: %s", err)
}
// Reset connections when spn is connected
// connect and disconnecting is triggered on config change event but connecting takеs more time
err = module.RegisterEventHook(
"captain",
onSPNConnectEvent,
"reset connection verdicts",
func(ctx context.Context, _ interface{}) error {
resetAllConnectionVerdicts()
return nil
},
)
if err != nil {
log.Errorf("failed registering event hook: %s", err)
}
if err := registerConfig(); err != nil {
return err
}
return prepAPIAuth()
}
func start() error {
getConfig()
startAPIAuth()
module.StartServiceWorker("packet handler", 0, packetHandler)
module.StartServiceWorker("bandwidth update handler", 0, bandwidthUpdateHandler)
// Start stat logger if logging is set to trace.
if log.GetLogLevel() == log.TraceLevel {
module.StartServiceWorker("stat logger", 0, statLogger)
}
return nil
}
func stop() error {
return nil
}

View File

@@ -11,10 +11,8 @@ import (
"github.com/google/gopacket/layers"
"github.com/tevino/abool"
"golang.org/x/sync/singleflight"
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portmaster/compat"
_ "github.com/safing/portmaster/core/base"
"github.com/safing/portmaster/firewall/inspection"
@@ -27,8 +25,6 @@ import (
)
var (
interceptionModule *modules.Module
nameserverIPMatcher func(ip net.IP) bool
nameserverIPMatcherSet = abool.New()
nameserverIPMatcherReady = abool.New()
@@ -44,71 +40,6 @@ var (
ownPID = os.Getpid()
)
const (
configChangeEvent = "config change"
profileConfigChangeEvent = "profile config change"
onSPNConnectEvent = "spn connect"
)
func init() {
// TODO: Move interception module to own package (dir).
interceptionModule = modules.Register("interception", interceptionPrep, interceptionStart, interceptionStop, "base", "updates", "network", "notifications", "profiles")
network.SetDefaultFirewallHandler(defaultHandler)
}
func interceptionPrep() error {
// Reset connections every time configuration changes
// this will be triggered on spn enable/disable
err := interceptionModule.RegisterEventHook(
"config",
configChangeEvent,
"reset connection verdicts",
func(ctx context.Context, _ interface{}) error {
resetAllConnectionVerdicts()
return nil
},
)
if err != nil {
log.Errorf("interception: failed registering event hook: %s", err)
}
// Reset connections every time profile changes
err = interceptionModule.RegisterEventHook(
"profiles",
profileConfigChangeEvent,
"reset connection verdicts",
func(ctx context.Context, _ interface{}) error {
resetAllConnectionVerdicts()
return nil
},
)
if err != nil {
log.Errorf("failed registering event hook: %s", err)
}
// Reset connections when spn is connected
// connect and disconnecting is triggered on config change event but connecting takеs more time
err = interceptionModule.RegisterEventHook(
"captain",
onSPNConnectEvent,
"reset connection verdicts",
func(ctx context.Context, _ interface{}) error {
resetAllConnectionVerdicts()
return nil
},
)
if err != nil {
log.Errorf("failed registering event hook: %s", err)
}
if err := registerConfig(); err != nil {
return err
}
return prepAPIAuth()
}
func resetAllConnectionVerdicts() {
// Resetting will force all the connection to be evaluated by the firewall again
// this will set new verdicts if configuration was update or spn has been disabled or enabled.
@@ -120,6 +51,11 @@ func resetAllConnectionVerdicts() {
// Re-evaluate all connections.
var changedVerdicts int
for _, conn := range network.GetAllConnections() {
// Skip incomplete connections.
if !conn.DataIsComplete() {
continue
}
func() {
conn.Lock()
defer conn.Unlock()
@@ -165,25 +101,6 @@ func resetAllConnectionVerdicts() {
tracer.Submit()
}
func interceptionStart() error {
getConfig()
if err := registerMetrics(); err != nil {
return err
}
startAPIAuth()
interceptionModule.StartWorker("stat logger", statLogger)
interceptionModule.StartWorker("packet handler", packetHandler)
return interception.Start()
}
func interceptionStop() error {
return interception.Stop()
}
// SetNameserverIPMatcher sets a function that is used to match the internal
// nameserver IP(s). Can only bet set once.
func SetNameserverIPMatcher(fn func(ip net.IP) bool) error {
@@ -196,92 +113,38 @@ func SetNameserverIPMatcher(fn func(ip net.IP) bool) error {
return nil
}
func handlePacket(ctx context.Context, pkt packet.Packet) {
// Record metrics.
startTime := time.Now()
defer packetHandlingHistogram.UpdateDuration(startTime)
if fastTrackedPermit(pkt) {
func handlePacket(pkt packet.Packet) {
// First, check for an existing connection.
conn, ok := network.GetConnection(pkt.GetConnectionID())
if ok {
// Add packet to connection handler queue or apply verdict directly.
conn.HandlePacket(pkt)
return
}
// Add context tracer and set context on packet.
traceCtx, tracer := log.AddTracer(ctx)
if tracer != nil {
// The trace is submitted in `network.Connection.packetHandler()`.
tracer.Tracef("filter: handling packet: %s", pkt)
}
pkt.SetCtx(traceCtx)
// Else create new incomplete connection from the packet and start the new handler.
conn = network.NewIncompleteConnection(pkt)
conn.Lock()
defer conn.Unlock()
conn.SetFirewallHandler(fastTrackHandler)
// Get connection of packet.
conn, err := getConnection(pkt)
if err != nil {
tracer.Errorf("filter: packet %s dropped: %s", pkt, err)
_ = pkt.Drop()
return
}
// handle packet
// Let the new connection handler worker handle the packet.
conn.HandlePacket(pkt)
}
var getConnectionSingleInflight singleflight.Group
func getConnection(pkt packet.Packet) (*network.Connection, error) {
created := false
// Create or get connection in single inflight lock in order to prevent duplicates.
newConn, err, shared := getConnectionSingleInflight.Do(pkt.GetConnectionID(), func() (interface{}, error) {
// First, check for an existing connection.
conn, ok := network.GetConnection(pkt.GetConnectionID())
if ok {
return conn, nil
}
// Else create new one from the packet.
conn = network.NewConnectionFromFirstPacket(pkt)
conn.Lock()
defer conn.Unlock()
conn.SetFirewallHandler(initialHandler)
created = true
return conn, nil
})
if err != nil {
return nil, fmt.Errorf("failed to get connection: %w", err)
}
if newConn == nil {
return nil, errors.New("connection getter returned nil")
}
// Transform and log result.
conn := newConn.(*network.Connection) //nolint:forcetypeassert // Can only be a *network.Connection.
sharedIndicator := ""
if shared {
sharedIndicator = " (shared)"
}
if created {
log.Tracer(pkt.Ctx()).Tracef("filter: created new connection %s%s", conn.ID, sharedIndicator)
} else {
log.Tracer(pkt.Ctx()).Tracef("filter: assigned connection %s%s", conn.ID, sharedIndicator)
}
return conn, nil
}
// fastTrackedPermit quickly permits certain network critical or internal connections.
func fastTrackedPermit(pkt packet.Packet) (handled bool) {
func fastTrackedPermit(pkt packet.Packet) (verdict network.Verdict, permanent bool) {
meta := pkt.Info()
// Check if packed was already fast-tracked by the OS integration.
if pkt.FastTrackedByIntegration() {
log.Debugf("filter: fast-tracked by OS integration: %s", pkt)
return true
return network.VerdictAccept, true
}
// Check if connection was already blocked.
if meta.Dst.Equal(blockedIPv4) || meta.Dst.Equal(blockedIPv6) {
_ = pkt.PermanentBlock()
return true
return network.VerdictBlock, true
}
// Some programs do a network self-check where they connects to the same
@@ -290,8 +153,8 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
if meta.SrcPort == meta.DstPort &&
meta.Src.Equal(meta.Dst) {
log.Debugf("filter: fast-track network self-check: %s", pkt)
_ = pkt.PermanentAccept()
return true
return network.VerdictAccept, true
}
switch meta.Protocol { //nolint:exhaustive // Checking for specific values only.
@@ -300,8 +163,7 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
err := pkt.LoadPacketData()
if err != nil {
log.Debugf("filter: failed to load ICMP packet data: %s", err)
_ = pkt.PermanentAccept()
return true
return network.VerdictAccept, true
}
// Submit to ICMP listener.
@@ -311,8 +173,7 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
// permanent accept, because then we won't see any future packets of that
// connection and thus cannot continue to submit them.
log.Debugf("filter: fast-track tracing ICMP/v6: %s", pkt)
_ = pkt.Accept()
return true
return network.VerdictAccept, false
}
// Handle echo request and replies regularly.
@@ -323,20 +184,19 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
switch icmpLayer.TypeCode.Type() {
case layers.ICMPv4TypeEchoRequest,
layers.ICMPv4TypeEchoReply:
return false
return network.VerdictUndecided, false
}
case *layers.ICMPv6:
switch icmpLayer.TypeCode.Type() {
case layers.ICMPv6TypeEchoRequest,
layers.ICMPv6TypeEchoReply:
return false
return network.VerdictUndecided, false
}
}
// Permit all ICMP/v6 packets that are not echo requests or replies.
log.Debugf("filter: fast-track accepting ICMP/v6: %s", pkt)
_ = pkt.PermanentAccept()
return true
return network.VerdictAccept, true
case packet.UDP, packet.TCP:
switch meta.DstPort {
@@ -346,37 +206,36 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
// DHCP and DHCPv6 must be UDP.
if meta.Protocol != packet.UDP {
return false
return network.VerdictUndecided, false
}
// DHCP is only valid in local network scopes.
switch netutils.ClassifyIP(meta.Dst) { //nolint:exhaustive // Checking for specific values only.
case netutils.HostLocal, netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
default:
return false
return network.VerdictUndecided, false
}
// Log and permit.
log.Debugf("filter: fast-track accepting DHCP: %s", pkt)
_ = pkt.PermanentAccept()
return true
return network.VerdictAccept, true
case apiPort:
// Always allow direct access to the Portmaster API.
// Portmaster API is TCP only.
if meta.Protocol != packet.TCP {
return false
return network.VerdictUndecided, false
}
// Check if the api port is even set.
if !apiPortSet {
return false
return network.VerdictUndecided, false
}
// Must be destined for the API IP.
if !meta.Dst.Equal(apiIP) {
return false
return network.VerdictUndecided, false
}
// Only fast-track local requests.
@@ -384,15 +243,14 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
switch {
case err != nil:
log.Debugf("filter: failed to check if %s is own IP for fast-track: %s", meta.Src, err)
return false
return network.VerdictUndecided, false
case !isMe:
return false
return network.VerdictUndecided, false
}
// Log and permit.
log.Debugf("filter: fast-track accepting api connection: %s", pkt)
_ = pkt.PermanentAccept()
return true
return network.VerdictAccept, true
case 53:
// Always allow direct access to the Portmaster Nameserver.
@@ -400,12 +258,12 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
// Check if a nameserver IP matcher is set.
if !nameserverIPMatcherReady.IsSet() {
return false
return network.VerdictUndecided, false
}
// Check if packet is destined for a nameserver IP.
if !nameserverIPMatcher(meta.Dst) {
return false
return network.VerdictUndecided, false
}
// Only fast-track local requests.
@@ -413,32 +271,76 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
switch {
case err != nil:
log.Debugf("filter: failed to check if %s is own IP for fast-track: %s", meta.Src, err)
return false
return network.VerdictUndecided, false
case !isMe:
return false
return network.VerdictUndecided, false
}
// Log and permit.
log.Debugf("filter: fast-track accepting local dns: %s", pkt)
_ = pkt.PermanentAccept()
return true
return network.VerdictAccept, true
}
case compat.SystemIntegrationCheckProtocol:
if pkt.Info().Dst.Equal(compat.SystemIntegrationCheckDstIP) {
compat.SubmitSystemIntegrationCheckPacket(pkt)
_ = pkt.Drop()
return true
return network.VerdictDrop, false
}
}
return false
return network.VerdictUndecided, false
}
func initialHandler(conn *network.Connection, pkt packet.Packet) {
func fastTrackHandler(conn *network.Connection, pkt packet.Packet) {
fastTrackedVerdict, permanent := fastTrackedPermit(pkt)
if fastTrackedVerdict != network.VerdictUndecided {
// Set verdict on connection.
conn.Verdict.Active = fastTrackedVerdict
conn.Verdict.Firewall = fastTrackedVerdict
// Apply verdict to (real) packet.
if !pkt.InfoOnly() {
issueVerdict(conn, pkt, fastTrackedVerdict, permanent)
}
// Stop handler if permanent.
if permanent {
conn.SetVerdict(fastTrackedVerdict, "fast-tracked", "", nil)
conn.Verdict.Worst = fastTrackedVerdict
// Do not finalize verdict, as we are missing necessary data.
conn.StopFirewallHandler()
}
// Do not continue to next handler.
return
}
// If packet is not fast-tracked, continue with gathering more information.
conn.UpdateFirewallHandler(gatherDataHandler)
gatherDataHandler(conn, pkt)
}
func gatherDataHandler(conn *network.Connection, pkt packet.Packet) {
// Get process info
_ = conn.GatherConnectionInfo(pkt)
// Errors are informational and are logged to the context.
// Run this handler again if data is not yet complete.
if !conn.DataIsComplete() {
return
}
// Continue to filter handler, when connection data is complete.
conn.UpdateFirewallHandler(filterHandler)
filterHandler(conn, pkt)
}
func filterHandler(conn *network.Connection, pkt packet.Packet) {
// Skip if data is not complete.
if !conn.DataIsComplete() {
return
}
filterConnection := true
log.Tracer(pkt.Ctx()).Trace("filter: handing over to connection-based handler")
// Check for special (internal) connection cases.
switch {
case !conn.Inbound && localPortIsPreAuthenticated(conn.Entity.Protocol, conn.LocalPort):
@@ -480,8 +382,8 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
switch {
case conn.Inspecting:
log.Tracer(pkt.Ctx()).Trace("filter: start inspecting")
conn.SetFirewallHandler(inspectThenVerdict)
inspectThenVerdict(conn, pkt)
conn.SetFirewallHandler(inspectAndVerdictHandler)
inspectAndVerdictHandler(conn, pkt)
default:
conn.StopFirewallHandler()
issueVerdict(conn, pkt, 0, true)
@@ -490,6 +392,11 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
// FilterConnection runs all the filtering (and tunneling) procedures.
func FilterConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet, checkFilter, checkTunnel bool) {
// Skip if data is not complete.
if !conn.DataIsComplete() {
return
}
if checkFilter {
if filterEnabled() {
log.Tracer(ctx).Trace("filter: starting decision process")
@@ -537,12 +444,11 @@ func FilterConnection(ctx context.Context, conn *network.Connection, pkt packet.
}
}
func defaultHandler(conn *network.Connection, pkt packet.Packet) {
// TODO: `pkt` has an active trace log, which we currently don't submit.
func verdictHandler(conn *network.Connection, pkt packet.Packet) {
issueVerdict(conn, pkt, 0, true)
}
func inspectThenVerdict(conn *network.Connection, pkt packet.Packet) {
func inspectAndVerdictHandler(conn *network.Connection, pkt packet.Packet) {
pktVerdict, continueInspection := inspection.RunInspectors(conn, pkt)
if continueInspection {
issueVerdict(conn, pkt, pktVerdict, false)
@@ -555,6 +461,11 @@ func inspectThenVerdict(conn *network.Connection, pkt packet.Packet) {
}
func issueVerdict(conn *network.Connection, pkt packet.Packet, verdict network.Verdict, allowPermanent bool) {
// Check if packed was already fast-tracked by the OS integration.
if pkt.FastTrackedByIntegration() {
return
}
// enable permanent verdict
if allowPermanent && !conn.VerdictPermanent {
conn.VerdictPermanent = permanentVerdicts()
@@ -689,14 +600,66 @@ func packetHandler(ctx context.Context) error {
case <-ctx.Done():
return nil
case pkt := <-interception.Packets:
interceptionModule.StartWorker("initial packet handler", func(workerCtx context.Context) error {
handlePacket(workerCtx, pkt)
return nil
})
if pkt != nil {
handlePacket(pkt)
} else {
return errors.New("received nil packet from interception")
}
}
}
}
func bandwidthUpdateHandler(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return nil
case bwUpdate := <-interception.BandwidthUpdates:
if bwUpdate != nil {
updateBandwidth(bwUpdate)
// DEBUG:
// log.Debugf("filter: bandwidth update: %s", bwUpdate)
} else {
return errors.New("received nil bandwidth update from interception")
}
}
}
}
func updateBandwidth(bwUpdate *packet.BandwidthUpdate) {
// Check if update makes sense.
if bwUpdate.RecvBytes == 0 && bwUpdate.SentBytes == 0 {
return
}
// Get connection.
conn, ok := network.GetConnection(bwUpdate.ConnID)
if !ok {
return
}
// Do not wait for connections that are locked.
// TODO: Use atomic operations for updating bandwidth stats.
if !conn.TryLock() {
return
}
defer conn.Unlock()
// Update stats according to method.
switch bwUpdate.Method {
case packet.Absolute:
conn.RecvBytes = bwUpdate.RecvBytes
conn.SentBytes = bwUpdate.SentBytes
case packet.Additive:
conn.RecvBytes += bwUpdate.RecvBytes
conn.SentBytes += bwUpdate.SentBytes
default:
log.Warningf("filter: unsupported bandwidth update method: %d", bwUpdate.Method)
}
// TODO: Send update.
}
func statLogger(ctx context.Context) error {
for {
select {

41
go.mod
View File

@@ -4,6 +4,7 @@ go 1.19
require (
github.com/agext/levenshtein v1.2.3
github.com/cilium/ebpf v0.11.0
github.com/coreos/go-iptables v0.6.0
github.com/florianl/go-conntrack v0.4.0
github.com/florianl/go-nfqueue v1.3.1
@@ -13,23 +14,23 @@ require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-version v1.6.0
github.com/jackc/puddle/v2 v2.2.0
github.com/miekg/dns v1.1.54
github.com/oschwald/maxminddb-golang v1.10.0
github.com/jackc/puddle/v2 v2.2.1
github.com/miekg/dns v1.1.55
github.com/oschwald/maxminddb-golang v1.11.0
github.com/safing/jess v0.3.1
github.com/safing/portbase v0.16.6
github.com/safing/portmaster-android/go v0.0.0-20230414104013-be2efcb51d9d
github.com/safing/portbase v0.17.0
github.com/safing/portmaster-android/go v0.0.0-20230605085256-6abf4c495626
github.com/safing/spn v0.6.8
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spf13/cobra v1.7.0
github.com/spkg/zipfs v0.7.1
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.4
github.com/tannerryan/ring v1.1.2
github.com/tevino/abool v1.2.0
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
golang.org/x/net v0.10.0
golang.org/x/sync v0.2.0
golang.org/x/sys v0.8.0
golang.org/x/net v0.12.0
golang.org/x/sync v0.3.0
golang.org/x/sys v0.10.0
zombiezen.com/go/sqlite v0.13.0
)
@@ -37,7 +38,7 @@ require (
github.com/VictoriaMetrics/metrics v1.24.0 // indirect
github.com/aead/ecdh v0.2.0 // indirect
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/alessio/shellescape v1.4.2 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/awalterschulze/gographviz v2.0.3+incompatible // indirect
github.com/bluele/gcache v0.0.2 // indirect
@@ -65,7 +66,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rot256/pblind v0.0.0-20211117203330-22455f90b565 // indirect
github.com/rot256/pblind v0.0.0-20230622102829-4dc2c6e4b857 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/seehuhn/fortuna v1.0.1 // indirect
github.com/seehuhn/sha256d v1.0.0 // indirect
@@ -75,7 +76,7 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/valyala/fastrand v1.1.0 // indirect
github.com/valyala/histogram v1.2.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
@@ -84,16 +85,16 @@ require (
github.com/yusufpapurcu/wmi v1.2.3 // indirect
github.com/zalando/go-keyring v0.2.3 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.9.1 // indirect
golang.org/x/tools v0.11.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 // indirect
modernc.org/libc v1.22.6 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.22.1 // indirect
modernc.org/libc v1.24.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.6.0 // indirect
modernc.org/sqlite v1.24.0 // indirect
)

89
go.sum
View File

@@ -14,8 +14,9 @@ github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 h1:5L8Mj9Co9sJVgW3TpY
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=
github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
@@ -31,6 +32,8 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
@@ -60,6 +63,7 @@ github.com/florianl/go-conntrack v0.4.0/go.mod h1:iPDx4oIats2T7X7Jm3PFyRCJM1GfZh
github.com/florianl/go-nfqueue v1.3.1 h1:khQ9fYCrjbu5CF8dZF55G2RTIEIQRI0Aj5k3msJR6Gw=
github.com/florianl/go-nfqueue v1.3.1/go.mod h1:aHWbgkhryJxF5XxYvJ3oRZpdD4JP74Zu/hP1zuhja+M=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg=
github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU=
@@ -118,8 +122,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk=
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
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/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
@@ -135,11 +139,11 @@ github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmK
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@@ -172,8 +176,8 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -182,19 +186,19 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0=
github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rot256/pblind v0.0.0-20211117203330-22455f90b565 h1:jVOT0WWSrjQx6pYq48qSJCrlQ3XU1BHvEI6PsRsE9Bc=
github.com/rot256/pblind v0.0.0-20211117203330-22455f90b565/go.mod h1:SI9+Ls7HSJkgYArhh8oBPhXiNL7tJltkU1H6Pm2o8Zo=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rot256/pblind v0.0.0-20230622102829-4dc2c6e4b857 h1:0FEd4UaKVIMBc18S6zAHWtvqpUPcLVhGU2lweJwJhuA=
github.com/rot256/pblind v0.0.0-20230622102829-4dc2c6e4b857/go.mod h1:SI9+Ls7HSJkgYArhh8oBPhXiNL7tJltkU1H6Pm2o8Zo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safing/jess v0.3.0/go.mod h1:JbYsPk5iJZx0OXDZeMcjS9qEdkGVUg+DCA8Fw2LdN9s=
@@ -202,10 +206,10 @@ github.com/safing/jess v0.3.1 h1:cMZVhi2whW/YdD98MPLeLIWJndQ7o2QVt2HefQ/ByFA=
github.com/safing/jess v0.3.1/go.mod h1:aj73Eot1zm2ETkJuw9hJlIO8bRom52uBbsCHemvlZmA=
github.com/safing/portbase v0.15.2/go.mod h1:5bHi99fz7Hh/wOsZUOI631WF9ePSHk57c4fdlOMS91Y=
github.com/safing/portbase v0.16.2/go.mod h1:mzNCWqPbO7vIYbbK5PElGbudwd2vx4YPNawymL8Aro8=
github.com/safing/portbase v0.16.6 h1:6MbK8VGusSKswmbZ031Dher1yijYGxUQ36nNuEIP9P8=
github.com/safing/portbase v0.16.6/go.mod h1:CJSY+0pgbgh00UcM9myS/l+Bcgqgc6afLJX2BqzD+Ek=
github.com/safing/portmaster-android/go v0.0.0-20230414104013-be2efcb51d9d h1:HUPeI54LUyq42YRtd1GdystaQioBJrCY1jPJY36U2nE=
github.com/safing/portmaster-android/go v0.0.0-20230414104013-be2efcb51d9d/go.mod h1:E3MFiTwsHxsPfzI+CGhpE8BOGQYS/V7tkXttMTFeyuc=
github.com/safing/portbase v0.17.0 h1:RsDzbCGxRIbgaArri3y7MZskfxytEvvkzJpiboDUERQ=
github.com/safing/portbase v0.17.0/go.mod h1:eKCRqsfMFLVhNpd2sY/fKvnbuk+LrIYnQEZCg1i86Ho=
github.com/safing/portmaster-android/go v0.0.0-20230605085256-6abf4c495626 h1:olc/REnUdpJN/Gmz8B030OxLpMYxyPDTrDILNEw0eKs=
github.com/safing/portmaster-android/go v0.0.0-20230605085256-6abf4c495626/go.mod h1:abwyAQrZGemWbSh/aCD9nnkp0SvFFf/mGWkAbOwPnFE=
github.com/safing/spn v0.6.8 h1:2obvyMzyw5X3CIYedLBE88kNBBrJumF84q1qtQSFqkc=
github.com/safing/spn v0.6.8/go.mod h1:Mh9bmkqFhO/dHNi9RWXzoXjQij893I4Lj8Wn4tQ0KZA=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
@@ -234,7 +238,6 @@ github.com/spkg/zipfs v0.7.1/go.mod h1:48LW+/Rh1G7aAav1ew1PdlYn52T+LM+ARmSHfDNJv
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -243,8 +246,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tannerryan/ring v1.1.2 h1:iXayOjqHQOLzuy9GwSKuG3nhWfzQkldMlQivcgIr7gQ=
github.com/tannerryan/ring v1.1.2/go.mod h1:DkELJEjbZhJBtFKR9Xziwj3HKZnb/knRgljNqp65vH4=
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
@@ -267,8 +270,9 @@ github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7Am
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 h1:UFHFmFfixpmfRBcxuu+LA9l8MdURWVdVNUHxO5n1d2w=
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26/go.mod h1:IGhd0qMDsUa9acVjsbsT7bu3ktadtGOHI79+idTew/M=
@@ -301,17 +305,17 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -334,14 +338,14 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -386,8 +390,9 @@ golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -403,8 +408,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -413,7 +418,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -427,13 +432,13 @@ gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 h1:cv/zaNV0nr1mJzaeo4S5mHIm
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
modernc.org/libc v1.22.6 h1:cbXU8R+A6aOjRuhsFh3nbDWXO/Hs4ClJRXYB11KmPDo=
modernc.org/libc v1.22.6/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.22.1 h1:P2+Dhp5FR1RlVRkQ3dDfCiv3Ok8XPxqpe70IjYVA9oE=
modernc.org/sqlite v1.22.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI=
modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
zombiezen.com/go/sqlite v0.13.0 h1:iEeyVqcm3fk5PCA8OQBhBxPnqrP4yYuVJBF+XZpSnOE=
zombiezen.com/go/sqlite v0.13.0/go.mod h1:Ht/5Rg3Ae2hoyh1I7gbWtWAl89CNocfqeb/aAMTkJr4=

View File

@@ -87,6 +87,9 @@ func (mng *Manager) HandleFeed(ctx context.Context, feed <-chan *network.Connect
if !ok {
return
}
if !conn.DataIsComplete() {
continue
}
model, err := convertConnection(conn)
if err != nil {

View File

@@ -51,6 +51,11 @@ func cleanConnections() (activePIDs map[int]struct{}) {
// delete inactive connections
switch {
case !conn.DataIsComplete():
// Step 0: delete old incomplete connections
if conn.Started < deleteOlderThan {
conn.delete()
}
case conn.Ended == 0:
// Step 1: check if still active
exists := state.Exists(&packet.Info{
@@ -61,6 +66,7 @@ func cleanConnections() (activePIDs map[int]struct{}) {
SrcPort: conn.LocalPort,
Dst: conn.Entity.IP,
DstPort: conn.Entity.Port,
PID: process.UndefinedProcessID,
}, now)
activePIDs[conn.process.Pid] = struct{}{}

View File

@@ -4,9 +4,12 @@ import (
"context"
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/tevino/abool"
"github.com/safing/portbase/database/record"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/intel"
@@ -102,6 +105,8 @@ type Connection struct { //nolint:maligned // TODO: fix alignment
// set for connections created from DNS requests. LocalPort is
// considered immutable once a connection object has been created.
LocalPort uint16
// PID holds the PID of the owning process.
PID int
// Entity describes the remote entity that the connection has been
// established to. The entity might be changed or information might
// be added to it during the livetime of a connection. Access to
@@ -168,6 +173,22 @@ type Connection struct { //nolint:maligned // TODO: fix alignment
StopTunnel() error
}
RecvBytes uint64
SentBytes uint64
// pkgQueue is used to serialize packet handling for a single
// connection and is served by the connections packetHandler.
pktQueue chan packet.Packet
// pktQueueActive signifies whether the packet queue is active and may be written to.
pktQueueActive bool
// pktQueueLock locks access to pktQueueActive and writing to pktQueue.
pktQueueLock sync.Mutex
// dataComplete signifies that all information about the connection is
// available and an actual packet has been seen.
// As long as this flag is not set, the connection may not be evaluated for
// a verdict and may not be sent to the UI.
dataComplete *abool.AtomicBool
// Internal is set to true if the connection is attributed as an
// Portmaster internal connection. Internal may be set at different
// points and access to it must be guarded by the connection lock.
@@ -175,9 +196,6 @@ type Connection struct { //nolint:maligned // TODO: fix alignment
// process holds a reference to the actor process. That is, the
// process instance that initiated the connection.
process *process.Process
// pkgQueue is used to serialize packet handling for a single
// connection and is served by the connections packetHandler.
pktQueue chan packet.Packet
// firewallHandler is the firewall handler that is called for
// each packet sent to pktQueue.
firewallHandler FirewallHandler
@@ -250,29 +268,50 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []stri
ipVersion = packet.IPv4
}
// get Process
proc, _, err := process.GetProcessByConnection(
ctx,
&packet.Info{
Inbound: false, // outbound as we are looking for the process of the source address
Version: ipVersion,
Protocol: packet.UDP,
Src: localIP, // source as in the process we are looking for
SrcPort: localPort, // source as in the process we are looking for
Dst: nil, // do not record direction
DstPort: 0, // do not record direction
},
)
if err != nil {
log.Tracer(ctx).Debugf("network: failed to find process of dns request for %s: %s", fqdn, err)
proc = process.GetUnidentifiedProcess(ctx)
// Create packet info for dns request connection.
pi := &packet.Info{
Inbound: false, // outbound as we are looking for the process of the source address
Version: ipVersion,
Protocol: packet.UDP,
Src: localIP, // source as in the process we are looking for
SrcPort: localPort, // source as in the process we are looking for
Dst: nil, // do not record direction
DstPort: 0, // do not record direction
PID: process.UndefinedProcessID,
}
// Check if the dns request connection was reported with process info.
dnsRequestConnID := pi.CreateConnectionID()
// Cut the destination, as the dns request may have been redirected and we
// don't know the original destination.
dnsRequestConnIDPrefix, ok := strings.CutSuffix(dnsRequestConnID, "<nil>-0")
if !ok {
log.Tracer(ctx).Warningf("network: unexpected connection ID for finding dns requests connection: %s", dnsRequestConnID)
}
// Find matching dns request connection.
dnsRequestConn, ok := conns.findByPrefix(dnsRequestConnIDPrefix)
if ok && dnsRequestConn.PID != process.UndefinedProcessID {
log.Tracer(ctx).Debugf("network: found matching dns request connection %s", dnsRequestConn)
pi.PID = dnsRequestConn.PID
}
// Find process by remote IP/Port.
if pi.PID == process.UndefinedProcessID {
pi.PID, _, _ = process.GetPidOfConnection(
ctx,
pi,
)
}
// Get process and profile with PID.
proc, _ := process.GetProcessWithProfile(ctx, pi.PID)
timestamp := time.Now().Unix()
dnsConn := &Connection{
ID: connID,
Type: DNSRequest,
Scope: fqdn,
PID: proc.Pid,
Entity: &intel.Entity{
Domain: fqdn,
CNAME: cnames,
@@ -281,6 +320,7 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []stri
ProcessContext: getProcessContext(ctx, proc),
Started: timestamp,
Ended: timestamp,
dataComplete: abool.NewBool(true),
}
// Inherit internal status of profile.
@@ -292,6 +332,7 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []stri
// query. Blocked requests are saved immediately, accepted ones are only
// saved if they are not "used" by a connection.
dnsConn.UpdateMeta()
return dnsConn
}
@@ -308,6 +349,7 @@ func NewConnectionFromExternalDNSRequest(ctx context.Context, fqdn string, cname
Type: DNSRequest,
External: true,
Scope: fqdn,
PID: process.NetworkHostProcessID,
Entity: &intel.Entity{
Domain: fqdn,
CNAME: cnames,
@@ -316,6 +358,7 @@ func NewConnectionFromExternalDNSRequest(ctx context.Context, fqdn string, cname
ProcessContext: getProcessContext(ctx, remoteHost),
Started: timestamp,
Ended: timestamp,
dataComplete: abool.NewBool(true),
}
// Inherit internal status of profile.
@@ -327,52 +370,116 @@ func NewConnectionFromExternalDNSRequest(ctx context.Context, fqdn string, cname
// query. Blocked requests are saved immediately, accepted ones are only
// saved if they are not "used" by a connection.
dnsConn.UpdateMeta()
return dnsConn, nil
}
// NewConnectionFromFirstPacket returns a new connection based on the given packet.
func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
// get Process
proc, inbound, err := process.GetProcessByConnection(pkt.Ctx(), pkt.Info())
if err != nil {
log.Tracer(pkt.Ctx()).Debugf("network: failed to find process of packet %s: %s", pkt, err)
if inbound && !netutils.ClassifyIP(pkt.Info().Dst).IsLocalhost() {
proc = process.GetUnsolicitedProcess(pkt.Ctx())
// NewIncompleteConnection creates a new incomplete connection with only minimal information.
func NewIncompleteConnection(pkt packet.Packet) *Connection {
info := pkt.Info()
// Create new connection object.
// We do not yet know the direction of the connection for sure, so we can only set minimal information.
conn := &Connection{
ID: pkt.GetConnectionID(),
Type: IPConnection,
IPVersion: info.Version,
IPProtocol: info.Protocol,
Started: info.SeenAt.Unix(),
PID: info.PID,
dataComplete: abool.NewBool(false),
}
// Save connection to internal state in order to mitigate creation of
// duplicates. Do not propagate yet, as data is not yet complete.
conn.UpdateMeta()
conns.add(conn)
return conn
}
// GatherConnectionInfo gathers information on the process and remote entity.
func (conn *Connection) GatherConnectionInfo(pkt packet.Packet) (err error) {
// Get PID if not yet available.
if conn.PID == process.UndefinedProcessID {
// Get process by looking at the system state tables.
// Apply direction as reported from the state tables.
conn.PID, conn.Inbound, _ = process.GetPidOfConnection(pkt.Ctx(), pkt.Info())
// Errors are informational and are logged to the context.
}
// Get Process and Profile.
if conn.process == nil {
// We got connection from the system.
conn.process, err = process.GetProcessWithProfile(pkt.Ctx(), conn.PID)
if err == nil {
// Add process/profile metadata for connection.
conn.ProcessContext = getProcessContext(pkt.Ctx(), conn.process)
conn.ProfileRevisionCounter = conn.process.Profile().RevisionCnt()
// Inherit internal status of profile.
if localProfile := conn.process.Profile().LocalProfile(); localProfile != nil {
conn.Internal = localProfile.Internal
}
} else {
proc = process.GetUnidentifiedProcess(pkt.Ctx())
conn.process = nil
if pkt.InfoOnly() {
log.Tracer(pkt.Ctx()).Debugf("network: failed to get process and profile of PID %d: %s", conn.PID, err)
} else {
log.Tracer(pkt.Ctx()).Warningf("network: failed to get process and profile of PID %d: %s", conn.PID, err)
}
}
}
// Create the (remote) entity.
entity := &intel.Entity{
Protocol: uint8(pkt.Info().Protocol),
Port: pkt.Info().RemotePort(),
}
entity.SetIP(pkt.Info().RemoteIP())
entity.SetDstPort(pkt.Info().DstPort)
var scope string
var resolverInfo *resolver.ResolverInfo
var dnsContext *resolver.DNSRequestContext
if inbound {
switch entity.IPScope {
case netutils.HostLocal:
scope = IncomingHost
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
scope = IncomingLAN
case netutils.Global, netutils.GlobalMulticast:
scope = IncomingInternet
case netutils.Undefined, netutils.Invalid:
fallthrough
default:
scope = IncomingInvalid
// Create remote entity.
if conn.Entity == nil {
// Remote
conn.Entity = &intel.Entity{
Protocol: uint8(pkt.Info().Protocol),
Port: pkt.Info().RemotePort(),
}
} else {
conn.Entity.SetIP(pkt.Info().RemoteIP())
conn.Entity.SetDstPort(pkt.Info().DstPort)
// Local
conn.SetLocalIP(pkt.Info().LocalIP())
conn.LocalPort = pkt.Info().LocalPort()
if conn.Inbound {
switch conn.Entity.IPScope {
case netutils.HostLocal:
conn.Scope = IncomingHost
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
conn.Scope = IncomingLAN
case netutils.Global, netutils.GlobalMulticast:
conn.Scope = IncomingInternet
case netutils.Undefined, netutils.Invalid:
fallthrough
default:
conn.Scope = IncomingInvalid
}
} else {
// Outbound direct (possibly P2P) connection.
switch conn.Entity.IPScope {
case netutils.HostLocal:
conn.Scope = PeerHost
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
conn.Scope = PeerLAN
case netutils.Global, netutils.GlobalMulticast:
conn.Scope = PeerInternet
case netutils.Undefined, netutils.Invalid:
fallthrough
default:
conn.Scope = PeerInvalid
}
}
}
// Find domain and DNS context of entity.
if conn.Entity.Domain == "" && conn.process.Profile() != nil {
// check if we can find a domain for that IP
ipinfo, err := resolver.GetIPInfo(proc.Profile().LocalProfile().ID, pkt.Info().RemoteIP().String())
ipinfo, err := resolver.GetIPInfo(conn.process.Profile().LocalProfile().ID, pkt.Info().RemoteIP().String())
if err != nil {
// Try again with the global scope, in case DNS went through the system resolver.
ipinfo, err = resolver.GetIPInfo(resolver.IPInfoProfileScopeGlobal, pkt.Info().RemoteIP().String())
@@ -380,78 +487,38 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
if err == nil {
lastResolvedDomain := ipinfo.MostRecentDomain()
if lastResolvedDomain != nil {
scope = lastResolvedDomain.Domain
entity.Domain = lastResolvedDomain.Domain
entity.CNAME = lastResolvedDomain.CNAMEs
dnsContext = lastResolvedDomain.DNSRequestContext
resolverInfo = lastResolvedDomain.Resolver
removeOpenDNSRequest(proc.Pid, lastResolvedDomain.Domain)
conn.Scope = lastResolvedDomain.Domain
conn.Entity.Domain = lastResolvedDomain.Domain
conn.Entity.CNAME = lastResolvedDomain.CNAMEs
conn.DNSContext = lastResolvedDomain.DNSRequestContext
conn.Resolver = lastResolvedDomain.Resolver
removeOpenDNSRequest(conn.process.Pid, lastResolvedDomain.Domain)
}
}
}
// check if destination IP is the captive portal's IP
// Check if destination IP is the captive portal's IP.
if conn.Entity.Domain == "" {
portal := netenv.GetCaptivePortal()
if pkt.Info().RemoteIP().Equal(portal.IP) {
scope = portal.Domain
entity.Domain = portal.Domain
}
if scope == "" {
// outbound direct (possibly P2P) connection
switch entity.IPScope {
case netutils.HostLocal:
scope = PeerHost
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
scope = PeerLAN
case netutils.Global, netutils.GlobalMulticast:
scope = PeerInternet
case netutils.Undefined, netutils.Invalid:
fallthrough
default:
scope = PeerInvalid
}
conn.Scope = portal.Domain
conn.Entity.Domain = portal.Domain
}
}
// Create new connection object.
newConn := &Connection{
ID: pkt.GetConnectionID(),
Type: IPConnection,
Scope: scope,
IPVersion: pkt.Info().Version,
Inbound: inbound,
// local endpoint
IPProtocol: pkt.Info().Protocol,
LocalPort: pkt.Info().LocalPort(),
ProcessContext: getProcessContext(pkt.Ctx(), proc),
DNSContext: dnsContext,
process: proc,
// remote endpoint
Entity: entity,
// resolver used to resolve dns request
Resolver: resolverInfo,
// meta
Started: time.Now().Unix(),
ProfileRevisionCounter: proc.Profile().RevisionCnt(),
}
newConn.SetLocalIP(pkt.Info().LocalIP())
// Inherit internal status of profile.
if localProfile := proc.Profile().LocalProfile(); localProfile != nil {
newConn.Internal = localProfile.Internal
// Data collection is only complete with a packet.
if pkt.InfoOnly() {
return nil
}
// Save connection to internal state in order to mitigate creation of
// duplicates. Do not propagate yet, as there is no verdict yet.
conns.add(newConn)
return newConn
// If we have all data and have seen an actual packet, the connection data is complete.
conn.dataComplete.Set()
return nil
}
// GetConnection fetches a Connection from the database.
func GetConnection(id string) (*Connection, bool) {
return conns.get(id)
func GetConnection(connID string) (*Connection, bool) {
return conns.get(connID)
}
// GetAllConnections Gets all connection.
@@ -563,6 +630,14 @@ func (conn *Connection) VerdictVerb() string {
)
}
// DataIsComplete returns whether all information about the connection is
// available and an actual packet has been seen.
// As long as this flag is not set, the connection may not be evaluated for
// a verdict and may not be sent to the UI.
func (conn *Connection) DataIsComplete() bool {
return conn.dataComplete.IsSet()
}
// Process returns the connection's process.
func (conn *Connection) Process() *process.Process {
return conn.process
@@ -579,9 +654,13 @@ func (conn *Connection) SaveWhenFinished() {
// Callers must make sure to lock the connection itself before calling
// Save().
func (conn *Connection) Save() {
conn.addToMetrics()
conn.UpdateMeta()
// Do not save/update until data is complete.
if !conn.DataIsComplete() {
return
}
if !conn.KeyIsSet() {
if conn.Type == DNSRequest {
conn.SetKey(makeKey(conn.process.Pid, dbScopeDNS, conn.ID))
@@ -592,6 +671,8 @@ func (conn *Connection) Save() {
}
}
conn.addToMetrics()
// notify database controller
dbController.PushUpdate(conn)
}
@@ -610,29 +691,61 @@ func (conn *Connection) delete() {
}
conn.Meta().Delete()
dbController.PushUpdate(conn)
// Notify database controller if data is complete and thus connection was previously exposed.
if conn.DataIsComplete() {
dbController.PushUpdate(conn)
}
}
// SetFirewallHandler sets the firewall handler for this link, and starts a
// worker to handle the packets.
// The caller needs to hold a lock on the connection.
// Cannot be called with "nil" handler. Call StopFirewallHandler() instead.
func (conn *Connection) SetFirewallHandler(handler FirewallHandler) {
if conn.firewallHandler == nil {
conn.pktQueue = make(chan packet.Packet, 100)
if handler == nil {
return
}
// Start packet handler worker when first handler is set.
if conn.firewallHandler == nil {
// start handling
module.StartWorker("packet handler", conn.packetHandlerWorker)
}
// Set new handler.
conn.firewallHandler = handler
// Initialize packet queue, if needed.
conn.pktQueueLock.Lock()
defer conn.pktQueueLock.Unlock()
if !conn.pktQueueActive {
conn.pktQueue = make(chan packet.Packet, 100)
conn.pktQueueActive = true
}
}
// UpdateFirewallHandler sets the firewall handler if it already set and the
// given handler is not nil.
// The caller needs to hold a lock on the connection.
func (conn *Connection) UpdateFirewallHandler(handler FirewallHandler) {
if handler != nil && conn.firewallHandler != nil {
conn.firewallHandler = handler
}
}
// StopFirewallHandler unsets the firewall handler and stops the handler worker.
// The caller needs to hold a lock on the connection.
func (conn *Connection) StopFirewallHandler() {
conn.pktQueueLock.Lock()
defer conn.pktQueueLock.Unlock()
// Unset the firewall handler to revert to the default handler.
conn.firewallHandler = nil
// Signal the packet handler worker that it can stop.
close(conn.pktQueue)
conn.pktQueueActive = false
// Unset the packet queue so that it can be freed.
conn.pktQueue = nil
@@ -640,15 +753,25 @@ func (conn *Connection) StopFirewallHandler() {
// HandlePacket queues packet of Link for handling.
func (conn *Connection) HandlePacket(pkt packet.Packet) {
conn.Lock()
defer conn.Unlock()
conn.pktQueueLock.Lock()
defer conn.pktQueueLock.Unlock()
// execute handler or verdict
if conn.firewallHandler != nil {
conn.pktQueue <- pkt
// TODO: drop if overflowing?
if conn.pktQueueActive {
select {
case conn.pktQueue <- pkt:
default:
log.Debugf(
"filter: dropping packet %s, as there is no space in the connection's handling queue",
pkt,
)
_ = pkt.Drop()
}
} else {
defaultFirewallHandler(conn, pkt)
// Record metrics.
packetHandlingHistogram.UpdateDuration(pkt.Info().SeenAt)
}
}
@@ -656,7 +779,15 @@ func (conn *Connection) HandlePacket(pkt packet.Packet) {
func (conn *Connection) packetHandlerWorker(ctx context.Context) error {
// Copy packet queue, so we can remove the reference from the connection
// when we stop the firewall handler.
pktQueue := conn.pktQueue
var pktQueue chan packet.Packet
func() {
conn.pktQueueLock.Lock()
defer conn.pktQueueLock.Unlock()
pktQueue = conn.pktQueue
}()
// pktSeq counts the seen packets.
var pktSeq int
for {
select {
@@ -664,21 +795,58 @@ func (conn *Connection) packetHandlerWorker(ctx context.Context) error {
if pkt == nil {
return nil
}
packetHandlerHandleConn(conn, pkt)
pktSeq++
// Check if we should expect an(other) info only packet.
// Only wait if this is the first packet and is not an info packet itself.
if pktSeq == 1 && pkt.ExpectInfo() && !pkt.InfoOnly() {
// Debug: FIXME
// log.Debugf("filter: waiting for info only packet in order to pull forward: %s", pkt)
select {
case infoPkt := <-pktQueue:
if infoPkt != nil {
// Debug: FIXME
// log.Debugf("filter: packet #%d [pulled forward] info=%v PID=%d packet: %s", pktSeq, pkt.InfoOnly(), pkt.Info().PID, pkt)
packetHandlerHandleConn(ctx, conn, pkt)
pktSeq++
}
case <-time.After(5 * time.Millisecond):
}
}
// Debug: FIXME
// switch {
// case pkt.Info().Inbound:
// log.Debugf("filter: packet #%d info=%v PID=%d packet: %s", pktSeq, pkt.InfoOnly(), pkt.Info().PID, pkt)
// case pktSeq == 1 && !pkt.InfoOnly():
// log.Warningf("filter: packet #%d [should be info only!] info=%v PID=%d packet: %s", pktSeq, pkt.InfoOnly(), pkt.Info().PID, pkt)
// case pktSeq >= 2 && pkt.InfoOnly():
// log.Errorf("filter: packet #%d [should not be info only!] info=%v PID=%d packet: %s", pktSeq, pkt.InfoOnly(), pkt.Info().PID, pkt)
// default:
// log.Debugf("filter: packet #%d info=%v PID=%d packet: %s", pktSeq, pkt.InfoOnly(), pkt.Info().PID, pkt)
// }
packetHandlerHandleConn(ctx, conn, pkt)
case <-ctx.Done():
conn.Lock()
defer conn.Unlock()
conn.firewallHandler = nil
return nil
}
}
}
func packetHandlerHandleConn(conn *Connection, pkt packet.Packet) {
func packetHandlerHandleConn(ctx context.Context, conn *Connection, pkt packet.Packet) {
conn.Lock()
defer conn.Unlock()
// Create tracing context.
// Add context tracer and set context on packet.
traceCtx, tracer := log.AddTracer(ctx)
if tracer != nil {
// The trace is submitted in `network.Connection.packetHandler()`.
tracer.Tracef("filter: handling packet: %s", pkt)
}
pkt.SetCtx(traceCtx)
// Handle packet with appropriate handler.
if conn.firewallHandler != nil {
conn.firewallHandler(conn, pkt)
@@ -686,13 +854,22 @@ func packetHandlerHandleConn(conn *Connection, pkt packet.Packet) {
defaultFirewallHandler(conn, pkt)
}
// Log verdict.
log.Tracer(pkt.Ctx()).Infof("filter: connection %s %s: %s", conn, conn.VerdictVerb(), conn.Reason.Msg)
// Submit trace logs.
log.Tracer(pkt.Ctx()).Submit()
// Record metrics.
packetHandlingHistogram.UpdateDuration(pkt.Info().SeenAt)
// Save() itself does not touch any changing data.
// Must not be locked - would deadlock with cleaner functions.
// Log result and submit trace.
switch {
case conn.DataIsComplete():
tracer.Infof("filter: connection %s %s: %s", conn, conn.VerdictVerb(), conn.Reason.Msg)
case conn.Verdict.Firewall != VerdictUndecided:
tracer.Debugf("filter: connection %s fast-tracked", pkt)
default:
tracer.Debugf("filter: gathered data on connection %s", conn)
}
// Submit trace logs.
tracer.Submit()
// Push changes, if there are any.
if conn.saveWhenFinished {
conn.saveWhenFinished = false
conn.Save()

View File

@@ -11,6 +11,7 @@ import (
"github.com/safing/portmaster/network/packet"
"github.com/safing/portmaster/process"
"github.com/safing/spn/navigator"
"github.com/tevino/abool"
)
// NewDefaultConnection creates a new connection with default values except local and remote IPs and protocols.
@@ -25,6 +26,7 @@ func NewDefaultConnection(localIP net.IP, localPort uint16, remoteIP net.IP, rem
LocalIP: localIP,
LocalIPScope: netutils.Global,
LocalPort: localPort,
PID: process.UnidentifiedProcessID,
Entity: &intel.Entity{
Protocol: uint8(protocol),
IP: remoteIP,
@@ -35,6 +37,7 @@ func NewDefaultConnection(localIP net.IP, localPort uint16, remoteIP net.IP, rem
VerdictPermanent: false,
Tunneled: true,
Encrypted: false,
DataComplete: abool.NewBool(true),
Internal: false,
addedToMetrics: true, // Metrics are not needed for now. This will mark the Connection to be ignored.
process: process.GetUnidentifiedProcess(context.Background()),

View File

@@ -1,6 +1,7 @@
package network
import (
"strings"
"sync"
)
@@ -37,6 +38,21 @@ func (cs *connectionStore) get(id string) (*Connection, bool) {
return conn, ok
}
// findByPrefix returns the first connection where the key matches the given prefix.
// If the prefix matches multiple entries, the result is not deterministic.
func (cs *connectionStore) findByPrefix(prefix string) (*Connection, bool) {
cs.rw.RLock()
defer cs.rw.RUnlock()
for key, conn := range cs.items {
if strings.HasPrefix(key, prefix) {
return conn, true
}
}
return nil, false
}
func (cs *connectionStore) clone() map[string]*Connection {
cs.rw.RLock()
defer cs.rw.RUnlock()

View File

@@ -90,12 +90,12 @@ func (s *StorageInterface) Get(key string) (record.Record, error) {
switch scope {
case dbScopeDNS:
if r, ok := dnsConns.get(id); ok {
return r, nil
if c, ok := dnsConns.get(id); ok && c.DataIsComplete() {
return c, nil
}
case dbScopeIP:
if r, ok := conns.get(id); ok {
return r, nil
if c, ok := conns.get(id); ok && c.DataIsComplete() {
return c, nil
}
case dbScopeNone:
if proc, ok := process.GetProcessFromStorage(pid); ok {
@@ -143,11 +143,16 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
if scope == dbScopeNone || scope == dbScopeDNS {
// dns scopes only
for _, dnsConn := range dnsConns.clone() {
if !dnsConn.DataIsComplete() {
continue
}
func() {
dnsConn.Lock()
defer dnsConn.Unlock()
matches = q.Matches(dnsConn)
}()
if matches {
it.Next <- dnsConn
}
@@ -157,11 +162,16 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
if scope == dbScopeNone || scope == dbScopeIP {
// connections
for _, conn := range conns.clone() {
if !conn.DataIsComplete() {
continue
}
func() {
conn.Lock()
defer conn.Unlock()
matches = q.Matches(conn)
}()
if matches {
it.Next <- conn
}

View File

@@ -8,6 +8,7 @@ import (
)
var (
packetHandlingHistogram *metrics.Histogram
blockedOutConnCounter *metrics.Counter
encryptedAndTunneledOutConnCounter *metrics.Counter
encryptedOutConnCounter *metrics.Counter
@@ -15,8 +16,21 @@ var (
outConnCounter *metrics.Counter
)
func registerMetrics() error {
_, err := metrics.NewGauge(
func registerMetrics() (err error) {
// This needed to be moved here, because every packet is now handled by the
// connection handler worker.
packetHandlingHistogram, err = metrics.NewHistogram(
"firewall/handling/duration/seconds",
nil,
&metrics.Options{
Permission: api.PermitUser,
ExpertiseLevel: config.ExpertiseLevelExpert,
})
if err != nil {
return err
}
_, err = metrics.NewGauge(
"network/connections/active/total",
nil,
func() float64 {

View File

@@ -15,6 +15,8 @@ func GetMulticastRequestConn(responseConn *Connection, responseFromNet *net.IPNe
// Find requesting multicast/broadcast connection.
for _, conn := range conns.clone() {
switch {
case !conn.DataIsComplete():
// Ignore connection with incomplete data.
case conn.Inbound:
// Ignore incoming connections.
case conn.Ended != 0:

View File

@@ -0,0 +1,35 @@
package packet
import "fmt"
// BandwidthUpdate holds an update to the seen bandwidth of a connection.
type BandwidthUpdate struct {
ConnID string
RecvBytes uint64
SentBytes uint64
Method BandwidthUpdateMethod
}
// BandwidthUpdateMethod defines how the bandwidth data of a bandwidth update should be interpreted.
type BandwidthUpdateMethod uint8
// Bandwidth Update Methods.
const (
Absolute BandwidthUpdateMethod = iota
Additive
)
func (bu *BandwidthUpdate) String() string {
return fmt.Sprintf("%s: %dB recv | %dB sent [%s]", bu.ConnID, bu.RecvBytes, bu.SentBytes, bu.Method)
}
func (bum BandwidthUpdateMethod) String() string {
switch bum {
case Absolute:
return "absolute"
case Additive:
return "additive"
default:
return "unknown"
}
}

View File

@@ -0,0 +1,77 @@
package packet
import (
"errors"
"fmt"
)
// InfoPacket does not represent an actual packet, but only holds metadata.
// Implements the packet.Packet interface.
type InfoPacket struct {
Base
}
// NewInfoPacket returns a new InfoPacket with the given info.
func NewInfoPacket(info Info) *InfoPacket {
return &InfoPacket{
Base{
info: info,
},
}
}
// 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("%w: info-only packet", ErrFailedToLoadPayload)
}
// ErrInfoOnlyPacket is returned for unsupported operations on an info-only packet.
var ErrInfoOnlyPacket = errors.New("info-only packet")
// Accept does nothing on an info-only packet.
func (pkt *InfoPacket) Accept() error {
return ErrInfoOnlyPacket
}
// Block does nothing on an info-only packet.
func (pkt *InfoPacket) Block() error {
return ErrInfoOnlyPacket
}
// Drop does nothing on an info-only packet.
func (pkt *InfoPacket) Drop() error {
return ErrInfoOnlyPacket
}
// PermanentAccept does nothing on an info-only packet.
func (pkt *InfoPacket) PermanentAccept() error {
return ErrInfoOnlyPacket
}
// PermanentBlock does nothing on an info-only packet.
func (pkt *InfoPacket) PermanentBlock() error {
return ErrInfoOnlyPacket
}
// PermanentDrop does nothing on an info-only packet.
func (pkt *InfoPacket) PermanentDrop() error {
return ErrInfoOnlyPacket
}
// RerouteToNameserver does nothing on an info-only packet.
func (pkt *InfoPacket) RerouteToNameserver() error {
return ErrInfoOnlyPacket
}
// RerouteToTunnel does nothing on an info-only packet.
func (pkt *InfoPacket) RerouteToTunnel() error {
return ErrInfoOnlyPacket
}
var _ Packet = &InfoPacket{}

View File

@@ -24,6 +24,17 @@ func (pkt *Base) FastTrackedByIntegration() bool {
return false
}
// InfoOnly returns whether the packet is informational only and does not
// represent an actual packet.
func (pkt *Base) InfoOnly() bool {
return false
}
// ExpectInfo returns whether the next packet is expected to be informational only.
func (pkt *Base) ExpectInfo() bool {
return false
}
// SetCtx sets the packet context.
func (pkt *Base) SetCtx(ctx context.Context) {
pkt.ctx = ctx
@@ -101,27 +112,11 @@ func (pkt *Base) Payload() []byte {
// GetConnectionID returns the link ID for this packet.
func (pkt *Base) GetConnectionID() string {
if pkt.connID == "" {
pkt.createConnectionID()
pkt.connID = pkt.info.CreateConnectionID()
}
return pkt.connID
}
func (pkt *Base) createConnectionID() {
if pkt.info.Protocol == TCP || pkt.info.Protocol == UDP {
if pkt.info.Inbound {
pkt.connID = fmt.Sprintf("%d-%s-%d-%s-%d", pkt.info.Protocol, pkt.info.Dst, pkt.info.DstPort, pkt.info.Src, pkt.info.SrcPort)
} else {
pkt.connID = fmt.Sprintf("%d-%s-%d-%s-%d", pkt.info.Protocol, pkt.info.Src, pkt.info.SrcPort, pkt.info.Dst, pkt.info.DstPort)
}
} else {
if pkt.info.Inbound {
pkt.connID = fmt.Sprintf("%d-%s-%s", pkt.info.Protocol, pkt.info.Dst, pkt.info.Src)
} else {
pkt.connID = fmt.Sprintf("%d-%s-%s", pkt.info.Protocol, pkt.info.Src, pkt.info.Dst)
}
}
}
// MatchesAddress checks if a the packet matches a given endpoint (remote or local) in protocol, network and port.
//
// Comparison matrix:
@@ -236,6 +231,8 @@ type Packet interface {
RerouteToNameserver() error
RerouteToTunnel() error
FastTrackedByIntegration() bool
InfoOnly() bool
ExpectInfo() bool
// Info.
SetCtx(context.Context)

View File

@@ -1,7 +1,9 @@
package packet
import (
"fmt"
"net"
"time"
)
// Info holds IP and TCP/UDP header information.
@@ -13,6 +15,9 @@ type Info struct {
Protocol IPProtocol
SrcPort, DstPort uint16
Src, Dst net.IP
PID int
SeenAt time.Time
}
// LocalIP returns the local IP of the packet.
@@ -46,3 +51,26 @@ func (pi *Info) RemotePort() uint16 {
}
return pi.DstPort
}
// CreateConnectionID creates a connection ID.
// In most circumstances, this method should not be used directly, but
// packet.GetConnectionID() should be called instead.
func (pi *Info) CreateConnectionID() string {
return CreateConnectionID(pi.Protocol, pi.Src, pi.SrcPort, pi.Dst, pi.DstPort, pi.Inbound)
}
// CreateConnectionID creates a connection ID.
func CreateConnectionID(protocol IPProtocol, src net.IP, srcPort uint16, dst net.IP, dstPort uint16, inbound bool) string {
// TODO: make this ID not depend on the packet direction for better support for forwarded packets.
if protocol == TCP || protocol == UDP {
if inbound {
return fmt.Sprintf("%d-%s-%d-%s-%d", protocol, dst, dstPort, src, srcPort)
}
return fmt.Sprintf("%d-%s-%d-%s-%d", protocol, src, srcPort, dst, dstPort)
}
if inbound {
return fmt.Sprintf("%d-%s-%s", protocol, dst, src)
}
return fmt.Sprintf("%d-%s-%s", protocol, src, dst)
}

View File

@@ -28,13 +28,18 @@ nextPort:
// Check if the generated port is unused.
nextConnection:
for _, conn := range allConns {
// Skip connection if the protocol does not match the protocol of interest.
if conn.Entity.Protocol != protocol {
switch {
case !conn.DataIsComplete():
// Skip connection if the data is not complete.
continue nextConnection
}
// Skip port if the local port is in dangerous proximity.
// Consecutive port numbers are very common.
if conn.LocalPort <= port && conn.LocalPort >= portRangeStart {
case conn.Entity.Protocol != protocol:
// Skip connection if the protocol does not match the protocol of interest.
continue nextConnection
case conn.LocalPort <= port && conn.LocalPort >= portRangeStart:
// Skip port if the local port is in dangerous proximity.
// Consecutive port numbers are very common.
continue nextPort
}
}

View File

@@ -14,38 +14,19 @@ import (
"github.com/safing/portmaster/profile"
)
// GetProcessByConnection returns the process that owns the described connection.
func GetProcessByConnection(ctx context.Context, pktInfo *packet.Info) (process *Process, connInbound bool, err error) {
// GetProcessWithProfile returns the process, including the profile.
// Always returns valid data.
// Errors are logged and returned for information or special handling purposes.
func GetProcessWithProfile(ctx context.Context, pid int) (process *Process, err error) {
if !enableProcessDetection() {
log.Tracer(ctx).Tracef("process: process detection disabled")
return GetUnidentifiedProcess(ctx), pktInfo.Inbound, nil
}
// Use fast search for inbound packets, as the listening socket should
// already be there for a while now.
fastSearch := pktInfo.Inbound
log.Tracer(ctx).Tracef("process: getting pid from system network state")
var pid int
pid, connInbound, err = state.Lookup(pktInfo, fastSearch)
if err != nil {
log.Tracer(ctx).Tracef("process: failed to find PID of connection: %s", err)
return nil, pktInfo.Inbound, err
}
// Fallback to special profiles if PID could not be found.
if pid == UndefinedProcessID {
if connInbound {
pid = UnsolicitedProcessID
} else {
pid = UnidentifiedProcessID
}
return GetUnidentifiedProcess(ctx), nil
}
process, err = GetOrFindProcess(ctx, pid)
if err != nil {
log.Tracer(ctx).Debugf("process: failed to find (primary) process with PID: %s", err)
return nil, connInbound, err
log.Tracer(ctx).Debugf("process: failed to find process with PID: %s", err)
return GetUnidentifiedProcess(ctx), err
}
changed, err := process.GetProfile(ctx)
@@ -57,7 +38,46 @@ func GetProcessByConnection(ctx context.Context, pktInfo *packet.Info) (process
process.Save()
}
return process, connInbound, nil
return process, nil
}
// GetPidOfConnection returns the PID of the process that owns the described connection.
// Always returns valid data.
// Errors are logged and returned for information or special handling purposes.
func GetPidOfConnection(ctx context.Context, pktInfo *packet.Info) (pid int, connInbound bool, err error) {
if !enableProcessDetection() {
return UnidentifiedProcessID, pktInfo.Inbound, nil
}
// Use fast search for inbound packets, as the listening socket should
// already be there for a while now.
fastSearch := pktInfo.Inbound
connInbound = pktInfo.Inbound
// Check if we need to get the PID.
if pktInfo.PID == UndefinedProcessID {
log.Tracer(ctx).Tracef("process: getting pid from system network state")
pid, connInbound, err = state.Lookup(pktInfo, fastSearch)
if err != nil {
err = fmt.Errorf("failed to find PID of connection: %w", err)
log.Tracer(ctx).Tracef("process: %s", err)
pid = UndefinedProcessID
}
} else {
log.Tracer(ctx).Tracef("process: pid already set in packet (by ebpf or kext)")
pid = pktInfo.PID
}
// Fallback to special profiles if PID could not be found.
if pid == UndefinedProcessID {
if connInbound && !netutils.ClassifyIP(pktInfo.Dst).IsLocalhost() {
pid = UnsolicitedProcessID
} else {
pid = UnidentifiedProcessID
}
}
return pid, connInbound, err
}
// GetNetworkHost returns a *Process that represents a host on the network.
@@ -106,9 +126,15 @@ func GetProcessByRequestOrigin(ar *api.Request) (*Process, error) {
Protocol: packet.TCP,
Src: remoteIP, // source as in the process we are looking for
SrcPort: remotePort, // source as in the process we are looking for
PID: UndefinedProcessID,
}
proc, _, err := GetProcessByConnection(ar.Context(), pkt)
pid, _, err := GetPidOfConnection(ar.Context(), pkt)
if err != nil {
return nil, err
}
proc, err := GetProcessWithProfile(ar.Context(), pid)
if err != nil {
return nil, err
}

View File

@@ -217,7 +217,7 @@ func loadProcess(ctx context.Context, key string, pInfo *processInfo.Process) (*
}
// UID
// net yet implemented for windows
// TODO: implemented for windows
if onLinux {
var uids []int32
uids, err = pInfo.UidsWithContext(ctx)

View File

@@ -62,6 +62,9 @@ func (h *SVCHostTagHandler) AddTags(p *process.Process) {
p.Name += fmt.Sprintf(" (%s)", strings.Join(svcNames, ", "))
// Add services as tags.
for _, svcName := range svcNames {
// Remove tags from service names, such as "CDPUserSvc_1bf5729".
svcName, _, _ := strings.Cut(svcName, "_")
// Add service as tag.
p.Tags = append(p.Tags, profile.Tag{
Key: svchostTagKey,
Value: svcName,

View File

@@ -63,6 +63,7 @@ var (
cfgOptionNoAssignedNameserversOrder = 1
CfgOptionUseStaleCacheKey = "dns/useStaleCache"
useStaleCacheConfigOption *config.Option
useStaleCache config.BoolOption
cfgOptionUseStaleCacheOrder = 2
@@ -205,7 +206,7 @@ The format is: "protocol://ip:port?parameter=value&parameter=value"
}
noAssignedNameservers = status.SecurityLevelOption(CfgOptionNoAssignedNameserversKey)
err = config.Register(&config.Option{
useStaleCacheConfigOption = &config.Option{
Name: "Always Use DNS Cache",
Key: CfgOptionUseStaleCacheKey,
Description: "Always use the DNS cache, even if entries have expired. Expired entries are refreshed afterwards in the background. This can improve DNS resolving performance a lot, but may lead to occasional connection errors due to outdated DNS records.",
@@ -217,7 +218,8 @@ The format is: "protocol://ip:port?parameter=value&parameter=value"
config.DisplayOrderAnnotation: cfgOptionUseStaleCacheOrder,
config.CategoryAnnotation: "Resolving",
},
})
}
err = config.Register(useStaleCacheConfigOption)
if err != nil {
return err
}

View File

@@ -51,7 +51,7 @@ var suggestUsingStaleCacheNotification *notifications.Notification
func suggestUsingStaleCacheTask(ctx context.Context, t *modules.Task) error {
switch {
case useStaleCache():
case useStaleCache() || useStaleCacheConfigOption.IsSetByUser():
// If setting is already active, disable task repeating.
t.Repeat(0)