From 0e06fb0152efdd60ed0d5da1ddb79f3bc5731b9b Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 16 Jul 2026 11:11:02 -0400 Subject: [PATCH] Rapid test the local endpoints as well --- peers/manager.go | 40 +++++++++++++++++++++++++++++++------ peers/monitor/monitor.go | 43 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/peers/manager.go b/peers/manager.go index 26ec4fb..24af689 100644 --- a/peers/manager.go +++ b/peers/manager.go @@ -196,7 +196,7 @@ func (pm *PeerManager) AddPeer(siteConfig SiteConfig) error { // Perform rapid initial holepunch test (outside of lock to avoid blocking) // This quickly determines if holepunch is viable and triggers relay if not - go pm.performRapidInitialTest(siteConfig.SiteId, siteConfig.Endpoint) + go pm.performRapidInitialTest(siteConfig.SiteId, siteConfig.Endpoint, siteConfig.LocalEndpoints) return nil } @@ -868,15 +868,43 @@ endpoint=%s:%d`, util.FixKey(peer.PublicKey), formattedEndpoint, relayPort) } // performRapidInitialTest performs a rapid holepunch test for a newly added peer. -// If the test fails, it immediately requests relay to minimize connection delay. -// This runs in a goroutine to avoid blocking AddPeer. -func (pm *PeerManager) performRapidInitialTest(siteId int, endpoint string) { +// It races a test of the public endpoint against a test of the local candidate endpoints +// (if any) and waits for both to finish before acting, so we never request relay only to +// have it immediately superseded by a local connection (or vice versa). Local wins if it's +// viable at all; otherwise relay is requested only if the public endpoint isn't viable. +// This runs in a goroutine to avoid blocking AddPeer - the peer already starts out pointed +// at the public endpoint (set synchronously in AddPeer), so this just settles the peer onto +// its steady-state connection within ~1-2 seconds. +func (pm *PeerManager) performRapidInitialTest(siteId int, endpoint string, localEndpoints []string) { if pm.peerMonitor == nil { return } - // Perform rapid test - this takes ~1-2 seconds max - holepunchViable := pm.peerMonitor.RapidTestPeer(siteId, endpoint) + var wg sync.WaitGroup + var localWinner string + var holepunchViable bool + + if len(localEndpoints) > 0 { + wg.Add(1) + go func() { + defer wg.Done() + localWinner = pm.peerMonitor.RapidTestLocalEndpoints(siteId, localEndpoints) + }() + } + + wg.Add(1) + go func() { + defer wg.Done() + holepunchViable = pm.peerMonitor.RapidTestPeer(siteId, endpoint) + }() + + wg.Wait() + + if localWinner != "" { + logger.Info("Rapid test: local connection viable for site %d, switching to %s", siteId, localWinner) + pm.LocalPeer(siteId, localWinner) + return + } if !holepunchViable { // Holepunch failed rapid test, request relay immediately diff --git a/peers/monitor/monitor.go b/peers/monitor/monitor.go index 9072d9c..88d3edf 100644 --- a/peers/monitor/monitor.go +++ b/peers/monitor/monitor.go @@ -370,6 +370,49 @@ func (pm *PeerMonitor) RapidTestPeer(siteID int, endpoint string) bool { return false } +// RapidTestLocalEndpoints performs a rapid connectivity test of local candidate endpoints +// for a newly added peer, so local viability is known within the same ~1-2 second window as +// RapidTestPeer's public-endpoint test (rather than waiting for the next backoff-loop tick, +// which could be tens of seconds away). Candidates are tried in order (best-to-worst) and +// the first reachable one wins. Returns the winning endpoint, or "" if none are reachable. +func (pm *PeerMonitor) RapidTestLocalEndpoints(siteID int, endpoints []string) string { + if pm.holepunchTester == nil || len(endpoints) == 0 { + return "" + } + + pm.mutex.Lock() + timeout := pm.rapidTestTimeout + pm.mutex.Unlock() + + for _, endpoint := range endpoints { + result := pm.holepunchTester.TestEndpoint(endpoint, timeout) + if !result.Success { + continue + } + + logger.Info("Rapid test: local endpoint %s for site %d SUCCEEDED (RTT: %v)", endpoint, siteID, result.RTT) + + pm.mutex.Lock() + // Peer may have been removed while we were testing. + stillTracked := false + if _, tracked := pm.localEndpoints[siteID]; tracked { + stillTracked = true + pm.localActiveEndpoint[siteID] = endpoint + pm.localFailures[siteID] = 0 + } + pm.mutex.Unlock() + + if stillTracked { + pm.sendLocal(siteID, endpoint) + } + + return endpoint + } + + logger.Info("Rapid test: no local endpoint reachable for site %d", siteID) + return "" +} + // UpdatePeerEndpoint updates the monitor endpoint for a peer func (pm *PeerMonitor) UpdatePeerEndpoint(siteID int, monitorPeer string) { pm.mutex.Lock()