mirror of
https://github.com/safing/portmaster
synced 2025-09-02 18:49:14 +00:00
Show notification about manual DNS setup instead of compatibility notice
This commit is contained in:
parent
f5afe8b5df
commit
2c3def3bc4
5 changed files with 98 additions and 28 deletions
|
@ -16,7 +16,7 @@ var (
|
||||||
module *modules.Module
|
module *modules.Module
|
||||||
|
|
||||||
selfcheckTask *modules.Task
|
selfcheckTask *modules.Task
|
||||||
selfcheckTaskRetryAfter = 10 * time.Second
|
selfcheckTaskRetryAfter = 5 * time.Second
|
||||||
|
|
||||||
// selfCheckIsFailing holds whether or not the self-check is currently
|
// selfCheckIsFailing holds whether or not the self-check is currently
|
||||||
// failing. This helps other failure systems to not make noise when there is
|
// failing. This helps other failure systems to not make noise when there is
|
||||||
|
@ -47,6 +47,8 @@ func prep() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() error {
|
func start() error {
|
||||||
|
startNotify()
|
||||||
|
|
||||||
selfcheckTask = module.NewTask("compatibility self-check", selfcheckTaskFunc).
|
selfcheckTask = module.NewTask("compatibility self-check", selfcheckTaskFunc).
|
||||||
Repeat(5 * time.Minute).
|
Repeat(5 * time.Minute).
|
||||||
MaxDelay(selfcheckTaskRetryAfter).
|
MaxDelay(selfcheckTaskRetryAfter).
|
||||||
|
@ -74,12 +76,18 @@ func stop() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func selfcheckTaskFunc(ctx context.Context, task *modules.Task) error {
|
func selfcheckTaskFunc(ctx context.Context, task *modules.Task) error {
|
||||||
|
// Create tracing logger.
|
||||||
|
ctx, tracer := log.AddTracer(ctx)
|
||||||
|
defer tracer.Submit()
|
||||||
|
tracer.Tracef("compat: running self-check")
|
||||||
|
|
||||||
// Run selfcheck and return if successful.
|
// Run selfcheck and return if successful.
|
||||||
issue, err := selfcheck(ctx)
|
issue, err := selfcheck(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
selfCheckIsFailing.UnSet()
|
selfCheckIsFailing.UnSet()
|
||||||
selfcheckFails = 0
|
selfcheckFails = 0
|
||||||
resetSystemIssue()
|
resetSystemIssue()
|
||||||
|
tracer.Debugf("compat: self-check successful")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +96,7 @@ func selfcheckTaskFunc(ctx context.Context, task *modules.Task) error {
|
||||||
selfCheckIsFailing.Set()
|
selfCheckIsFailing.Set()
|
||||||
selfcheckFails++
|
selfcheckFails++
|
||||||
|
|
||||||
log.Errorf("compat: %s", err)
|
tracer.Errorf("compat: %s", err)
|
||||||
if selfcheckFails >= selfcheckFailThreshold {
|
if selfcheckFails >= selfcheckFailThreshold {
|
||||||
issue.notify(err)
|
issue.notify(err)
|
||||||
}
|
}
|
||||||
|
@ -100,7 +108,7 @@ func selfcheckTaskFunc(ctx context.Context, task *modules.Task) error {
|
||||||
selfcheckFails = 0
|
selfcheckFails = 0
|
||||||
|
|
||||||
// Only log internal errors, but don't notify.
|
// Only log internal errors, but don't notify.
|
||||||
log.Warningf("compat: %s", err)
|
tracer.Warningf("compat: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -3,10 +3,12 @@ package compat
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/config"
|
||||||
"github.com/safing/portbase/log"
|
"github.com/safing/portbase/log"
|
||||||
"github.com/safing/portbase/modules"
|
"github.com/safing/portbase/modules"
|
||||||
"github.com/safing/portbase/notifications"
|
"github.com/safing/portbase/notifications"
|
||||||
|
@ -19,6 +21,7 @@ type baseIssue struct {
|
||||||
title string //nolint:structcheck // Inherited.
|
title string //nolint:structcheck // Inherited.
|
||||||
message string //nolint:structcheck // Inherited.
|
message string //nolint:structcheck // Inherited.
|
||||||
level notifications.Type //nolint:structcheck // Inherited.
|
level notifications.Type //nolint:structcheck // Inherited.
|
||||||
|
actions []*notifications.Action //nolint:structcheck // Inherited.
|
||||||
}
|
}
|
||||||
|
|
||||||
type systemIssue baseIssue
|
type systemIssue baseIssue
|
||||||
|
@ -26,6 +29,10 @@ type systemIssue baseIssue
|
||||||
type appIssue baseIssue
|
type appIssue baseIssue
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// Copy of firewall.CfgOptionDNSQueryInterceptionKey.
|
||||||
|
cfgOptionDNSQueryInterceptionKey = "filter/dnsQueryInterception"
|
||||||
|
dnsQueryInterception config.BoolOption
|
||||||
|
|
||||||
systemIssueNotification *notifications.Notification
|
systemIssueNotification *notifications.Notification
|
||||||
systemIssueNotificationLock sync.Mutex
|
systemIssueNotificationLock sync.Mutex
|
||||||
|
|
||||||
|
@ -41,6 +48,22 @@ var (
|
||||||
message: "Portmaster detected that something is interfering with its operation. This could be a VPN, an Anti-Virus or another network protection software. Please check if you are running an incompatible [VPN client](https://docs.safing.io/portmaster/install/status/vpn-compatibility) or [software](https://docs.safing.io/portmaster/install/status/software-compatibility). Otherwise, please report the issue via [GitHub](https://github.com/safing/portmaster/issues) or send a mail to [support@safing.io](mailto:support@safing.io) so we can help you out.",
|
message: "Portmaster detected that something is interfering with its operation. This could be a VPN, an Anti-Virus or another network protection software. Please check if you are running an incompatible [VPN client](https://docs.safing.io/portmaster/install/status/vpn-compatibility) or [software](https://docs.safing.io/portmaster/install/status/software-compatibility). Otherwise, please report the issue via [GitHub](https://github.com/safing/portmaster/issues) or send a mail to [support@safing.io](mailto:support@safing.io) so we can help you out.",
|
||||||
level: notifications.Error,
|
level: notifications.Error,
|
||||||
}
|
}
|
||||||
|
// manualDNSSetupRequired is additionally initialized in startNotify().
|
||||||
|
manualDNSSetupRequired = &systemIssue{
|
||||||
|
id: "compat:manual-dns-setup-required",
|
||||||
|
title: "Manual DNS Setup Required",
|
||||||
|
level: notifications.Error,
|
||||||
|
actions: []*notifications.Action{
|
||||||
|
{
|
||||||
|
Text: "Revert",
|
||||||
|
Type: notifications.ActionTypeOpenSetting,
|
||||||
|
Payload: ¬ifications.ActionTypeOpenSettingPayload{
|
||||||
|
Key: cfgOptionDNSQueryInterceptionKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
manualDNSSetupRequiredMessage = "You have disabled Seamless DNS Integration. As a result, Portmaster can no longer protect you or filter connections reliably. To fix this, you have to manually configure %s as the DNS Server in your system and in any conflicting application. This message will disappear 10 seconds after correct configuration."
|
||||||
|
|
||||||
secureDNSBypassIssue = &appIssue{
|
secureDNSBypassIssue = &appIssue{
|
||||||
id: "compat:secure-dns-bypass-%s",
|
id: "compat:secure-dns-bypass-%s",
|
||||||
|
@ -58,6 +81,37 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func startNotify() {
|
||||||
|
dnsQueryInterception = config.Concurrent.GetAsBool(cfgOptionDNSQueryInterceptionKey, true)
|
||||||
|
|
||||||
|
systemIssueNotificationLock.Lock()
|
||||||
|
defer systemIssueNotificationLock.Unlock()
|
||||||
|
|
||||||
|
manualDNSSetupRequired.message = fmt.Sprintf(
|
||||||
|
manualDNSSetupRequiredMessage,
|
||||||
|
`"127.0.0.1"`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNameserverListenIP sets the IP address the nameserver is listening on.
|
||||||
|
// The IP address is used in compatibility notifications.
|
||||||
|
func SetNameserverListenIP(ip net.IP) {
|
||||||
|
systemIssueNotificationLock.Lock()
|
||||||
|
defer systemIssueNotificationLock.Unlock()
|
||||||
|
|
||||||
|
manualDNSSetupRequired.message = fmt.Sprintf(
|
||||||
|
manualDNSSetupRequiredMessage,
|
||||||
|
`"`+ip.String()+`"`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func systemCompatOrManualDNSIssue() *systemIssue {
|
||||||
|
if dnsQueryInterception() {
|
||||||
|
return systemCompatibilityIssue
|
||||||
|
}
|
||||||
|
return manualDNSSetupRequired
|
||||||
|
}
|
||||||
|
|
||||||
func (issue *systemIssue) notify(err error) {
|
func (issue *systemIssue) notify(err error) {
|
||||||
systemIssueNotificationLock.Lock()
|
systemIssueNotificationLock.Lock()
|
||||||
defer systemIssueNotificationLock.Unlock()
|
defer systemIssueNotificationLock.Unlock()
|
||||||
|
@ -79,6 +133,7 @@ func (issue *systemIssue) notify(err error) {
|
||||||
Title: issue.title,
|
Title: issue.title,
|
||||||
Message: issue.message,
|
Message: issue.message,
|
||||||
ShowOnSystem: true,
|
ShowOnSystem: true,
|
||||||
|
AvailableActions: issue.actions,
|
||||||
}
|
}
|
||||||
notifications.Notify(n)
|
notifications.Notify(n)
|
||||||
|
|
||||||
|
@ -146,12 +201,15 @@ func (issue *appIssue) notify(proc *process.Process) {
|
||||||
Title: fmt.Sprintf(issue.title, p.Name),
|
Title: fmt.Sprintf(issue.title, p.Name),
|
||||||
Message: message,
|
Message: message,
|
||||||
ShowOnSystem: true,
|
ShowOnSystem: true,
|
||||||
AvailableActions: []*notifications.Action{
|
AvailableActions: issue.actions,
|
||||||
|
}
|
||||||
|
if len(n.AvailableActions) == 0 {
|
||||||
|
n.AvailableActions = []*notifications.Action{
|
||||||
{
|
{
|
||||||
ID: "ack",
|
ID: "ack",
|
||||||
Text: "OK",
|
Text: "OK",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
notifications.Notify(n)
|
notifications.Notify(n)
|
||||||
|
|
||||||
|
|
|
@ -28,12 +28,12 @@ var (
|
||||||
systemIntegrationCheckDialNet = fmt.Sprintf("ip4:%d", uint8(SystemIntegrationCheckProtocol))
|
systemIntegrationCheckDialNet = fmt.Sprintf("ip4:%d", uint8(SystemIntegrationCheckProtocol))
|
||||||
systemIntegrationCheckDialIP = SystemIntegrationCheckDstIP.String()
|
systemIntegrationCheckDialIP = SystemIntegrationCheckDstIP.String()
|
||||||
systemIntegrationCheckPackets = make(chan packet.Packet, 1)
|
systemIntegrationCheckPackets = make(chan packet.Packet, 1)
|
||||||
systemIntegrationCheckWaitDuration = 30 * time.Second
|
systemIntegrationCheckWaitDuration = 20 * time.Second
|
||||||
|
|
||||||
// DNSCheckInternalDomainScope is the domain scope to use for dns checks.
|
// DNSCheckInternalDomainScope is the domain scope to use for dns checks.
|
||||||
DNSCheckInternalDomainScope = ".self-check." + resolver.InternalSpecialUseDomain
|
DNSCheckInternalDomainScope = ".self-check." + resolver.InternalSpecialUseDomain
|
||||||
dnsCheckReceivedDomain = make(chan string, 1)
|
dnsCheckReceivedDomain = make(chan string, 1)
|
||||||
dnsCheckWaitDuration = 30 * time.Second
|
dnsCheckWaitDuration = 20 * time.Second
|
||||||
dnsCheckAnswerLock sync.Mutex
|
dnsCheckAnswerLock sync.Mutex
|
||||||
dnsCheckAnswer net.IP
|
dnsCheckAnswer net.IP
|
||||||
)
|
)
|
||||||
|
@ -61,7 +61,7 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create system integration conn: %w", err)
|
return nil, fmt.Errorf("failed to create system integration conn: %w", err)
|
||||||
}
|
}
|
||||||
_, err = conn.Write([]byte("SELF-CHECK"))
|
_, err = conn.Write([]byte("PORTMASTER SELF CHECK"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to send system integration packet: %w", err)
|
return nil, fmt.Errorf("failed to send system integration packet: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) {
|
||||||
select {
|
select {
|
||||||
case <-systemIntegrationCheckPackets:
|
case <-systemIntegrationCheckPackets:
|
||||||
// Check passed!
|
// Check passed!
|
||||||
log.Tracef("compat: self-check #1: system integration check passed")
|
log.Tracer(ctx).Tracef("compat: self-check #1: system integration check passed")
|
||||||
case <-time.After(systemIntegrationCheckWaitDuration):
|
case <-time.After(systemIntegrationCheckWaitDuration):
|
||||||
return systemIntegrationIssue, fmt.Errorf("self-check #1: system integration check failed: did not receive test packet after %s", systemIntegrationCheckWaitDuration)
|
return systemIntegrationIssue, fmt.Errorf("self-check #1: system integration check failed: did not receive test packet after %s", systemIntegrationCheckWaitDuration)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
@ -139,12 +139,12 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) {
|
||||||
select {
|
select {
|
||||||
case receivedTestDomain := <-dnsCheckReceivedDomain:
|
case receivedTestDomain := <-dnsCheckReceivedDomain:
|
||||||
if receivedTestDomain != randomSubdomain {
|
if receivedTestDomain != randomSubdomain {
|
||||||
return systemCompatibilityIssue, fmt.Errorf("self-check #2: dns integration check failed: received unmatching subdomain %q", receivedTestDomain)
|
return systemCompatOrManualDNSIssue(), fmt.Errorf("self-check #2: dns integration check failed: received unmatching subdomain %q", receivedTestDomain)
|
||||||
}
|
}
|
||||||
case <-time.After(dnsCheckWaitDuration):
|
case <-time.After(dnsCheckWaitDuration):
|
||||||
return systemCompatibilityIssue, fmt.Errorf("self-check #2: dns integration check failed: did not receive test query after %s", dnsCheckWaitDuration)
|
return systemCompatOrManualDNSIssue(), fmt.Errorf("self-check #2: dns integration check failed: did not receive test query after %s", dnsCheckWaitDuration)
|
||||||
}
|
}
|
||||||
log.Tracef("compat: self-check #2: dns integration query check passed")
|
log.Tracer(ctx).Tracef("compat: self-check #2: dns integration query check passed")
|
||||||
|
|
||||||
// Step 3: Have the nameserver respond with random data in the answer section.
|
// Step 3: Have the nameserver respond with random data in the answer section.
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) {
|
||||||
if !dnsCheckReturnedIP.Equal(randomAnswer) {
|
if !dnsCheckReturnedIP.Equal(randomAnswer) {
|
||||||
return systemCompatibilityIssue, fmt.Errorf("self-check #3: dns integration check failed: received unmatching response %q", dnsCheckReturnedIP)
|
return systemCompatibilityIssue, fmt.Errorf("self-check #3: dns integration check failed: received unmatching response %q", dnsCheckReturnedIP)
|
||||||
}
|
}
|
||||||
log.Tracef("compat: self-check #3: dns integration response check passed")
|
log.Tracer(ctx).Tracef("compat: self-check #3: dns integration response check passed")
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/safing/portbase/log"
|
"github.com/safing/portbase/log"
|
||||||
"github.com/safing/portbase/modules"
|
"github.com/safing/portbase/modules"
|
||||||
"github.com/safing/portbase/modules/subsystems"
|
"github.com/safing/portbase/modules/subsystems"
|
||||||
|
"github.com/safing/portmaster/compat"
|
||||||
"github.com/safing/portmaster/firewall"
|
"github.com/safing/portmaster/firewall"
|
||||||
"github.com/safing/portmaster/netenv"
|
"github.com/safing/portmaster/netenv"
|
||||||
)
|
)
|
||||||
|
@ -53,6 +54,9 @@ func start() error {
|
||||||
return fmt.Errorf("failed to parse nameserver listen address: %w", err)
|
return fmt.Errorf("failed to parse nameserver listen address: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tell the compat module where we are listening.
|
||||||
|
compat.SetNameserverListenIP(ip1)
|
||||||
|
|
||||||
// Get own hostname.
|
// Get own hostname.
|
||||||
hostname, err = os.Hostname()
|
hostname, err = os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -168,7 +168,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
tracer.Warningf("nameserver: external request for %s%s, ignoring", q.FQDN, q.QType)
|
tracer.Warningf("nameserver: external request from %s for %s%s, ignoring", remoteAddr, q.FQDN, q.QType)
|
||||||
return reply(nsutil.Refused("external queries are not permitted"))
|
return reply(nsutil.Refused("external queries are not permitted"))
|
||||||
}
|
}
|
||||||
conn.Lock()
|
conn.Lock()
|
||||||
|
|
Loading…
Add table
Reference in a new issue