wip: migrate to mono-repo. SPN has already been moved to spn/
This commit is contained in:
306
spn/captain/navigation.go
Normal file
306
spn/captain/navigation.go
Normal file
@@ -0,0 +1,306 @@
|
||||
package captain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
"github.com/safing/portmaster/service/intel"
|
||||
"github.com/safing/portmaster/service/netenv"
|
||||
"github.com/safing/portmaster/service/profile/endpoints"
|
||||
"github.com/safing/portmaster/spn/access"
|
||||
"github.com/safing/portmaster/spn/docks"
|
||||
"github.com/safing/portmaster/spn/hub"
|
||||
"github.com/safing/portmaster/spn/navigator"
|
||||
"github.com/safing/portmaster/spn/terminal"
|
||||
)
|
||||
|
||||
const stopCraneAfterBeingUnsuggestedFor = 6 * time.Hour
|
||||
|
||||
var (
|
||||
// ErrAllHomeHubsExcluded is returned when all available home hubs were excluded.
|
||||
ErrAllHomeHubsExcluded = errors.New("all home hubs are excluded")
|
||||
|
||||
// ErrReInitSPNSuggested is returned when no home hub can be found, even without rules.
|
||||
ErrReInitSPNSuggested = errors.New("SPN re-init suggested")
|
||||
)
|
||||
|
||||
func establishHomeHub(ctx context.Context) error {
|
||||
// Get own IP.
|
||||
locations, ok := netenv.GetInternetLocation()
|
||||
if !ok || len(locations.All) == 0 {
|
||||
return errors.New("failed to locate own device")
|
||||
}
|
||||
log.Debugf(
|
||||
"spn/captain: looking for new home hub near %s and %s",
|
||||
locations.BestV4(),
|
||||
locations.BestV6(),
|
||||
)
|
||||
|
||||
// Get own entity.
|
||||
// Checking the entity against the entry policies is somewhat hit and miss
|
||||
// anyway, as the device location is an approximation.
|
||||
var myEntity *intel.Entity
|
||||
if dl := locations.BestV4(); dl != nil && dl.IP != nil {
|
||||
myEntity = (&intel.Entity{IP: dl.IP}).Init(0)
|
||||
myEntity.FetchData(ctx)
|
||||
} else if dl := locations.BestV6(); dl != nil && dl.IP != nil {
|
||||
myEntity = (&intel.Entity{IP: dl.IP}).Init(0)
|
||||
myEntity.FetchData(ctx)
|
||||
}
|
||||
|
||||
// Get home hub policy for selecting the home hub.
|
||||
homePolicy, err := getHomeHubPolicy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build navigation options for searching for a home hub.
|
||||
opts := &navigator.Options{
|
||||
Home: &navigator.HomeHubOptions{
|
||||
HubPolicies: []endpoints.Endpoints{homePolicy},
|
||||
CheckHubPolicyWith: myEntity,
|
||||
},
|
||||
}
|
||||
|
||||
// Add requirement to only use Safing nodes when not using community nodes.
|
||||
if !cfgOptionUseCommunityNodes() {
|
||||
opts.Home.RequireVerifiedOwners = NonCommunityVerifiedOwners
|
||||
}
|
||||
|
||||
// Require a trusted home node when the routing profile requires less than two hops.
|
||||
routingProfile := navigator.GetRoutingProfile(cfgOptionRoutingAlgorithm())
|
||||
if routingProfile.MinHops < 2 {
|
||||
opts.Home.Regard = opts.Home.Regard.Add(navigator.StateTrusted)
|
||||
}
|
||||
|
||||
// Find nearby hubs.
|
||||
findCandidates:
|
||||
candidates, err := navigator.Main.FindNearestHubs(
|
||||
locations.BestV4().LocationOrNil(),
|
||||
locations.BestV6().LocationOrNil(),
|
||||
opts, navigator.HomeHub,
|
||||
)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, navigator.ErrEmptyMap):
|
||||
// bootstrap to the network!
|
||||
err := bootstrapWithUpdates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
goto findCandidates
|
||||
|
||||
case errors.Is(err, navigator.ErrAllPinsDisregarded):
|
||||
if len(homePolicy) > 0 {
|
||||
return ErrAllHomeHubsExcluded
|
||||
}
|
||||
return ErrReInitSPNSuggested
|
||||
|
||||
default:
|
||||
return fmt.Errorf("failed to find nearby hubs: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Try connecting to a hub.
|
||||
var tries int
|
||||
var candidate *hub.Hub
|
||||
for tries, candidate = range candidates {
|
||||
err = connectToHomeHub(ctx, candidate)
|
||||
if err != nil {
|
||||
// Check if context is canceled.
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
// Check if the SPN protocol is stopping again.
|
||||
if errors.Is(err, terminal.ErrStopping) {
|
||||
return err
|
||||
}
|
||||
log.Warningf("spn/captain: failed to connect to %s as new home: %s", candidate, err)
|
||||
} else {
|
||||
log.Infof("spn/captain: established connection to %s as new home with %d failed tries", candidate, tries)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to a new home hub - tried %d hubs: %w", tries+1, err)
|
||||
}
|
||||
return fmt.Errorf("no home hub candidates available")
|
||||
}
|
||||
|
||||
func connectToHomeHub(ctx context.Context, dst *hub.Hub) error {
|
||||
// Create new context with timeout.
|
||||
// The maximum timeout is a worst case safeguard.
|
||||
// Keep in mind that multiple IPs and protocols may be tried in all configurations.
|
||||
// Some servers will be (possibly on purpose) hard to reach.
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// Set and clean up exceptions.
|
||||
setExceptions(dst.Info.IPv4, dst.Info.IPv6)
|
||||
defer setExceptions(nil, nil)
|
||||
|
||||
// Connect to hub.
|
||||
crane, err := EstablishCrane(ctx, dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Cleanup connection in case of failure.
|
||||
var success bool
|
||||
defer func() {
|
||||
if !success {
|
||||
crane.Stop(nil)
|
||||
}
|
||||
}()
|
||||
|
||||
// Query all gossip msgs on first connection.
|
||||
gossipQuery, tErr := NewGossipQueryOp(crane.Controller)
|
||||
if tErr != nil {
|
||||
log.Warningf("spn/captain: failed to start initial gossip query: %s", tErr)
|
||||
}
|
||||
// Wait for gossip query to complete.
|
||||
select {
|
||||
case <-gossipQuery.ctx.Done():
|
||||
case <-ctx.Done():
|
||||
return context.Canceled
|
||||
}
|
||||
|
||||
// Create communication terminal.
|
||||
homeTerminal, initData, tErr := docks.NewLocalCraneTerminal(crane, nil, terminal.DefaultHomeHubTerminalOpts())
|
||||
if tErr != nil {
|
||||
return tErr.Wrap("failed to create home terminal")
|
||||
}
|
||||
tErr = crane.EstablishNewTerminal(homeTerminal, initData)
|
||||
if tErr != nil {
|
||||
return tErr.Wrap("failed to connect home terminal")
|
||||
}
|
||||
|
||||
if !DisableAccount {
|
||||
// Authenticate to home hub.
|
||||
authOp, tErr := access.AuthorizeToTerminal(homeTerminal)
|
||||
if tErr != nil {
|
||||
return tErr.Wrap("failed to authorize")
|
||||
}
|
||||
select {
|
||||
case tErr := <-authOp.Result:
|
||||
if !tErr.Is(terminal.ErrExplicitAck) {
|
||||
return tErr.Wrap("failed to authenticate to")
|
||||
}
|
||||
case <-time.After(3 * time.Second):
|
||||
return terminal.ErrTimeout.With("waiting for auth to complete")
|
||||
case <-ctx.Done():
|
||||
return terminal.ErrStopping
|
||||
}
|
||||
}
|
||||
|
||||
// Set new home on map.
|
||||
ok := navigator.Main.SetHome(dst.ID, homeTerminal)
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to set home hub on map")
|
||||
}
|
||||
|
||||
// Assign crane to home hub in order to query it later.
|
||||
docks.AssignCrane(crane.ConnectedHub.ID, crane)
|
||||
|
||||
success = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func optimizeNetwork(ctx context.Context, task *modules.Task) error {
|
||||
if publicIdentity == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
optimize:
|
||||
result, err := navigator.Main.Optimize(nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, navigator.ErrEmptyMap) {
|
||||
// bootstrap to the network!
|
||||
err := bootstrapWithUpdates()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
goto optimize
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Create any new connections.
|
||||
var createdConnections int
|
||||
var attemptedConnections int
|
||||
for _, connectTo := range result.SuggestedConnections {
|
||||
// Skip duplicates.
|
||||
if connectTo.Duplicate {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if connection already exists.
|
||||
crane := docks.GetAssignedCrane(connectTo.Hub.ID)
|
||||
if crane != nil {
|
||||
// Update last suggested timestamp.
|
||||
crane.NetState.UpdateLastSuggestedAt()
|
||||
// Continue crane if stopping.
|
||||
if crane.AbortStopping() {
|
||||
log.Infof("spn/captain: optimization aborted retiring of %s, removed stopping mark", crane)
|
||||
crane.NotifyUpdate()
|
||||
}
|
||||
|
||||
// Create new connections if we have connects left.
|
||||
} else if createdConnections < result.MaxConnect {
|
||||
attemptedConnections++
|
||||
|
||||
crane, tErr := EstablishPublicLane(ctx, connectTo.Hub)
|
||||
if !tErr.IsOK() {
|
||||
log.Warningf("spn/captain: failed to establish lane to %s: %s", connectTo.Hub, tErr)
|
||||
} else {
|
||||
createdConnections++
|
||||
crane.NetState.UpdateLastSuggestedAt()
|
||||
|
||||
log.Infof("spn/captain: established lane to %s", connectTo.Hub)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log optimization result.
|
||||
if attemptedConnections > 0 {
|
||||
log.Infof(
|
||||
"spn/captain: created %d/%d new connections for %s optimization",
|
||||
createdConnections,
|
||||
attemptedConnections,
|
||||
result.Purpose)
|
||||
} else {
|
||||
log.Infof(
|
||||
"spn/captain: checked %d connections for %s optimization",
|
||||
len(result.SuggestedConnections),
|
||||
result.Purpose,
|
||||
)
|
||||
}
|
||||
|
||||
// Retire cranes if unsuggested for a while.
|
||||
if result.StopOthers {
|
||||
for _, crane := range docks.GetAllAssignedCranes() {
|
||||
switch {
|
||||
case crane.Stopped():
|
||||
// Crane already stopped.
|
||||
case crane.IsStopping():
|
||||
// Crane is stopping, forcibly stop if mine and suggested.
|
||||
if crane.IsMine() && crane.NetState.StopSuggested() {
|
||||
crane.Stop(nil)
|
||||
}
|
||||
case crane.IsMine() && crane.NetState.StoppingSuggested():
|
||||
// Mark as stopping if mine and suggested.
|
||||
crane.MarkStopping()
|
||||
case crane.NetState.RequestStoppingSuggested(stopCraneAfterBeingUnsuggestedFor):
|
||||
// Mark as stopping requested.
|
||||
crane.MarkStoppingRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user