diff --git a/api/api.go b/api/api.go index 4dea66b..b8f4e56 100644 --- a/api/api.go +++ b/api/api.go @@ -51,6 +51,7 @@ type PeerStatus struct { LastSeen time.Time `json:"lastSeen"` Endpoint string `json:"endpoint,omitempty"` IsRelay bool `json:"isRelay"` + IsLocal bool `json:"isLocal"` // true when connected via a local network endpoint, bypassing both the public endpoint and relay PeerIP string `json:"peerAddress,omitempty"` HolepunchConnected bool `json:"holepunchConnected"` } @@ -229,7 +230,7 @@ func (s *API) Stop() error { return nil } -func (s *API) AddPeerStatus(siteID int, siteName string, connected bool, rtt time.Duration, endpoint string, isRelay bool) { +func (s *API) AddPeerStatus(siteID int, siteName string, connected bool, rtt time.Duration, endpoint string, isRelay bool, isLocal bool) { s.statusMu.Lock() defer s.statusMu.Unlock() @@ -247,10 +248,11 @@ func (s *API) AddPeerStatus(siteID int, siteName string, connected bool, rtt tim status.LastSeen = time.Now() status.Endpoint = endpoint status.IsRelay = isRelay + status.IsLocal = isLocal } -// UpdatePeerStatus updates the status of a peer including endpoint and relay info -func (s *API) UpdatePeerStatus(siteID int, connected bool, rtt time.Duration, endpoint string, isRelay bool) { +// UpdatePeerStatus updates the status of a peer including endpoint, relay, and local info +func (s *API) UpdatePeerStatus(siteID int, connected bool, rtt time.Duration, endpoint string, isRelay bool, isLocal bool) { s.statusMu.Lock() defer s.statusMu.Unlock() @@ -267,6 +269,7 @@ func (s *API) UpdatePeerStatus(siteID int, connected bool, rtt time.Duration, en status.LastSeen = time.Now() status.Endpoint = endpoint status.IsRelay = isRelay + status.IsLocal = isLocal } func (s *API) RemovePeerStatus(siteID int) { // remove the peer from the status map @@ -363,6 +366,31 @@ func (s *API) UpdatePeerRelayStatus(siteID int, endpoint string, isRelay bool) { status.Endpoint = endpoint status.IsRelay = isRelay + if isRelay { + // Relay and local are mutually exclusive; local always wins when viable. + status.IsLocal = false + } +} + +// UpdatePeerLocalStatus updates only the local-connection status of a peer. A peer using a +// local connection is never simultaneously relayed. +func (s *API) UpdatePeerLocalStatus(siteID int, endpoint string, isLocal bool) { + s.statusMu.Lock() + defer s.statusMu.Unlock() + + status, exists := s.peerStatuses[siteID] + if !exists { + status = &PeerStatus{ + SiteID: siteID, + } + s.peerStatuses[siteID] = status + } + + status.Endpoint = endpoint + status.IsLocal = isLocal + if isLocal { + status.IsRelay = false + } } // UpdatePeerHolepunchStatus updates the holepunch connection status of a peer diff --git a/olm/connect.go b/olm/connect.go index 8eac7a4..ff2b5f4 100644 --- a/olm/connect.go +++ b/olm/connect.go @@ -192,7 +192,7 @@ func (o *Olm) handleConnect(msg websocket.WSMessage) { siteEndpoint = site.Endpoint } - o.apiServer.AddPeerStatus(site.SiteId, site.Name, false, 0, siteEndpoint, false) + o.apiServer.AddPeerStatus(site.SiteId, site.Name, false, 0, siteEndpoint, false, false) } // we still call this to add the aliases for jit lookup but we just do that then pass inside. need to skip the above so we dont add to the api diff --git a/peers/manager.go b/peers/manager.go index 24af689..b7a9925 100644 --- a/peers/manager.go +++ b/peers/manager.go @@ -192,7 +192,7 @@ func (pm *PeerManager) AddPeer(siteConfig SiteConfig) error { pm.peers[siteConfig.SiteId] = siteConfig - pm.APIServer.AddPeerStatus(siteConfig.SiteId, siteConfig.Name, false, 0, siteConfig.Endpoint, false) + pm.APIServer.AddPeerStatus(siteConfig.SiteId, siteConfig.Name, false, 0, siteConfig.Endpoint, false, false) // Perform rapid initial holepunch test (outside of lock to avoid blocking) // This quickly determines if holepunch is viable and triggers relay if not @@ -1031,6 +1031,10 @@ endpoint=%s`, util.FixKey(peer.PublicKey), localEndpoint) return } + if pm.APIServer != nil { + pm.APIServer.UpdatePeerLocalStatus(siteId, localEndpoint, true) + } + logger.Info("Switched peer %d to local connection at %s", siteId, localEndpoint) } @@ -1060,6 +1064,11 @@ func (pm *PeerManager) UnLocalPeer(siteId int) { if err := pm.UnRelayPeer(siteId, resolved); err != nil { logger.Error("Failed to fall back peer %d from local connection: %v", siteId, err) + return + } + + if pm.APIServer != nil { + pm.APIServer.UpdatePeerLocalStatus(siteId, resolved, false) } } diff --git a/peers/monitor/monitor.go b/peers/monitor/monitor.go index 7c22915..a93c796 100644 --- a/peers/monitor/monitor.go +++ b/peers/monitor/monitor.go @@ -502,9 +502,18 @@ func (pm *PeerMonitor) handleConnectionStatusChange(siteID int, status Connectio pm.wgConnectionRTT[siteID] = status.RTT } isRelayed := pm.relayedPeers[siteID] + localEndpoint := pm.localActiveEndpoint[siteID] endpoint := pm.holepunchEndpoints[siteID] pm.mutex.Unlock() + isLocal := localEndpoint != "" + if isLocal { + // Report the active local endpoint rather than the public one; local and relay + // are mutually exclusive. + endpoint = localEndpoint + isRelayed = false + } + // Log status changes if !exists || previousStatus != status.Connected { if status.Connected { @@ -516,7 +525,7 @@ func (pm *PeerMonitor) handleConnectionStatusChange(siteID int, status Connectio // Update API with connection status if pm.apiServer != nil { - pm.apiServer.UpdatePeerStatus(siteID, status.Connected, status.RTT, endpoint, isRelayed) + pm.apiServer.UpdatePeerStatus(siteID, status.Connected, status.RTT, endpoint, isRelayed, isLocal) } // Notify route optimizer of status change @@ -1002,8 +1011,10 @@ func (pm *PeerMonitor) checkHolepunchEndpoints() bool { wgConnected := pm.wgConnectionStatus[siteID] pm.mutex.Unlock() - // Update API - use holepunch endpoint and relay status - pm.apiServer.UpdatePeerStatus(siteID, wgConnected, result.RTT, endpoint, isRelayed) + // Update API - use holepunch endpoint and relay status. Sites with an active + // local endpoint are filtered out of this loop above, so isLocal is always + // false here. + pm.apiServer.UpdatePeerStatus(siteID, wgConnected, result.RTT, endpoint, isRelayed, false) } // Handle relay logic based on holepunch status