mirror of
https://github.com/safing/portmaster
synced 2025-09-01 10:09:11 +00:00
308 lines
6.8 KiB
Go
308 lines
6.8 KiB
Go
package hub
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"golang.org/x/exp/slices"
|
|
|
|
"github.com/safing/jess"
|
|
)
|
|
|
|
// VersionOffline is a special version used to signify that the Hub has gone offline.
|
|
// This is depracated, please use FlagOffline instead.
|
|
const VersionOffline = "offline"
|
|
|
|
// Status Flags.
|
|
const (
|
|
// FlagNetError signifies that the Hub reports a network connectivity failure or impairment.
|
|
FlagNetError = "net-error"
|
|
|
|
// FlagOffline signifies that the Hub has gone offline by itself.
|
|
FlagOffline = "offline"
|
|
|
|
// FlagAllowUnencrypted signifies that the Hub is available to handle unencrypted connections.
|
|
FlagAllowUnencrypted = "allow-unencrypted"
|
|
)
|
|
|
|
// Status is the message type used to update changing Hub Information. Changes are made automatically.
|
|
type Status struct {
|
|
Timestamp int64 `cbor:"t"`
|
|
|
|
// Version holds the current software version of the Hub.
|
|
Version string `cbor:"v"`
|
|
|
|
// Routing Information
|
|
Keys map[string]*Key `cbor:"k,omitempty" json:",omitempty"` // public keys (with type)
|
|
Lanes []*Lane `cbor:"c,omitempty" json:",omitempty"` // Connections to other Hubs.
|
|
|
|
// Status Information
|
|
// Load describes max(CPU, Memory) in percent, averaged over at least 15
|
|
// minutes. Load is published in fixed steps only.
|
|
Load int `cbor:"l,omitempty" json:",omitempty"`
|
|
|
|
// Flags holds flags that signify special states.
|
|
Flags []string `cbor:"f,omitempty" json:",omitempty"`
|
|
}
|
|
|
|
// Key represents a semi-ephemeral public key used for 0-RTT connection establishment.
|
|
type Key struct {
|
|
Scheme string
|
|
Key []byte
|
|
Expires int64
|
|
}
|
|
|
|
// Lane represents a connection to another Hub.
|
|
type Lane struct {
|
|
// ID is the Hub ID of the peer.
|
|
ID string
|
|
|
|
// Capacity designates the available bandwidth between these Hubs.
|
|
// It is specified in bit/s.
|
|
Capacity int
|
|
|
|
// Lateny designates the latency between these Hubs.
|
|
// It is specified in nanoseconds.
|
|
Latency time.Duration
|
|
}
|
|
|
|
// Copy returns a deep copy of the Status.
|
|
func (s *Status) Copy() *Status {
|
|
newStatus := &Status{
|
|
Timestamp: s.Timestamp,
|
|
Version: s.Version,
|
|
Lanes: slices.Clone(s.Lanes),
|
|
Load: s.Load,
|
|
Flags: slices.Clone(s.Flags),
|
|
}
|
|
// Copy map.
|
|
newStatus.Keys = make(map[string]*Key, len(s.Keys))
|
|
for k, v := range s.Keys {
|
|
newStatus.Keys[k] = v
|
|
}
|
|
return newStatus
|
|
}
|
|
|
|
// SelectSignet selects the public key to use for initiating connections to that Hub.
|
|
func (h *Hub) SelectSignet() *jess.Signet {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
|
|
// Return no Signet if we don't have a Status.
|
|
if h.Status == nil {
|
|
return nil
|
|
}
|
|
|
|
// TODO: select key based on preferred alg?
|
|
now := time.Now().Unix()
|
|
for id, key := range h.Status.Keys {
|
|
if now < key.Expires {
|
|
return &jess.Signet{
|
|
ID: id,
|
|
Scheme: key.Scheme,
|
|
Key: key.Key,
|
|
Public: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetSignet returns the public key identified by the given ID from the Hub Status.
|
|
func (h *Hub) GetSignet(id string, recipient bool) (*jess.Signet, error) {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
|
|
// check if public key is being requested
|
|
if !recipient {
|
|
return nil, jess.ErrSignetNotFound
|
|
}
|
|
// check if ID exists
|
|
key, ok := h.Status.Keys[id]
|
|
if !ok {
|
|
return nil, jess.ErrSignetNotFound
|
|
}
|
|
// transform and return
|
|
return &jess.Signet{
|
|
ID: id,
|
|
Scheme: key.Scheme,
|
|
Key: key.Key,
|
|
Public: true,
|
|
}, nil
|
|
}
|
|
|
|
// AddLane adds a new Lane to the Hub Status.
|
|
func (h *Hub) AddLane(newLane *Lane) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
|
|
// validity check
|
|
if h.Status == nil {
|
|
return ErrMissingInfo
|
|
}
|
|
|
|
// check if duplicate
|
|
for _, lane := range h.Status.Lanes {
|
|
if newLane.ID == lane.ID {
|
|
return errors.New("lane already exists")
|
|
}
|
|
}
|
|
|
|
// add
|
|
h.Status.Lanes = append(h.Status.Lanes, newLane)
|
|
return nil
|
|
}
|
|
|
|
// RemoveLane removes a Lane from the Hub Status.
|
|
func (h *Hub) RemoveLane(hubID string) error {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
|
|
// validity check
|
|
if h.Status == nil {
|
|
return ErrMissingInfo
|
|
}
|
|
|
|
for key, lane := range h.Status.Lanes {
|
|
if lane.ID == hubID {
|
|
h.Status.Lanes = append(h.Status.Lanes[:key], h.Status.Lanes[key+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetLaneTo returns the lane to the given Hub, if it exists.
|
|
func (h *Hub) GetLaneTo(hubID string) *Lane {
|
|
h.Lock()
|
|
defer h.Unlock()
|
|
|
|
// validity check
|
|
if h.Status == nil {
|
|
return nil
|
|
}
|
|
|
|
for _, lane := range h.Status.Lanes {
|
|
if lane.ID == hubID {
|
|
return lane
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Equal returns whether the Lane is equal to the given one.
|
|
func (l *Lane) Equal(other *Lane) bool {
|
|
switch {
|
|
case l == nil || other == nil:
|
|
return false
|
|
case l.ID != other.ID:
|
|
return false
|
|
case l.Capacity != other.Capacity:
|
|
return false
|
|
case l.Latency != other.Latency:
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// validateFormatting check if all values conform to the basic format.
|
|
func (s *Status) validateFormatting() error {
|
|
// public keys
|
|
if len(s.Keys) > 255 {
|
|
return fmt.Errorf("field Keys with array/slice length of %d exceeds max length of %d", len(s.Keys), 255)
|
|
}
|
|
for keyID, key := range s.Keys {
|
|
if err := checkStringFormat("Keys#ID", keyID, 255); err != nil {
|
|
return err
|
|
}
|
|
if err := checkStringFormat("Keys.Scheme", key.Scheme, 255); err != nil {
|
|
return err
|
|
}
|
|
if err := checkByteSliceFormat("Keys.Key", key.Key, 1024); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// connections
|
|
if len(s.Lanes) > 255 {
|
|
return fmt.Errorf("field Lanes with array/slice length of %d exceeds max length of %d", len(s.Lanes), 255)
|
|
}
|
|
for _, lanes := range s.Lanes {
|
|
if err := checkStringFormat("Lanes.ID", lanes.ID, 255); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Flags
|
|
if err := checkStringSliceFormat("Flags", s.Flags, 255, 255); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *Lane) String() string {
|
|
return fmt.Sprintf("<%s cap=%d lat=%d>", l.ID, l.Capacity, l.Latency)
|
|
}
|
|
|
|
// LanesEqual returns whether the given []*Lane are equal.
|
|
func LanesEqual(a, b []*Lane) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
|
|
for i, l := range a {
|
|
if !l.Equal(b[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
type lanes []*Lane
|
|
|
|
func (l lanes) Len() int { return len(l) }
|
|
func (l lanes) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
|
func (l lanes) Less(i, j int) bool { return l[i].ID < l[j].ID }
|
|
|
|
// SortLanes sorts a slice of Lanes.
|
|
func SortLanes(l []*Lane) {
|
|
sort.Sort(lanes(l))
|
|
}
|
|
|
|
// HasFlag returns whether the Status has the given flag set.
|
|
func (s *Status) HasFlag(flagName string) bool {
|
|
return slices.Contains[[]string, string](s.Flags, flagName)
|
|
}
|
|
|
|
// FlagsEqual returns whether the given status flags are equal.
|
|
func FlagsEqual(a, b []string) bool {
|
|
// Cannot be equal if lengths are different.
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
|
|
// If both are empty, they are equal.
|
|
if len(a) == 0 {
|
|
return true
|
|
}
|
|
|
|
// Make sure flags are sorted before comparing values.
|
|
sort.Strings(a)
|
|
sort.Strings(b)
|
|
|
|
// Compare values.
|
|
for i, v := range a {
|
|
if v != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|