Files
vps-commander/internal/cluster/geoip.go
T
2026-09-30 13:17:19 +08:00

116 lines
2.6 KiB
Go

package cluster
import (
"encoding/json"
"net"
"net/http"
"strings"
"sync"
"time"
)
type GeoLocation struct {
CountryCode string `json:"countryCode"`
Country string `json:"country"`
Status string `json:"status"`
}
var (
geoCache sync.Map
client = &http.Client{Timeout: 4 * time.Second}
)
func isPrivateIP(ipStr string) bool {
ip := net.ParseIP(ipStr)
if ip == nil {
return false
}
if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() {
return true
}
return false
}
// ExtractIPFromRequest retrieves the real client IP from HTTP headers or RemoteAddr
func ExtractIPFromRequest(r *http.Request) string {
if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" {
parts := strings.Split(fwd, ",")
for _, p := range parts {
clean := strings.TrimSpace(p)
if clean != "" && !isPrivateIP(clean) {
return clean
}
}
if len(parts) > 0 {
clean := strings.TrimSpace(parts[0])
if clean != "" {
return clean
}
}
}
if realIP := strings.TrimSpace(r.Header.Get("X-Real-IP")); realIP != "" {
return realIP
}
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err == nil {
return host
}
return r.RemoteAddr
}
// ResolveIP returns countryCode (e.g. "HK", "SG") and countryName (e.g. "Hong Kong", "Singapore")
func ResolveIP(ipStr string) (string, string) {
ipStr = strings.TrimSpace(ipStr)
if ipStr == "" {
return "", ""
}
if isPrivateIP(ipStr) {
return "CN", "中国"
}
if v, ok := geoCache.Load(ipStr); ok {
if g, ok := v.(GeoLocation); ok {
return g.CountryCode, g.Country
}
}
url := "http://ip-api.com/json/" + ipStr + "?fields=status,country,countryCode"
resp, err := client.Get(url)
if err != nil {
return "", ""
}
defer resp.Body.Close()
var g GeoLocation
if err := json.NewDecoder(resp.Body).Decode(&g); err != nil {
return "", ""
}
if g.Status == "success" && g.CountryCode != "" {
geoCache.Store(ipStr, g)
return g.CountryCode, g.Country
}
return "", ""
}
// ResolveLocalPublicIP queries the hub host's public IP location
func ResolveLocalPublicIP() (string, string) {
if v, ok := geoCache.Load("__local__"); ok {
if g, ok := v.(GeoLocation); ok {
return g.CountryCode, g.Country
}
}
resp, err := client.Get("http://ip-api.com/json/?fields=status,country,countryCode")
if err != nil {
return "HK", "中国香港"
}
defer resp.Body.Close()
var g GeoLocation
if err := json.NewDecoder(resp.Body).Decode(&g); err != nil {
return "HK", "中国香港"
}
if g.Status == "success" && g.CountryCode != "" {
geoCache.Store("__local__", g)
return g.CountryCode, g.Country
}
return "HK", "中国香港"
}