diff --git a/compat/module.go b/compat/module.go index 77478f7e..ea5fcd69 100644 --- a/compat/module.go +++ b/compat/module.go @@ -16,7 +16,7 @@ var ( module *modules.Module selfcheckTask *modules.Task - selfcheckTaskRetryAfter = 5 * time.Second + selfcheckTaskRetryAfter = 15 * time.Second // selfCheckIsFailing holds whether or not the self-check is currently // failing. This helps other failure systems to not make noise when there is @@ -34,7 +34,7 @@ var ( // selfcheckFailThreshold holds the threshold of how many times the selfcheck // must fail before it is reported. -const selfcheckFailThreshold = 5 +const selfcheckFailThreshold = 10 func init() { module = modules.Register("compat", prep, start, stop, "base", "network", "interception", "netenv", "notifications") @@ -60,7 +60,7 @@ func start() error { Schedule(time.Now().Add(selfcheckTaskRetryAfter)) module.NewTask("clean notify thresholds", cleanNotifyThreshold). - Repeat(10 * time.Minute) + Repeat(1 * time.Hour) return module.RegisterEventHook( netenv.ModuleName, diff --git a/compat/notify.go b/compat/notify.go index 5cf6fccc..a92e654f 100644 --- a/compat/notify.go +++ b/compat/notify.go @@ -236,7 +236,7 @@ func (issue *appIssue) notify(proc *process.Process) { } const ( - notifyThresholdMinIncidents = 11 + notifyThresholdMinIncidents = 10 notifyThresholdResetAfter = 2 * time.Minute ) diff --git a/compat/selfcheck.go b/compat/selfcheck.go index 4515d93c..fd4a22bc 100644 --- a/compat/selfcheck.go +++ b/compat/selfcheck.go @@ -28,12 +28,12 @@ var ( systemIntegrationCheckDialNet = fmt.Sprintf("ip4:%d", uint8(SystemIntegrationCheckProtocol)) systemIntegrationCheckDialIP = SystemIntegrationCheckDstIP.String() systemIntegrationCheckPackets = make(chan packet.Packet, 1) - systemIntegrationCheckWaitDuration = 40 * time.Second + systemIntegrationCheckWaitDuration = 45 * time.Second // DNSCheckInternalDomainScope is the domain scope to use for dns checks. DNSCheckInternalDomainScope = ".self-check." + resolver.InternalSpecialUseDomain dnsCheckReceivedDomain = make(chan string, 1) - dnsCheckWaitDuration = 40 * time.Second + dnsCheckWaitDuration = 45 * time.Second dnsCheckAnswerLock sync.Mutex dnsCheckAnswer net.IP ) diff --git a/core/api.go b/core/api.go index be5fffad..6a653909 100644 --- a/core/api.go +++ b/core/api.go @@ -224,7 +224,7 @@ func authorizeApp(ar *api.Request) (interface{}, error) { Title: "An app requests access to the Portmaster", Message: "Allow " + appName + " (" + proc.Profile().LocalProfile().Name + ") to query and modify the Portmaster?\n\nBinary: " + proc.Path, ShowOnSystem: true, - Expires: time.Now().Add(time.Minute).UnixNano(), + Expires: time.Now().Add(time.Minute).Unix(), AvailableActions: []*notifications.Action{ { ID: "allow", diff --git a/firewall/dns.go b/firewall/dns.go index 394e334a..5c3542f1 100644 --- a/firewall/dns.go +++ b/firewall/dns.go @@ -45,6 +45,7 @@ func filterDNSSection( ip = v.AAAA default: // add non A/AAAA entries + // TODO: Add support for dns.SVCB and dns.HTTPS goodEntries = append(goodEntries, rr) continue } @@ -257,6 +258,32 @@ func UpdateIPsAndCNAMEs(q *resolver.Query, rrCache *resolver.RRCache, conn *netw case *dns.AAAA: ips = append(ips, v.AAAA) + + case *dns.SVCB: + if len(v.Target) >= 2 { // Ignore "" and ".". + cnames[v.Hdr.Name] = v.Target + } + for _, pair := range v.Value { + switch svcbParam := pair.(type) { + case *dns.SVCBIPv4Hint: + ips = append(ips, svcbParam.Hint...) + case *dns.SVCBIPv6Hint: + ips = append(ips, svcbParam.Hint...) + } + } + + case *dns.HTTPS: + if len(v.Target) >= 2 { // Ignore "" and ".". + cnames[v.Hdr.Name] = v.Target + } + for _, pair := range v.Value { + switch svcbParam := pair.(type) { + case *dns.SVCBIPv4Hint: + ips = append(ips, svcbParam.Hint...) + case *dns.SVCBIPv6Hint: + ips = append(ips, svcbParam.Hint...) + } + } } } diff --git a/firewall/interception/ebpf/bandwidth/bpf_bpfeb.o b/firewall/interception/ebpf/bandwidth/bpf_bpfeb.o index 82069f79..4ceee0d9 100644 Binary files a/firewall/interception/ebpf/bandwidth/bpf_bpfeb.o and b/firewall/interception/ebpf/bandwidth/bpf_bpfeb.o differ diff --git a/firewall/interception/ebpf/bandwidth/bpf_bpfel.o b/firewall/interception/ebpf/bandwidth/bpf_bpfel.o index f7974b55..e52039fc 100644 Binary files a/firewall/interception/ebpf/bandwidth/bpf_bpfel.o and b/firewall/interception/ebpf/bandwidth/bpf_bpfel.o differ diff --git a/firewall/interception/ebpf/connection_listener/bpf_bpfeb.o b/firewall/interception/ebpf/connection_listener/bpf_bpfeb.o index 9545daa4..e8e7cdb7 100644 Binary files a/firewall/interception/ebpf/connection_listener/bpf_bpfeb.o and b/firewall/interception/ebpf/connection_listener/bpf_bpfeb.o differ diff --git a/firewall/interception/ebpf/connection_listener/bpf_bpfel.o b/firewall/interception/ebpf/connection_listener/bpf_bpfel.o index e4f9f3f8..5241f58e 100644 Binary files a/firewall/interception/ebpf/connection_listener/bpf_bpfel.o and b/firewall/interception/ebpf/connection_listener/bpf_bpfel.o differ diff --git a/firewall/interception/ebpf/programs/bandwidth.c b/firewall/interception/ebpf/programs/bandwidth.c index b56ce763..04222353 100644 --- a/firewall/interception/ebpf/programs/bandwidth.c +++ b/firewall/interception/ebpf/programs/bandwidth.c @@ -105,7 +105,7 @@ 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}; + struct sk_key key = {0}; key.protocol = PROTOCOL_UDP; key.src_ip[0] = skc->skc_rcv_saddr; key.dst_ip[0] = skc->skc_daddr; @@ -134,7 +134,7 @@ int BPF_PROG(udp_recvmsg, struct sock *sk, struct msghdr *msg, size_t len, int f struct sock_common *skc = &sk->__sk_common; // Create a key for the map and set all the nececery information. - struct sk_key key = {0}; + struct sk_key key = {0}; key.protocol = PROTOCOL_UDP; key.src_ip[0] = skc->skc_rcv_saddr; key.dst_ip[0] = skc->skc_daddr; diff --git a/firewall/interception/ebpf/programs/monitor.c b/firewall/interception/ebpf/programs/monitor.c index b084a5d8..70bb19de 100644 --- a/firewall/interception/ebpf/programs/monitor.c +++ b/firewall/interception/ebpf/programs/monitor.c @@ -76,7 +76,7 @@ int BPF_PROG(tcp_connect, struct sock *sk) { tcp_info->ipVersion = 6; } - // Send event + // Send event bpf_ringbuf_submit(tcp_info, 0); return 0; }; @@ -95,15 +95,15 @@ int BPF_PROG(udp_v4_connect, struct sock *sk) { return 0; } - // Allocate space for the event. + // 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()); + // Read PID (Careful: This is the Thread Group ID in kernel speak!) + udp_info->pid = __builtin_bswap32((u32)(bpf_get_current_pid_tgid() >> 32)); // Set src and dist ports udp_info->sport = __builtin_bswap16(sk->__sk_common.skc_num); @@ -119,7 +119,7 @@ int BPF_PROG(udp_v4_connect, struct sock *sk) { // Set protocol. No way to detect udplite for ipv4 udp_info->protocol = UDP; - // Send event + // Send event bpf_ringbuf_submit(udp_info, 0); return 0; } @@ -138,21 +138,21 @@ int BPF_PROG(udp_v6_connect, struct sock *sk) { return 0; } - // Make sure its udp6 socket + // Make sure its udp6 socket struct udp6_sock *us = bpf_skc_to_udp6_sock(sk); if (!us) { return 0; } - // Allocate space for the event. + // 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()); + // Read PID (Careful: This is the Thread Group ID in kernel speak!) + udp_info->pid = __builtin_bswap32((u32)(bpf_get_current_pid_tgid() >> 32)); // Set src and dist ports udp_info->sport = __builtin_bswap16(sk->__sk_common.skc_num); @@ -176,7 +176,7 @@ int BPF_PROG(udp_v6_connect, struct sock *sk) { udp_info->protocol = UDPLite; } - // Send event + // Send event bpf_ringbuf_submit(udp_info, 0); return 0; } \ No newline at end of file diff --git a/firewall/packet_handler.go b/firewall/packet_handler.go index cf5e57bf..7c364451 100644 --- a/firewall/packet_handler.go +++ b/firewall/packet_handler.go @@ -628,9 +628,9 @@ func bandwidthUpdateHandler(ctx context.Context) error { return nil case bwUpdate := <-interception.BandwidthUpdates: if bwUpdate != nil { - updateBandwidth(ctx, bwUpdate) // DEBUG: // log.Debugf("filter: bandwidth update: %s", bwUpdate) + updateBandwidth(ctx, bwUpdate) } else { return errors.New("received nil bandwidth update from interception") } @@ -653,6 +653,8 @@ func updateBandwidth(ctx context.Context, bwUpdate *packet.BandwidthUpdate) { // Do not wait for connections that are locked. // TODO: Use atomic operations for updating bandwidth stats. if !conn.TryLock() { + // DEBUG: + // log.Warningf("filter: failed to lock connection for bandwidth update: %s", conn) return } defer conn.Unlock() @@ -675,7 +677,7 @@ func updateBandwidth(ctx context.Context, bwUpdate *packet.BandwidthUpdate) { if err := netquery.DefaultModule.Store.UpdateBandwidth( ctx, conn.HistoryEnabled, - conn.Process().GetID(), + conn.Process().GetKey(), conn.ID, conn.BytesReceived, conn.BytesSent, diff --git a/go.mod b/go.mod index a816bee4..7affbd42 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/safing/portmaster -go 1.19 +go 1.20 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/coreos/go-iptables v0.7.0 github.com/florianl/go-conntrack v0.4.0 github.com/florianl/go-nfqueue v1.3.1 github.com/ghodss/yaml v1.0.0 @@ -16,9 +16,9 @@ require ( github.com/hashicorp/go-version v1.6.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/oschwald/maxminddb-golang v1.12.0 github.com/safing/jess v0.3.1 - github.com/safing/portbase v0.17.0 + github.com/safing/portbase v0.17.1 github.com/safing/portmaster-android/go v0.0.0-20230605085256-6abf4c495626 github.com/safing/spn v0.6.10 github.com/shirou/gopsutil v3.21.11+incompatible @@ -28,10 +28,10 @@ require ( 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/exp v0.0.0-20230713183714-613f0c0eb8a1 - golang.org/x/net v0.12.0 + golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b + golang.org/x/net v0.13.0 golang.org/x/sync v0.3.0 - golang.org/x/sys v0.10.0 + golang.org/x/sys v0.11.0 zombiezen.com/go/sqlite v0.13.0 ) @@ -89,12 +89,12 @@ require ( golang.org/x/crypto v0.11.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.11.0 // indirect + golang.org/x/tools v0.11.1 // 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.24.1 // indirect modernc.org/mathutil v1.6.0 // indirect modernc.org/memory v1.6.0 // indirect - modernc.org/sqlite v1.24.0 // indirect + modernc.org/sqlite v1.25.0 // indirect ) diff --git a/go.sum b/go.sum index ec51652f..899fb7a5 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ 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= -github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= +github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -186,8 +186,8 @@ 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.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0= -github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg= +github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= +github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= 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= @@ -208,6 +208,8 @@ github.com/safing/portbase v0.15.2/go.mod h1:5bHi99fz7Hh/wOsZUOI631WF9ePSHk57c4f github.com/safing/portbase v0.16.2/go.mod h1:mzNCWqPbO7vIYbbK5PElGbudwd2vx4YPNawymL8Aro8= 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/portbase v0.17.1 h1:q2aNHjJw4aoqTqKOxZpxRhYCciHw1exZ7lfGuB78i1E= +github.com/safing/portbase v0.17.1/go.mod h1:1cVgDZIsPiqM5b+K88Kshir5PGIvsftYkx7y1x925+8= 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.10 h1:4fFBb7UvUzoCcOSd8immOz1Buiuasy5C1/lxfVFacBQ= @@ -307,8 +309,8 @@ golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/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= @@ -338,8 +340,8 @@ 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.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.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= @@ -391,8 +393,8 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc 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/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/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.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= @@ -408,8 +410,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.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= -golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc= +golang.org/x/tools v0.11.1/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= @@ -438,7 +440,7 @@ 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= +modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA= +modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= zombiezen.com/go/sqlite v0.13.0 h1:iEeyVqcm3fk5PCA8OQBhBxPnqrP4yYuVJBF+XZpSnOE= zombiezen.com/go/sqlite v0.13.0/go.mod h1:Ht/5Rg3Ae2hoyh1I7gbWtWAl89CNocfqeb/aAMTkJr4= diff --git a/intel/entity.go b/intel/entity.go index 0394d96c..c1685377 100644 --- a/intel/entity.go +++ b/intel/entity.go @@ -60,6 +60,7 @@ type Entity struct { //nolint:maligned IP net.IP // IPScope holds the network scope of the IP. + // For DNS requests, this signifies in which scope the DNS request was resolved. IPScope netutils.IPScope // Country holds the country the IP address (ASN) is diff --git a/nameserver/nameserver.go b/nameserver/nameserver.go index a13e98cd..19251007 100644 --- a/nameserver/nameserver.go +++ b/nameserver/nameserver.go @@ -22,7 +22,7 @@ import ( var hostname string func handleRequestAsWorker(w dns.ResponseWriter, query *dns.Msg) { - err := module.RunWorker("dns request", func(ctx context.Context) error { + err := module.RunWorker("handle dns request", func(ctx context.Context) error { return handleRequest(ctx, w, query) }) if err != nil { @@ -187,6 +187,13 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg) if rrCache != nil { conn.DNSContext = rrCache.ToDNSRequestContext() conn.Resolver = rrCache.Resolver + conn.Entity.IPScope = rrCache.Resolver.IPScope + } else { + // Get resolvers for this query to determine the resolving scope. + resolvers, _, _ := resolver.GetResolversInScope(ctx, q) + if len(resolvers) > 0 { + conn.Entity.IPScope = resolvers[0].Info.IPScope + } } switch conn.Verdict.Active { @@ -297,11 +304,14 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg) return reply(nsutil.ServerFailure("internal error: empty reply")) case rrCache.RCode == dns.RcodeNameError: // Try alternatives domain names for unofficial domain spaces. - rrCache = checkAlternativeCaches(ctx, q) - if rrCache == nil { + altRRCache := checkAlternativeCaches(ctx, q) + if altRRCache != nil { + rrCache = altRRCache + } else { // Return now if NXDomain. return reply(nsutil.NxDomain("no answer found (NXDomain)")) } + } // Check with firewall again after resolving. diff --git a/netquery/database.go b/netquery/database.go index 00e4f006..7823a700 100644 --- a/netquery/database.go +++ b/netquery/database.go @@ -2,8 +2,6 @@ package netquery import ( "context" - "crypto/sha256" - "encoding/hex" "encoding/json" "fmt" "io" @@ -395,12 +393,8 @@ func (db *Database) MarkAllHistoryConnectionsEnded(ctx context.Context) error { // UpdateBandwidth updates bandwidth data for the connection and optionally also writes // the bandwidth data to the history database. func (db *Database) UpdateBandwidth(ctx context.Context, enableHistory bool, processKey string, connID string, bytesReceived uint64, bytesSent uint64) error { - data := connID + "-" + processKey - hash := sha256.Sum256([]byte(data)) - dbConnID := hex.EncodeToString(hash[:]) - params := map[string]any{ - ":id": dbConnID, + ":id": makeNqIDFromParts(processKey, connID), } parts := []string{} @@ -481,6 +475,12 @@ func (db *Database) Save(ctx context.Context, conn Conn, enableHistory bool) err // and save some CPU cycles for the user dbNames := []DatabaseName{LiveDatabase} + // TODO: Should we only add ended connection to the history database to save + // a couple INSERTs per connection? + // This means we need to write the current live DB to the history DB on + // shutdown in order to be able to pick the back up after a restart. + + // Save to history DB if enabled. if enableHistory { dbNames = append(dbNames, HistoryDatabase) } diff --git a/netquery/manager.go b/netquery/manager.go index b5db2ca0..9116cbad 100644 --- a/netquery/manager.go +++ b/netquery/manager.go @@ -100,35 +100,42 @@ 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 { - log.Errorf("netquery: failed to convert connection %s to sqlite model: %s", conn.ID, err) + func() { + conn.Lock() + defer conn.Unlock() - continue - } + if !conn.DataIsComplete() { + return + } - // DEBUG: - // log.Tracef("netquery: updating connection %s", conn.ID) + model, err := convertConnection(conn) + if err != nil { + log.Errorf("netquery: failed to convert connection %s to sqlite model: %s", conn.ID, err) - if err := mng.store.Save(ctx, *model, conn.HistoryEnabled); err != nil { - log.Errorf("netquery: failed to save connection %s in sqlite database: %s", conn.ID, err) + return + } - continue - } + // DEBUG: + // log.Tracef("netquery: updating connection %s", conn.ID) - // we clone the record metadata from the connection - // into the new model so the portbase/database layer - // can handle NEW/UPDATE correctly. - cloned := conn.Meta().Duplicate() + // Save to netquery database. + // Do not include internal connections in history. + if err := mng.store.Save(ctx, *model, conn.HistoryEnabled && !conn.Internal); err != nil { + log.Errorf("netquery: failed to save connection %s in sqlite database: %s", conn.ID, err) + return + } - // push an update for the connection - if err := mng.pushConnUpdate(ctx, *cloned, *model); err != nil { - log.Errorf("netquery: failed to push update for conn %s via database system: %s", conn.ID, err) - } + // we clone the record metadata from the connection + // into the new model so the portbase/database layer + // can handle NEW/UPDATE correctly. + cloned := conn.Meta().Duplicate() + + // push an update for the connection + if err := mng.pushConnUpdate(ctx, *cloned, *model); err != nil { + log.Errorf("netquery: failed to push update for conn %s via database system: %s", conn.ID, err) + } + }() } } } @@ -155,18 +162,16 @@ func (mng *Manager) pushConnUpdate(_ context.Context, meta record.Meta, conn Con } // convertConnection converts conn to the local representation used -// to persist the information in SQLite. convertConnection attempts -// to lock conn and may thus block for some time. +// to persist the information in SQLite. +// The caller must hold the lock to the given network.Connection. func convertConnection(conn *network.Connection) (*Conn, error) { - conn.Lock() - defer conn.Unlock() direction := "outbound" if conn.Inbound { direction = "inbound" } c := Conn{ - ID: genConnID(conn), + ID: makeNqIDFromConn(conn), External: conn.External, IPVersion: conn.IPVersion, IPProtocol: conn.IPProtocol, @@ -265,6 +270,13 @@ func convertConnection(conn *network.Connection) (*Conn, error) { return &c, nil } -func genConnID(conn *network.Connection) string { - return conn.ID + "-" + conn.Process().GetID() +// makeNqIDFromConn creates a netquery connection ID from the network connection. +func makeNqIDFromConn(conn *network.Connection) string { + return makeNqIDFromParts(conn.Process().GetKey(), conn.ID) +} + +// makeNqIDFromParts creates a netquery connection ID from the given network +// connection ID and the process key. +func makeNqIDFromParts(processKey string, netConnID string) string { + return processKey + "-" + netConnID } diff --git a/netquery/module_api.go b/netquery/module_api.go index 1a559dbf..860f4e4c 100644 --- a/netquery/module_api.go +++ b/netquery/module_api.go @@ -89,8 +89,8 @@ func (m *module) prepare() error { if err := api.RegisterEndpoint(api.Endpoint{ Path: "netquery/query", MimeType: "application/json", - Read: api.PermitUser, - Write: api.PermitUser, + Read: api.PermitUser, // Needs read+write as the query is sent using POST data. + Write: api.PermitUser, // Needs read+write as the query is sent using POST data. BelongsTo: m.Module, HandlerFunc: queryHander.ServeHTTP, Name: "Query Connections", @@ -102,7 +102,6 @@ func (m *module) prepare() error { if err := api.RegisterEndpoint(api.Endpoint{ Path: "netquery/charts/connection-active", MimeType: "application/json", - Read: api.PermitUser, Write: api.PermitUser, BelongsTo: m.Module, HandlerFunc: chartHandler.ServeHTTP, @@ -116,7 +115,6 @@ func (m *module) prepare() error { Path: "netquery/history/clear", MimeType: "application/json", Write: api.PermitUser, - Read: api.PermitUser, BelongsTo: m.Module, HandlerFunc: func(w http.ResponseWriter, r *http.Request) { var body struct { diff --git a/network/connection.go b/network/connection.go index f19422d8..dd4d7b22 100644 --- a/network/connection.go +++ b/network/connection.go @@ -325,8 +325,9 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []stri Scope: fqdn, PID: proc.Pid, Entity: &intel.Entity{ - Domain: fqdn, - CNAME: cnames, + Domain: fqdn, + CNAME: cnames, + IPScope: netutils.Global, // Assign a global IP scope as default. }, process: proc, ProcessContext: getProcessContext(ctx, proc), @@ -367,8 +368,9 @@ func NewConnectionFromExternalDNSRequest(ctx context.Context, fqdn string, cname Scope: fqdn, PID: process.NetworkHostProcessID, Entity: &intel.Entity{ - Domain: fqdn, - CNAME: cnames, + Domain: fqdn, + CNAME: cnames, + IPScope: netutils.Global, // Assign a global IP scope as default. }, process: remoteHost, ProcessContext: getProcessContext(ctx, remoteHost), @@ -782,15 +784,6 @@ func (conn *Connection) SetFirewallHandler(handler FirewallHandler) { 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() @@ -798,6 +791,14 @@ func (conn *Connection) SetFirewallHandler(handler FirewallHandler) { conn.pktQueue = make(chan packet.Packet, 100) conn.pktQueueActive = true } + + // Start packet handler worker when new handler is set. + if conn.firewallHandler == nil { + module.StartWorker("packet handler", conn.packetHandlerWorker) + } + + // Set new handler. + conn.firewallHandler = handler } // UpdateFirewallHandler sets the firewall handler if it already set and the diff --git a/network/dns.go b/network/dns.go index bf048d46..2a1628fc 100644 --- a/network/dns.go +++ b/network/dns.go @@ -8,6 +8,7 @@ import ( "time" "github.com/miekg/dns" + "golang.org/x/exp/slices" "github.com/safing/portbase/log" "github.com/safing/portmaster/nameserver/nsutil" @@ -18,6 +19,13 @@ import ( var ( openDNSRequests = make(map[string]*Connection) // key: /fqdn openDNSRequestsLock sync.Mutex + + supportedDomainToIPRecordTypes = []uint16{ + dns.TypeA, + dns.TypeAAAA, + dns.TypeSVCB, + dns.TypeHTTPS, + } ) const ( @@ -30,6 +38,13 @@ const ( openDNSRequestLimit = 3 * time.Second ) +// IsSupportDNSRecordType returns whether the given DSN RR type is supported +// by the network package, as in the requests are specially handled and can be +// "merged" into the resulting connection. +func IsSupportDNSRecordType(rrType uint16) bool { + return slices.Contains[[]uint16, uint16](supportedDomainToIPRecordTypes, rrType) +} + func getDNSRequestCacheKey(pid int, fqdn string, qType uint16) string { return strconv.Itoa(pid) + "/" + fqdn + dns.Type(qType).String() } @@ -39,28 +54,28 @@ func removeOpenDNSRequest(pid int, fqdn string) { defer openDNSRequestsLock.Unlock() // Delete PID-specific requests. - delete(openDNSRequests, getDNSRequestCacheKey(pid, fqdn, dns.TypeA)) - delete(openDNSRequests, getDNSRequestCacheKey(pid, fqdn, dns.TypeAAAA)) + for _, dnsType := range supportedDomainToIPRecordTypes { + delete(openDNSRequests, getDNSRequestCacheKey(pid, fqdn, dnsType)) + } // If process is known, also check for non-attributed requests. if pid != process.UnidentifiedProcessID { - delete(openDNSRequests, getDNSRequestCacheKey(process.UnidentifiedProcessID, fqdn, dns.TypeA)) - delete(openDNSRequests, getDNSRequestCacheKey(process.UnidentifiedProcessID, fqdn, dns.TypeAAAA)) + for _, dnsType := range supportedDomainToIPRecordTypes { + delete(openDNSRequests, getDNSRequestCacheKey(process.UnidentifiedProcessID, fqdn, dnsType)) + } } } // SaveOpenDNSRequest saves a dns request connection that was allowed to proceed. func SaveOpenDNSRequest(q *resolver.Query, rrCache *resolver.RRCache, conn *Connection) { - // Only save requests that actually went out to reduce clutter. - if rrCache == nil || rrCache.ServedFromCache { + // Only save requests that actually went out (or triggered an async resolve) to reduce clutter. + if rrCache == nil || (rrCache.ServedFromCache && !rrCache.RequestingNew) { return } - // Try to "merge" A and AAAA requests into the resulting connection. + // Try to "merge" supported requests into the resulting connection. // Save others immediately. - switch uint16(q.QType) { - case dns.TypeA, dns.TypeAAAA: - default: + if !IsSupportDNSRecordType(uint16(q.QType)) { conn.Save() return } @@ -68,18 +83,12 @@ func SaveOpenDNSRequest(q *resolver.Query, rrCache *resolver.RRCache, conn *Conn openDNSRequestsLock.Lock() defer openDNSRequestsLock.Unlock() - // Check if there is an existing open DNS requests for the same domain/type. - // If so, save it now and replace it with the new request. - key := getDNSRequestCacheKey(conn.process.Pid, conn.Entity.Domain, uint16(q.QType)) - if existingConn, ok := openDNSRequests[key]; ok { - // End previous request and save it. - existingConn.Lock() - existingConn.Ended = conn.Started - existingConn.Unlock() - existingConn.Save() - } + // Do not check for an existing open DNS request, as duplicates in such quick + // succession are not worth keeping. + // DNS queries are usually retried pretty quick. // Save to open dns requests. + key := getDNSRequestCacheKey(conn.process.Pid, conn.Entity.Domain, uint16(q.QType)) openDNSRequests[key] = conn } @@ -103,12 +112,15 @@ func writeOpenDNSRequestsToDB() { threshold := time.Now().Add(-openDNSRequestLimit).Unix() for id, conn := range openDNSRequests { - conn.Lock() - if conn.Ended < threshold { - conn.Save() - delete(openDNSRequests, id) - } - conn.Unlock() + func() { + conn.Lock() + defer conn.Unlock() + + if conn.Ended < threshold { + conn.Save() + delete(openDNSRequests, id) + } + }() } } diff --git a/process/process.go b/process/process.go index 733ba819..e982c901 100644 --- a/process/process.go +++ b/process/process.go @@ -314,10 +314,10 @@ func loadProcess(ctx context.Context, key string, pInfo *processInfo.Process) (* return process, nil } -// GetID returns the key that is used internally to identify the process. -// The ID consists of the PID and the start time of the process as reported by +// GetKey returns the key that is used internally to identify the process. +// The key consists of the PID and the start time of the process as reported by // the system. -func (p *Process) GetID() string { +func (p *Process) GetKey() string { return p.processKey } diff --git a/resolver/ipinfo.go b/resolver/ipinfo.go index 58bfc98a..6c1eed31 100644 --- a/resolver/ipinfo.go +++ b/resolver/ipinfo.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" "sync" + "time" "github.com/safing/portbase/database" "github.com/safing/portbase/database/record" @@ -170,7 +171,7 @@ func (info *IPInfo) Save() error { } // Calculate and set cache expiry. - var expires int64 = 86400 // Minimum TTL of one day. + expires := time.Now().Unix() + 86400 // Minimum TTL of one day. for _, rd := range info.ResolvedDomains { if rd.Expires > expires { expires = rd.Expires diff --git a/resolver/resolve.go b/resolver/resolve.go index 94a8f583..09ee2685 100644 --- a/resolver/resolve.go +++ b/resolver/resolve.go @@ -293,11 +293,19 @@ func startAsyncQuery(ctx context.Context, q *Query, currentRRCache *RRCache) { // Set flag and log that we are refreshing this entry. currentRRCache.RequestingNew = true - log.Tracer(ctx).Tracef( - "resolver: cache for %s will expire in %s, refreshing async now", - q.ID(), - time.Until(time.Unix(currentRRCache.Expires, 0)).Round(time.Second), - ) + if currentRRCache.Expired() { + log.Tracer(ctx).Tracef( + "resolver: cache for %s has expired %s ago, refreshing async now", + q.ID(), + time.Since(time.Unix(currentRRCache.Expires, 0)).Round(time.Second), + ) + } else { + log.Tracer(ctx).Tracef( + "resolver: cache for %s will expire in %s, refreshing async now", + q.ID(), + time.Until(time.Unix(currentRRCache.Expires, 0)).Round(time.Second), + ) + } // resolve async module.StartWorker("resolve async", func(asyncCtx context.Context) error { diff --git a/updates/os_integration_linux.go b/updates/os_integration_linux.go index 4f6554c0..b20e3da4 100644 --- a/updates/os_integration_linux.go +++ b/updates/os_integration_linux.go @@ -130,8 +130,8 @@ func upgradeSystemIntegrationFileContents( } // Check if we are allowed to upgrade from the existing file. - if !slices.Contains[string](permittedUpgradeHashes, existingHexSum) { - return fmt.Errorf("%s at %s %w, as it is not a previously published version and cannot be automatically upgraded - try installing again", name, filePath, ErrRequiresManualUpgrade) + if !slices.Contains[[]string, string](permittedUpgradeHashes, existingHexSum) { + return fmt.Errorf("%s at %s (sha256:%s) %w, as it is not a previously published version and cannot be automatically upgraded - try installing again", name, filePath, existingHexSum, ErrRequiresManualUpgrade) } // Start with upgrade!