From da2115584c71e08840a2257c79f7a87b3eeb9d40 Mon Sep 17 00:00:00 2001 From: jdl Date: Thu, 18 Jun 2026 06:41:55 +0200 Subject: [PATCH] Fixed relay selection - deterministic w/ fallback. --- peer/on_hub.go | 30 +++++++++++++++++++++--------- peer/on_statetick.go | 6 ++++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/peer/on_hub.go b/peer/on_hub.go index 8198755..83e73a8 100644 --- a/peer/on_hub.go +++ b/peer/on_hub.go @@ -67,27 +67,39 @@ func (a *App) onRemovePeer(key wgtypes.Key) { } } -// switchActiveRelay promotes the lowest-latency relay peer to active. +// switchActiveRelay selects the live relay with the lowest VPN IP as active. +// +// Choosing deterministically by IP (rather than by lowest RTT) makes every +// non-public peer converge on the same relay, so two non-public peers always +// share a relay and can reach each other. Failover walks up to the next-lowest +// live relay; failback returns to a lower-IP relay once it recovers. The call +// is idempotent: if the best relay is already active it does nothing, so it's +// safe to run every state tick. func (a *App) switchActiveRelay() { - if a.relay != nil { - // If we have a relay, it's public, so should go back to being a direct - // peer - this will convert it's /24 to a /32. - a.devAddDirect(a.relay, a.relay.PreferredEndpoint()) - a.relay = nil - } - var best *Peer for _, p := range a.peersByKey { if !p.CanRelay() { continue } - if best == nil || p.RTT < best.RTT { + if best == nil || p.VPNIP.Less(best.VPNIP) { best = p } } + + if best == a.relay { + return // Already on the best relay (or none available and none before). + } + + if a.relay != nil { + // The old relay is public, so it goes back to being a direct peer - + // this converts its /24 back to a /32. + a.devAddDirect(a.relay, a.relay.PreferredEndpoint()) + } + if best == nil { log.Printf("no relay available") + a.relay = nil return } diff --git a/peer/on_statetick.go b/peer/on_statetick.go index c996346..0248816 100644 --- a/peer/on_statetick.go +++ b/peer/on_statetick.go @@ -61,8 +61,10 @@ func (a *App) onStateTick() { } } - // Ensure we have a live relay (if we're not public). - if !a.isPublic && (a.relay == nil || !a.relay.Up()) { + // Keep the active relay pinned to the lowest-IP live relay (if we're not + // public). switchActiveRelay is idempotent and handles failover when the + // current relay dies as well as failback when a lower-IP relay recovers. + if !a.isPublic { a.switchActiveRelay() } }