mirror of
https://github.com/safing/portmaster
synced 2025-04-25 13:29:10 +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>
186 lines
4.8 KiB
Go
186 lines
4.8 KiB
Go
package patrol
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/tevino/abool"
|
|
|
|
"github.com/safing/portmaster/base/log"
|
|
"github.com/safing/portmaster/service/mgr"
|
|
"github.com/safing/portmaster/spn/conf"
|
|
)
|
|
|
|
var httpsConnectivityConfirmed = abool.NewBool(true)
|
|
|
|
// HTTPSConnectivityConfirmed returns whether the last HTTPS connectivity check succeeded.
|
|
// Is "true" before first test.
|
|
func HTTPSConnectivityConfirmed() bool {
|
|
return httpsConnectivityConfirmed.IsSet()
|
|
}
|
|
|
|
func connectivityCheckTask(wc *mgr.WorkerCtx) error {
|
|
// Start tracing logs.
|
|
ctx, tracer := log.AddTracer(wc.Ctx())
|
|
defer tracer.Submit()
|
|
|
|
// Run checks and report status.
|
|
success := runConnectivityChecks(ctx)
|
|
if success {
|
|
tracer.Info("spn/patrol: all connectivity checks succeeded")
|
|
if httpsConnectivityConfirmed.SetToIf(false, true) {
|
|
module.EventChangeSignal.Submit(struct{}{})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
tracer.Errorf("spn/patrol: connectivity check failed")
|
|
if httpsConnectivityConfirmed.SetToIf(true, false) {
|
|
module.EventChangeSignal.Submit(struct{}{})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func runConnectivityChecks(ctx context.Context) (ok bool) {
|
|
switch {
|
|
case conf.HubHasIPv4() && !runHTTPSConnectivityChecks(ctx, "tcp4"):
|
|
return false
|
|
case conf.HubHasIPv6() && !runHTTPSConnectivityChecks(ctx, "tcp6"):
|
|
return false
|
|
default:
|
|
// All checks passed.
|
|
return true
|
|
}
|
|
}
|
|
|
|
func runHTTPSConnectivityChecks(ctx context.Context, network string) (ok bool) {
|
|
// Step 1: Check 1 domain, require 100%
|
|
if checkHTTPSConnectivity(ctx, network, 1, 1) {
|
|
return true
|
|
}
|
|
|
|
// Step 2: Check 5 domains, require 80%
|
|
if checkHTTPSConnectivity(ctx, network, 5, 0.8) {
|
|
return true
|
|
}
|
|
|
|
// Step 3: Check 20 domains, require 70%
|
|
if checkHTTPSConnectivity(ctx, network, 20, 0.7) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func checkHTTPSConnectivity(ctx context.Context, network string, checks int, requiredSuccessFraction float32) (ok bool) {
|
|
log.Tracer(ctx).Tracef(
|
|
"spn/patrol: testing connectivity via https (%d checks; %.0f%% required)",
|
|
checks,
|
|
requiredSuccessFraction*100,
|
|
)
|
|
|
|
// Run tests.
|
|
var succeeded int
|
|
for range checks {
|
|
if checkHTTPSConnection(ctx, network) {
|
|
succeeded++
|
|
}
|
|
}
|
|
|
|
// Check success.
|
|
successFraction := float32(succeeded) / float32(checks)
|
|
if successFraction < requiredSuccessFraction {
|
|
log.Tracer(ctx).Warningf(
|
|
"spn/patrol: https/%s connectivity check failed: %d/%d (%.0f%%)",
|
|
network,
|
|
succeeded,
|
|
checks,
|
|
successFraction*100,
|
|
)
|
|
return false
|
|
}
|
|
|
|
log.Tracer(ctx).Debugf(
|
|
"spn/patrol: https/%s connectivity check succeeded: %d/%d (%.0f%%)",
|
|
network,
|
|
succeeded,
|
|
checks,
|
|
successFraction*100,
|
|
)
|
|
return true
|
|
}
|
|
|
|
func checkHTTPSConnection(ctx context.Context, network string) (ok bool) {
|
|
testDomain := getRandomTestDomain()
|
|
code, err := CheckHTTPSConnection(ctx, network, testDomain)
|
|
if err != nil {
|
|
log.Tracer(ctx).Debugf("spn/patrol: https/%s connect check failed: %s: %s", network, testDomain, err)
|
|
return false
|
|
}
|
|
|
|
log.Tracer(ctx).Tracef("spn/patrol: https/%s connect check succeeded: %s [%d]", network, testDomain, code)
|
|
return true
|
|
}
|
|
|
|
// CheckHTTPSConnection checks if a HTTPS connection to the given domain can be established.
|
|
func CheckHTTPSConnection(ctx context.Context, network, domain string) (statusCode int, err error) {
|
|
// Check network parameter.
|
|
switch network {
|
|
case "tcp4":
|
|
case "tcp6":
|
|
default:
|
|
return 0, fmt.Errorf("provided unsupported network: %s", network)
|
|
}
|
|
|
|
// Build URL.
|
|
// Use HTTPS to ensure that we have really communicated with the desired
|
|
// server and not with an intermediate.
|
|
url := fmt.Sprintf("https://%s/", domain)
|
|
|
|
// Prepare all parts of the request.
|
|
// TODO: Evaluate if we want to change the User-Agent.
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
dialer := &net.Dialer{
|
|
Timeout: 15 * time.Second,
|
|
LocalAddr: conf.GetBindAddr(network),
|
|
FallbackDelay: -1, // Disables Fast Fallback from IPv6 to IPv4.
|
|
KeepAlive: -1, // Disable keep-alive.
|
|
}
|
|
dialWithNet := func(ctx context.Context, _, addr string) (net.Conn, error) {
|
|
// Ignore network by http client.
|
|
// Instead, force either tcp4 or tcp6.
|
|
return dialer.DialContext(ctx, network, addr)
|
|
}
|
|
client := &http.Client{
|
|
Transport: &http.Transport{
|
|
DialContext: dialWithNet,
|
|
DisableKeepAlives: true,
|
|
DisableCompression: true,
|
|
TLSHandshakeTimeout: 15 * time.Second,
|
|
},
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
Timeout: 30 * time.Second,
|
|
}
|
|
|
|
// Make request to server.
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to send http request: %w", err)
|
|
}
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
|
return resp.StatusCode, fmt.Errorf("unexpected status code: %s", resp.Status)
|
|
}
|
|
|
|
return resp.StatusCode, nil
|
|
}
|