mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-20 23:41:28 +02:00
114 lines
3.6 KiB
Go
114 lines
3.6 KiB
Go
package client
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// EnvRelayTransport pins the relay transport. Valid values: "auto" (default,
|
|
// race QUIC and WebSocket), "quic" (QUIC only), "ws" (WebSocket only),
|
|
// "prefer-quic" / "prefer-ws" (try the preferred transport first, fall back to
|
|
// the other only if it fails to connect; no race). The prefer modes trade a
|
|
// slower connect when the preferred transport is blackholed for deterministic
|
|
// transport selection.
|
|
const EnvRelayTransport = "NB_RELAY_TRANSPORT"
|
|
|
|
const (
|
|
// transportFallbackBase is the initial window a relay server is pinned to
|
|
// WebSocket after a QUIC datagram is rejected as too large.
|
|
transportFallbackBase = 10 * time.Minute
|
|
// transportFallbackMax caps the pinned window when failures repeat.
|
|
transportFallbackMax = 60 * time.Minute
|
|
)
|
|
|
|
// TransportMode selects which relay dialers are used.
|
|
type TransportMode string
|
|
|
|
const (
|
|
TransportModeAuto TransportMode = "auto"
|
|
TransportModeQUIC TransportMode = "quic"
|
|
TransportModeWS TransportMode = "ws"
|
|
TransportModePreferQUIC TransportMode = "prefer-quic"
|
|
TransportModePreferWS TransportMode = "prefer-ws"
|
|
)
|
|
|
|
// transportModeFromEnv reads EnvRelayTransport, defaulting to auto for an empty
|
|
// or unrecognized value.
|
|
func transportModeFromEnv() TransportMode {
|
|
switch TransportMode(strings.ToLower(strings.TrimSpace(os.Getenv(EnvRelayTransport)))) {
|
|
case "", TransportModeAuto:
|
|
return TransportModeAuto
|
|
case TransportModeQUIC:
|
|
return TransportModeQUIC
|
|
case TransportModeWS:
|
|
return TransportModeWS
|
|
case TransportModePreferQUIC:
|
|
return TransportModePreferQUIC
|
|
case TransportModePreferWS:
|
|
return TransportModePreferWS
|
|
default:
|
|
log.Warnf("invalid %s value %q, using %q", EnvRelayTransport, os.Getenv(EnvRelayTransport), TransportModeAuto)
|
|
return TransportModeAuto
|
|
}
|
|
}
|
|
|
|
// sequential reports whether the mode tries dialers in order with fallback
|
|
// instead of racing them concurrently.
|
|
func (m TransportMode) sequential() bool {
|
|
return m == TransportModePreferQUIC || m == TransportModePreferWS
|
|
}
|
|
|
|
// transportFallback tracks relay servers that have failed to carry QUIC
|
|
// datagrams and should temporarily use WebSocket instead. It is shared across
|
|
// the relay manager so the preference survives client recreation (foreign relay
|
|
// clients are evicted and rebuilt on disconnect). Entries are keyed by server
|
|
// URL and expire after a window that grows on repeated failures.
|
|
type transportFallback struct {
|
|
mu sync.Mutex
|
|
entries map[string]*fallbackEntry
|
|
}
|
|
|
|
type fallbackEntry struct {
|
|
until time.Time
|
|
duration time.Duration
|
|
}
|
|
|
|
func newTransportFallback() *transportFallback {
|
|
return &transportFallback{entries: make(map[string]*fallbackEntry)}
|
|
}
|
|
|
|
// preferWS reports whether serverURL is currently within a WebSocket fallback
|
|
// window.
|
|
func (f *transportFallback) preferWS(serverURL string) bool {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
e := f.entries[serverURL]
|
|
return e != nil && time.Now().Before(e.until)
|
|
}
|
|
|
|
// recordFailure pins serverURL to WebSocket for a window: transportFallbackBase
|
|
// on the first failure, doubling up to transportFallbackMax when QUIC fails
|
|
// again after a previous window expired. It returns the active window duration.
|
|
func (f *transportFallback) recordFailure(serverURL string) time.Duration {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
e := f.entries[serverURL]
|
|
switch {
|
|
case e == nil:
|
|
e = &fallbackEntry{duration: transportFallbackBase}
|
|
f.entries[serverURL] = e
|
|
case now.Before(e.until):
|
|
return time.Until(e.until)
|
|
default:
|
|
e.duration = min(e.duration*2, transportFallbackMax)
|
|
}
|
|
e.until = now.Add(e.duration)
|
|
return e.duration
|
|
}
|