Show notification about manual DNS setup instead of compatibility notice

This commit is contained in:
Daniel 2022-04-15 13:06:59 +02:00
parent f5afe8b5df
commit 2c3def3bc4
5 changed files with 98 additions and 28 deletions

View file

@ -16,7 +16,7 @@ var (
module *modules.Module
selfcheckTask *modules.Task
selfcheckTaskRetryAfter = 10 * time.Second
selfcheckTaskRetryAfter = 5 * 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
@ -47,6 +47,8 @@ func prep() error {
}
func start() error {
startNotify()
selfcheckTask = module.NewTask("compatibility self-check", selfcheckTaskFunc).
Repeat(5 * time.Minute).
MaxDelay(selfcheckTaskRetryAfter).
@ -74,12 +76,18 @@ func stop() 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.
issue, err := selfcheck(ctx)
if err == nil {
selfCheckIsFailing.UnSet()
selfcheckFails = 0
resetSystemIssue()
tracer.Debugf("compat: self-check successful")
return nil
}
@ -88,7 +96,7 @@ func selfcheckTaskFunc(ctx context.Context, task *modules.Task) error {
selfCheckIsFailing.Set()
selfcheckFails++
log.Errorf("compat: %s", err)
tracer.Errorf("compat: %s", err)
if selfcheckFails >= selfcheckFailThreshold {
issue.notify(err)
}
@ -100,7 +108,7 @@ func selfcheckTaskFunc(ctx context.Context, task *modules.Task) error {
selfcheckFails = 0
// Only log internal errors, but don't notify.
log.Warningf("compat: %s", err)
tracer.Warningf("compat: %s", err)
}
return nil

View file

@ -3,10 +3,12 @@ package compat
import (
"context"
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/safing/portbase/config"
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portbase/notifications"
@ -15,10 +17,11 @@ import (
)
type baseIssue struct {
id string //nolint:structcheck // Inherited.
title string //nolint:structcheck // Inherited.
message string //nolint:structcheck // Inherited.
level notifications.Type //nolint:structcheck // Inherited.
id string //nolint:structcheck // Inherited.
title string //nolint:structcheck // Inherited.
message string //nolint:structcheck // Inherited.
level notifications.Type //nolint:structcheck // Inherited.
actions []*notifications.Action //nolint:structcheck // Inherited.
}
type systemIssue baseIssue
@ -26,6 +29,10 @@ type systemIssue baseIssue
type appIssue baseIssue
var (
// Copy of firewall.CfgOptionDNSQueryInterceptionKey.
cfgOptionDNSQueryInterceptionKey = "filter/dnsQueryInterception"
dnsQueryInterception config.BoolOption
systemIssueNotification *notifications.Notification
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.",
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: &notifications.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{
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) {
systemIssueNotificationLock.Lock()
defer systemIssueNotificationLock.Unlock()
@ -74,11 +128,12 @@ func (issue *systemIssue) notify(err error) {
// Create new notification.
n := &notifications.Notification{
EventID: issue.id,
Type: issue.level,
Title: issue.title,
Message: issue.message,
ShowOnSystem: true,
EventID: issue.id,
Type: issue.level,
Title: issue.title,
Message: issue.message,
ShowOnSystem: true,
AvailableActions: issue.actions,
}
notifications.Notify(n)
@ -141,17 +196,20 @@ func (issue *appIssue) notify(proc *process.Process) {
// Create a new notification.
n = &notifications.Notification{
EventID: eventID,
Type: issue.level,
Title: fmt.Sprintf(issue.title, p.Name),
Message: message,
ShowOnSystem: true,
AvailableActions: []*notifications.Action{
EventID: eventID,
Type: issue.level,
Title: fmt.Sprintf(issue.title, p.Name),
Message: message,
ShowOnSystem: true,
AvailableActions: issue.actions,
}
if len(n.AvailableActions) == 0 {
n.AvailableActions = []*notifications.Action{
{
ID: "ack",
Text: "OK",
},
},
}
}
notifications.Notify(n)

View file

@ -28,12 +28,12 @@ var (
systemIntegrationCheckDialNet = fmt.Sprintf("ip4:%d", uint8(SystemIntegrationCheckProtocol))
systemIntegrationCheckDialIP = SystemIntegrationCheckDstIP.String()
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 = ".self-check." + resolver.InternalSpecialUseDomain
dnsCheckReceivedDomain = make(chan string, 1)
dnsCheckWaitDuration = 30 * time.Second
dnsCheckWaitDuration = 20 * time.Second
dnsCheckAnswerLock sync.Mutex
dnsCheckAnswer net.IP
)
@ -61,7 +61,7 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) {
if err != nil {
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 {
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 {
case <-systemIntegrationCheckPackets:
// 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):
return systemIntegrationIssue, fmt.Errorf("self-check #1: system integration check failed: did not receive test packet after %s", systemIntegrationCheckWaitDuration)
case <-ctx.Done():
@ -139,12 +139,12 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) {
select {
case receivedTestDomain := <-dnsCheckReceivedDomain:
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):
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.
@ -164,7 +164,7 @@ func selfcheck(ctx context.Context) (issue *systemIssue, err error) {
if !dnsCheckReturnedIP.Equal(randomAnswer) {
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
}

View file

@ -13,6 +13,7 @@ import (
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portbase/modules/subsystems"
"github.com/safing/portmaster/compat"
"github.com/safing/portmaster/firewall"
"github.com/safing/portmaster/netenv"
)
@ -53,6 +54,9 @@ func start() error {
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.
hostname, err = os.Hostname()
if err != nil {

View file

@ -168,7 +168,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
}
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"))
}
conn.Lock()