Files
mrlee 278f8267b7 chore: 初始提交 Fonu 1.0.2
- 公网反代:Nginx 状态卡片(版本 / 运行状态 / 配置校验 / 端口监听)
- 部署设置第一、二梯队(HTTPS 开关、安全头、限流、连接数、仅中国大陆 IP)
- 隧道检测兼容 frps v0.68 / v0.71(V2 与旧版端点)
- 恢复被清空的 internal/frp/store.go、internal/api/ddns_handler.go、web/src/views/DdnsView.vue
2026-09-23 09:40:59 +08:00

100 lines
2.4 KiB
Go

package frp
import (
"context"
"encoding/json"
"io"
"net"
"net/http"
"strings"
"time"
)
const locationCacheTTL = 24 * time.Hour
type locationCache struct {
value string
expiresAt time.Time
}
type ipLocationResponse struct {
Success bool `json:"success"`
City string `json:"city"`
Region string `json:"region"`
Country string `json:"country"`
}
func (s *Service) serverLocation(ctx context.Context, host string) string {
ip := resolvePublicIP(ctx, host)
if ip == "" {
return strings.TrimSpace(host)
}
s.locationMu.Lock()
if cached, ok := s.locations[ip]; ok && time.Now().Before(cached.expiresAt) {
s.locationMu.Unlock()
return cached.value
}
s.locationMu.Unlock()
value := lookupIPLocation(ctx, ip)
// 定位失败只是展示信息缺失:缓存 IP 本身,不产生错误状态,下一次诊断会再次尝试
s.locationMu.Lock()
s.locations[ip] = locationCache{value: value, expiresAt: time.Now().Add(locationCacheTTL)}
s.locationMu.Unlock()
return value
}
func resolvePublicIP(ctx context.Context, host string) string {
if ip := net.ParseIP(host); isPublicIP(ip) {
return ip.String()
}
addresses, err := net.DefaultResolver.LookupIPAddr(ctx, host)
if err != nil {
return ""
}
for _, address := range addresses {
if isPublicIP(address.IP) {
return address.IP.String()
}
}
return ""
}
func isPublicIP(ip net.IP) bool {
return ip != nil && ip.IsGlobalUnicast() && !ip.IsPrivate() && !ip.IsLoopback() && !ip.IsLinkLocalUnicast()
}
func lookupIPLocation(ctx context.Context, ip string) string {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://ipwho.is/"+ip, nil)
if err != nil {
return "未定位"
}
client := &http.Client{Timeout: 4 * time.Second}
response, err := client.Do(request)
if err != nil {
return "未定位"
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return "未定位"
}
var data ipLocationResponse
if err := json.NewDecoder(io.LimitReader(response.Body, 128<<10)).Decode(&data); err != nil || !data.Success {
return "未定位"
}
return formatLocation(data)
}
func formatLocation(data ipLocationResponse) string {
parts := []string{data.City, data.Region, data.Country}
values := make([]string, 0, len(parts))
for _, part := range parts {
if part != "" && (len(values) == 0 || values[len(values)-1] != part) {
values = append(values, part)
}
}
if len(values) == 0 {
return "未定位"
}
return strings.Join(values, " · ")
}