Files
OpenFlare/backend/openflare/share/edge/nodeip/nodeip.go
T

118 lines
2.6 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package nodeip detects the preferred public IP address for edge nodes.
package nodeip
import (
"context"
"net"
"sync"
"time"
"Wavelet/openflare/share/geoip"
"Wavelet/openflare/share/geoip/iputil"
)
const (
outboundIPLookupTimeout = 5 * time.Second
publicIPPriorityScore = 2 // matches iputil.Score for public IPv4 addresses
ipCacheTTL = 10 * time.Minute
)
// LookupOutboundIP and LookupLocalIP are the provider functions used to detect the node's outbound/local IP.
// They are package-level variables so they can be overridden in tests.
var (
LookupOutboundIP = geoip.GetOutboundIP
LookupLocalIP = DetectLocal
cacheMu sync.RWMutex
cachedIP string
lastDetected time.Time
)
// Detect returns the best available outbound or local IPv4 address for this node.
func Detect() string {
return DetectWithContext(context.Background())
}
// DetectWithContext returns the best available outbound or local IPv4 address, respecting ctx for cancellation.
func DetectWithContext(ctx context.Context) string {
cacheMu.RLock()
if cachedIP != "" && time.Since(lastDetected) < ipCacheTTL {
ip := cachedIP
cacheMu.RUnlock()
return ip
}
cacheMu.RUnlock()
var ip string
if ip = detectOutbound(ctx); ip == "" {
ip = LookupLocalIP()
}
if ip != "" {
cacheMu.Lock()
cachedIP = ip
lastDetected = time.Now()
cacheMu.Unlock()
}
return ip
}
func detectOutbound(ctx context.Context) string {
ctx, cancel := context.WithTimeout(ctx, outboundIPLookupTimeout)
defer cancel()
ip, err := LookupOutboundIP(ctx)
if err != nil || ip == nil {
return ""
}
return ip.String()
}
// DetectLocal returns the highest-priority non-loopback local IPv4 address found on system interfaces.
func DetectLocal() string {
interfaces, err := net.Interfaces()
if err != nil {
return ""
}
bestIP := ""
bestPriority := -1
for _, iface := range interfaces {
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok || ipNet.IP == nil || ipNet.IP.IsLoopback() {
continue
}
ipv4 := ipNet.IP.To4()
if ipv4 == nil {
continue
}
priority := iputil.Score(ipv4)
if priority > bestPriority {
bestIP = ipv4.String()
bestPriority = priority
}
if bestPriority == publicIPPriorityScore {
return bestIP
}
}
}
return bestIP
}
// ResetCacheForTest clears the cached IP.
func ResetCacheForTest() {
cacheMu.Lock()
cachedIP = ""
lastDetected = time.Time{}
cacheMu.Unlock()
}