mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-06-17 02:54:13 +00:00
- Add `turn_stealth_domain` to gateway config for stealth TURN support - Introduce `turn_discovery` in `sni-router` to auto-discover per-namespace routes - Add database migration to enable stealth TURN per namespace - Document ephemeral state API in `SERVERLESS.md`
130 lines
4.1 KiB
Go
130 lines
4.1 KiB
Go
package sniproxy
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// discoveryWarnInterval rate-limits the "discovery scan failed" warning so a
|
|
// persistently-unreadable namespaces directory cannot flood the journal.
|
|
const discoveryWarnInterval = 5 * time.Minute
|
|
|
|
// StaticRoutes returns the operator-set routes parsed from the SNI router's own
|
|
// config file plus the fallback backend. The discoverer merges these with the
|
|
// auto-discovered TURN routes; static routes win on an SNI conflict.
|
|
type StaticRoutes func() (routes []Route, fallback Backend, err error)
|
|
|
|
// TURNRouteDiscoverer periodically rescans the namespaces directory for
|
|
// per-namespace TURNS listeners, merges the discovered routes with the static
|
|
// routes from the config file (static wins on conflict), and atomically
|
|
// installs the result on the Router.
|
|
//
|
|
// A transient failure (unreadable namespaces dir, or a bad static-config read)
|
|
// logs a rate-limited warning and KEEPS the previously-installed routes — a
|
|
// filesystem hiccup must never blackhole live :443 traffic.
|
|
type TURNRouteDiscoverer struct {
|
|
cfg TURNDiscoveryConfig
|
|
static StaticRoutes
|
|
router *Router
|
|
logger *zap.Logger
|
|
|
|
// lastWarn is only touched by the Run goroutine after the synchronous
|
|
// startup Apply, so it needs no lock.
|
|
lastWarn time.Time
|
|
}
|
|
|
|
// NewTURNRouteDiscoverer constructs a discoverer. static reads the operator's
|
|
// config-file routes + fallback; router receives the merged Replace calls.
|
|
func NewTURNRouteDiscoverer(cfg TURNDiscoveryConfig, static StaticRoutes, router *Router, logger *zap.Logger) *TURNRouteDiscoverer {
|
|
if logger == nil {
|
|
logger = zap.NewNop()
|
|
}
|
|
return &TURNRouteDiscoverer{cfg: cfg, static: static, router: router, logger: logger}
|
|
}
|
|
|
|
// Apply performs one scan+merge and installs the result atomically. On any
|
|
// transient error it returns the error and leaves the Router untouched so the
|
|
// caller can decide whether to fail startup (Apply) or keep stale routes (Run).
|
|
func (d *TURNRouteDiscoverer) Apply() error {
|
|
staticRoutes, fallback, err := d.static()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
discovered, err := DiscoverTURNRoutes(d.cfg, d.logger)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
merged := mergeRoutes(staticRoutes, discovered)
|
|
d.router.Replace(merged, fallback)
|
|
return nil
|
|
}
|
|
|
|
// Run scans immediately, then every rescan interval until stop is closed. A
|
|
// failed scan keeps the current routes and logs a rate-limited warning.
|
|
func (d *TURNRouteDiscoverer) Run(stop <-chan struct{}) {
|
|
if err := d.Apply(); err != nil {
|
|
d.warn("initial TURN route discovery failed; serving config-file routes only", err)
|
|
}
|
|
|
|
interval := d.cfg.RescanInterval
|
|
if interval <= 0 {
|
|
interval = DefaultDiscoveryRescanInterval
|
|
}
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
case <-ticker.C:
|
|
if err := d.Apply(); err != nil {
|
|
d.warn("TURN route discovery failed; keeping current routes", err)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// warn logs at most once per discoveryWarnInterval to avoid journal flooding
|
|
// when the namespaces directory is persistently unreadable.
|
|
func (d *TURNRouteDiscoverer) warn(msg string, err error) {
|
|
now := time.Now()
|
|
if now.Sub(d.lastWarn) < discoveryWarnInterval {
|
|
return
|
|
}
|
|
d.lastWarn = now
|
|
d.logger.Warn(msg,
|
|
zap.String("namespaces_dir", d.cfg.NamespacesDir),
|
|
zap.Error(err))
|
|
}
|
|
|
|
// mergeRoutes combines static and discovered routes with static taking
|
|
// precedence on an SNI-match conflict. Static routes keep their original order
|
|
// and precede discovered ones, matching Router.Pick's first-match semantics.
|
|
func mergeRoutes(static, discovered []Route) []Route {
|
|
seen := make(map[string]struct{}, len(static))
|
|
merged := make([]Route, 0, len(static)+len(discovered))
|
|
for _, r := range static {
|
|
seen[matchKey(r.Match)] = struct{}{}
|
|
merged = append(merged, r)
|
|
}
|
|
for _, r := range discovered {
|
|
if _, conflict := seen[matchKey(r.Match)]; conflict {
|
|
continue // static wins
|
|
}
|
|
merged = append(merged, r)
|
|
}
|
|
return merged
|
|
}
|
|
|
|
// matchKey normalizes an SNI match for conflict comparison (matching is
|
|
// case-insensitive, mirroring Router.Pick / matchSNI).
|
|
func matchKey(match string) string {
|
|
return strings.ToLower(match)
|
|
}
|