Files
-Fonu/internal/publicip/publicip.go
T
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

123 lines
2.7 KiB
Go

package publicip
import (
"context"
"fmt"
"io"
"net"
"net/http"
"strings"
"time"
)
var ipv4Providers = []string{
"https://api.ipify.org",
"https://ipv4.icanhazip.com",
"https://ifconfig.me/ip",
"http://ipv4.icanhazip.com",
}
var ipv6Providers = []string{
"https://api64.ipify.org",
"https://ipv6.icanhazip.com",
}
func Detect(ctx context.Context) (ipv4, ipv6 string, err error) {
ipv4, v4err := detectIPv4(ctx)
ipv6, _ = detectIPv6(ctx)
if v4err != nil {
return "", ipv6, v4err
}
return ipv4, ipv6, nil
}
func detectClient() *http.Client {
return &http.Client{
Timeout: 8 * time.Second,
Transport: &http.Transport{
Proxy: nil,
DialContext: (&net.Dialer{
Timeout: 8 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
},
}
}
func detectIPv4(ctx context.Context) (string, error) {
client := detectClient()
var lastErr error
for _, url := range ipv4Providers {
ip, err := fetchIP(ctx, client, url, false)
if err == nil {
return ip, nil
}
lastErr = err
}
return "", fmt.Errorf("获取公网 IPv4 失败:%w", lastErr)
}
func detectIPv6(ctx context.Context) (string, error) {
client := detectClient()
for _, url := range ipv6Providers {
ip, err := fetchIP(ctx, client, url, true)
if err == nil {
return ip, nil
}
}
return "", fmt.Errorf("no ipv6")
}
func fetchIP(ctx context.Context, client *http.Client, url string, wantV6 bool) (string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return "", err
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return "", fmt.Errorf("http %d", resp.StatusCode)
}
body, err := io.ReadAll(io.LimitReader(resp.Body, 128))
if err != nil {
return "", err
}
ip := strings.TrimSpace(string(body))
parsed := net.ParseIP(ip)
if parsed == nil {
return "", fmt.Errorf("invalid ip response")
}
if wantV6 && parsed.To4() != nil {
return "", fmt.Errorf("no ipv6")
}
if !wantV6 && parsed.To4() == nil {
return "", fmt.Errorf("no ipv4")
}
if !wantV6 && !IsPublicIPv4(ip) {
return "", fmt.Errorf("private ipv4")
}
if wantV6 && !IsPublicIPv6(ip) {
return "", fmt.Errorf("private ipv6")
}
return ip, nil
}
func IsPublicIPv4(ip string) bool {
parsed := net.ParseIP(strings.TrimSpace(ip))
if parsed == nil || parsed.To4() == nil {
return false
}
return !parsed.IsPrivate() && !parsed.IsLoopback() && !parsed.IsLinkLocalUnicast() && !parsed.IsUnspecified()
}
func IsPublicIPv6(ip string) bool {
parsed := net.ParseIP(strings.TrimSpace(ip))
if parsed == nil || parsed.To4() != nil {
return false
}
return !parsed.IsPrivate() && !parsed.IsLoopback() && !parsed.IsLinkLocalUnicast() && !parsed.IsUnspecified()
}