safing-portmaster/spn/docks/measurements.go

108 lines
2.8 KiB
Go

package docks
import (
"context"
"fmt"
"time"
"github.com/safing/portmaster/spn/hub"
"github.com/safing/portmaster/spn/ships"
"github.com/safing/portmaster/spn/terminal"
)
// Measurement Configuration.
const (
CraneMeasurementTTLDefault = 30 * time.Minute
CraneMeasurementTTLByCostBase = 1 * time.Minute
CraneMeasurementTTLByCostMin = 30 * time.Minute
CraneMeasurementTTLByCostMax = 3 * time.Hour
// With a base TTL of 1m, this leads to:
// 20c -> 20m -> raised to 30m
// 50c -> 50m
// 100c -> 1h40m
// 1000c -> 16h40m -> capped to 3h.
)
// MeasureHub measures the connection to this Hub and saves the results to the
// Hub.
func MeasureHub(ctx context.Context, h *hub.Hub, checkExpiryWith time.Duration) *terminal.Error {
// Check if we are measuring before building a connection.
if capacityTestRunning.IsSet() {
return terminal.ErrTryAgainLater.With("another capacity op is already running")
}
// Check if we have a connection to this Hub.
crane := GetAssignedCrane(h.ID)
if crane == nil {
// Connect to Hub.
var err error
crane, err = establishCraneForMeasuring(ctx, h)
if err != nil {
return terminal.ErrConnectionError.With("failed to connect to %s: %s", h, err)
}
// Stop crane if established just for measuring.
defer crane.Stop(nil)
}
// Run latency test.
_, expires := h.GetMeasurements().GetLatency()
if checkExpiryWith == 0 || time.Now().Add(-checkExpiryWith).After(expires) {
latOp, tErr := NewLatencyTestOp(crane.Controller)
if !tErr.IsOK() {
return tErr
}
select {
case tErr = <-latOp.Result():
if !tErr.IsOK() {
return tErr
}
case <-ctx.Done():
return terminal.ErrCanceled
case <-time.After(1 * time.Minute):
crane.Controller.StopOperation(latOp, terminal.ErrTimeout)
return terminal.ErrTimeout.With("waiting for latency test")
}
}
// Run capacity test.
_, expires = h.GetMeasurements().GetCapacity()
if checkExpiryWith == 0 || time.Now().Add(-checkExpiryWith).After(expires) {
capOp, tErr := NewCapacityTestOp(crane.Controller, nil)
if !tErr.IsOK() {
return tErr
}
select {
case tErr = <-capOp.Result():
if !tErr.IsOK() {
return tErr
}
case <-ctx.Done():
return terminal.ErrCanceled
case <-time.After(1 * time.Minute):
crane.Controller.StopOperation(capOp, terminal.ErrTimeout)
return terminal.ErrTimeout.With("waiting for capacity test")
}
}
return nil
}
func establishCraneForMeasuring(ctx context.Context, dst *hub.Hub) (*Crane, error) {
ship, err := ships.Launch(ctx, dst, nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to launch ship: %w", err)
}
crane, err := NewCrane(ship, dst, nil)
if err != nil {
return nil, fmt.Errorf("failed to create crane: %w", err)
}
err = crane.Start(ctx)
if err != nil {
return nil, fmt.Errorf("failed to start crane: %w", err)
}
return crane, nil
}