mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
* Move portbase into monorepo * Add new simple module mgr * [WIP] Switch to new simple module mgr * Add StateMgr and more worker variants * [WIP] Switch more modules * [WIP] Switch more modules * [WIP] swtich more modules * [WIP] switch all SPN modules * [WIP] switch all service modules * [WIP] Convert all workers to the new module system * [WIP] add new task system to module manager * [WIP] Add second take for scheduling workers * [WIP] Add FIXME for bugs in new scheduler * [WIP] Add minor improvements to scheduler * [WIP] Add new worker scheduler * [WIP] Fix more bug related to new module system * [WIP] Fix start handing of the new module system * [WIP] Improve startup process * [WIP] Fix minor issues * [WIP] Fix missing subsystem in settings * [WIP] Initialize managers in constructor * [WIP] Move module event initialization to constrictors * [WIP] Fix setting for enabling and disabling the SPN module * [WIP] Move API registeration into module construction * [WIP] Update states mgr for all modules * [WIP] Add CmdLine operation support * Add state helper methods to module group and instance * Add notification and module status handling to status package * Fix starting issues * Remove pilot widget and update security lock to new status data * Remove debug logs * Improve http server shutdown * Add workaround for cleanly shutting down firewall+netquery * Improve logging * Add syncing states with notifications for new module system * Improve starting, stopping, shutdown; resolve FIXMEs/TODOs * [WIP] Fix most unit tests * Review new module system and fix minor issues * Push shutdown and restart events again via API * Set sleep mode via interface * Update example/template module * [WIP] Fix spn/cabin unit test * Remove deprecated UI elements * Make log output more similar for the logging transition phase * Switch spn hub and observer cmds to new module system * Fix log sources * Make worker mgr less error prone * Fix tests and minor issues * Fix observation hub * Improve shutdown and restart handling * Split up big connection.go source file * Move varint and dsd packages to structures repo * Improve expansion test * Fix linter warnings * Fix interception module on windows * Fix linter errors --------- Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
206 lines
7.2 KiB
Go
206 lines
7.2 KiB
Go
package compat
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/safing/portmaster/base/log"
|
|
"github.com/safing/portmaster/base/rng"
|
|
"github.com/safing/portmaster/service/mgr"
|
|
"github.com/safing/portmaster/service/netenv"
|
|
"github.com/safing/portmaster/service/network/packet"
|
|
"github.com/safing/portmaster/service/resolver"
|
|
)
|
|
|
|
var (
|
|
selfcheckLock sync.Mutex
|
|
|
|
// SystemIntegrationCheckDstIP is the IP address to send a packet to for the
|
|
// system integration test.
|
|
SystemIntegrationCheckDstIP = net.IPv4(127, 65, 67, 75)
|
|
// SystemIntegrationCheckProtocol is the IP protocol to use for the system
|
|
// integration test.
|
|
SystemIntegrationCheckProtocol = packet.AnyHostInternalProtocol61
|
|
|
|
systemIntegrationCheckDialNet = fmt.Sprintf("ip4:%d", uint8(SystemIntegrationCheckProtocol))
|
|
systemIntegrationCheckDialIP = SystemIntegrationCheckDstIP.String()
|
|
systemIntegrationCheckPackets = make(chan packet.Packet, 1)
|
|
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 = 45 * time.Second
|
|
dnsCheckAnswerLock sync.Mutex
|
|
dnsCheckAnswer net.IP
|
|
|
|
errSelfcheckSkipped = errors.New("self-check skipped")
|
|
)
|
|
|
|
func selfcheck(ctx context.Context) (issue *systemIssue, err error) {
|
|
selfcheckLock.Lock()
|
|
defer selfcheckLock.Unlock()
|
|
|
|
// Step 0: Check if self-check makes sense.
|
|
if !netenv.Online() {
|
|
return nil, fmt.Errorf("%w: device is offline or in limited network", errSelfcheckSkipped)
|
|
}
|
|
|
|
// Step 1: Check if the system integration sees a packet.
|
|
|
|
// Empty recv channel.
|
|
select {
|
|
case <-systemIntegrationCheckPackets:
|
|
case <-ctx.Done():
|
|
return nil, context.Canceled
|
|
default:
|
|
}
|
|
|
|
// Send packet.
|
|
conn, err := net.DialTimeout(
|
|
systemIntegrationCheckDialNet,
|
|
systemIntegrationCheckDialIP,
|
|
time.Second,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create system integration conn: %w", err)
|
|
}
|
|
_, err = conn.Write([]byte("PORTMASTER SELF CHECK"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to send system integration packet: %w", err)
|
|
}
|
|
|
|
// Wait for packet.
|
|
select {
|
|
case <-systemIntegrationCheckPackets:
|
|
// 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():
|
|
return nil, context.Canceled
|
|
}
|
|
|
|
// Step 2: Check if a DNS request arrives at the nameserver
|
|
// This step necessary also includes some setup for step 3.
|
|
|
|
// Generate random subdomain.
|
|
randomSubdomainBytes, err := rng.Bytes(16)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("self-check #2: failed to get random bytes for subdomain check: %w", err)
|
|
}
|
|
randomSubdomain := "a" + strings.ToLower(hex.EncodeToString(randomSubdomainBytes)) + "b"
|
|
|
|
// Generate random answer.
|
|
var B, C, D uint64
|
|
B, err = rng.Number(255)
|
|
if err == nil {
|
|
C, err = rng.Number(255)
|
|
}
|
|
if err == nil {
|
|
D, err = rng.Number(255)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("self-check #2: failed to get random number for subdomain check response: %w", err)
|
|
}
|
|
randomAnswer := net.IPv4(127, byte(B), byte(C), byte(D))
|
|
func() {
|
|
dnsCheckAnswerLock.Lock()
|
|
defer dnsCheckAnswerLock.Unlock()
|
|
dnsCheckAnswer = randomAnswer
|
|
}()
|
|
|
|
// Setup variables for lookup worker.
|
|
var (
|
|
dnsCheckReturnedIP net.IP
|
|
dnsCheckLookupError = make(chan error)
|
|
)
|
|
|
|
// Empty recv channel.
|
|
select {
|
|
case <-dnsCheckReceivedDomain:
|
|
case <-ctx.Done():
|
|
return nil, context.Canceled
|
|
default:
|
|
}
|
|
|
|
// Start worker for the DNS lookup.
|
|
module.mgr.Go("dns check lookup", func(_ *mgr.WorkerCtx) error {
|
|
ips, err := net.LookupIP(randomSubdomain + DNSCheckInternalDomainScope)
|
|
if err == nil && len(ips) > 0 {
|
|
dnsCheckReturnedIP = ips[0]
|
|
}
|
|
select {
|
|
case dnsCheckLookupError <- err:
|
|
case <-time.After(dnsCheckWaitDuration * 2):
|
|
case <-ctx.Done():
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
// Wait for the resolver to receive the query.
|
|
select {
|
|
case receivedTestDomain := <-dnsCheckReceivedDomain:
|
|
if receivedTestDomain != randomSubdomain {
|
|
return systemCompatOrManualDNSIssue(), fmt.Errorf("self-check #2: dns integration check failed: received unmatching subdomain %q", receivedTestDomain)
|
|
}
|
|
case <-time.After(dnsCheckWaitDuration):
|
|
return systemCompatOrManualDNSIssue(), fmt.Errorf("self-check #2: dns integration check failed: did not receive test query after %s", dnsCheckWaitDuration)
|
|
}
|
|
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.
|
|
|
|
// Wait for the reply from the resolver.
|
|
select {
|
|
case err := <-dnsCheckLookupError:
|
|
if err != nil {
|
|
return systemCompatibilityIssue, fmt.Errorf("self-check #3: dns integration check failed: failed to receive test response: %w", err)
|
|
}
|
|
case <-time.After(dnsCheckWaitDuration):
|
|
return systemCompatibilityIssue, fmt.Errorf("self-check #3: dns integration check failed: did not receive test response after %s", dnsCheckWaitDuration)
|
|
case <-ctx.Done():
|
|
return nil, context.Canceled
|
|
}
|
|
|
|
// Check response.
|
|
if !dnsCheckReturnedIP.Equal(randomAnswer) {
|
|
return systemCompatibilityIssue, fmt.Errorf("self-check #3: dns integration check failed: received unmatching response %q", dnsCheckReturnedIP)
|
|
}
|
|
log.Tracer(ctx).Tracef("compat: self-check #3: dns integration response check passed")
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
/*
|
|
|
|
* Check if the system integration sees a packet:
|
|
* Send raw IP packet with random content and protocol, report finding to compat module.
|
|
* use `Dial("ip4:61", "127.65.67.75")`.
|
|
* Firewall reports back the data seen on `ip4:61` to IP `127.65.67.75`.
|
|
* If this fails, the system integration is broken. -> Integration Issue
|
|
* Check if a DNS request arrives at the nameserver:
|
|
* Send A question for `[random-subdomain].self-check.portmaster.home.arpa.`.
|
|
* Nameserver reports back the data seen.
|
|
* If this fails, redirection to the nameserver fails.
|
|
* This means there is another software interfering with DNS. -> Compatibility Issue
|
|
* Have the nameserver respond with random data in the answer section.
|
|
* Compat provides nameserver with random response data.
|
|
* Compat module checks if the received data matches.
|
|
* If this fails, redirection to the nameserver fails.
|
|
* This means there is another software interfering with DNS on the return path. -> Compatibility Issue
|
|
* DROPPED: If resolvers are reported failing, but we are online:
|
|
* Send out plain DNS requests to one.one.one.one. and dns.quad9.net via the Go standard lookup and check if the responses are correct.
|
|
* If not, something is blocking the Portmaster -> Secure DNS Issue
|
|
* Discuss if this is necessary:
|
|
* Does this improve from only having a failed TCP connection to the resolver?
|
|
* Could another program block port 853, but fully leave requests for one.one.one.one. to port 53 alone?
|
|
|
|
*/
|