// Package utils 提供通用工具函数:客户端 IP 解析、随机字符串等。 package utils import ( "fmt" "net" "net/http" "strings" "sync" "github.com/gin-gonic/gin" ) // clientIPHeaders 按优先级排列的单值客户端 IP 请求头;Forwarded 与 X-Forwarded-For 因需要链式解析单独处理。 var clientIPHeaders = []string{ "CF-Connecting-IP", "True-Client-IP", "Ali-CDN-Real-IP", "X-Real-IP", "X-Client-IP", "Fastly-Client-IP", } var ( trustedMu sync.RWMutex trustedCIDRs []*net.IPNet ) // SetTrustedProxies 设置可信代理/CDN 回源网段,支持 IP 与 CIDR,启动时调用一次;并发安全。 func SetTrustedProxies(cidrs []string) error { parsed := make([]*net.IPNet, 0, len(cidrs)) for _, raw := range cidrs { value := strings.TrimSpace(raw) if value == "" { continue } if ip := net.ParseIP(value); ip != nil { bits := 128 if ip.To4() != nil { bits = 32 } _, cidr, err := net.ParseCIDR(fmt.Sprintf("%s/%d", ip.String(), bits)) if err != nil { return fmt.Errorf("invalid trusted proxy %q: %w", raw, err) } parsed = append(parsed, cidr) continue } if _, cidr, err := net.ParseCIDR(value); err == nil { parsed = append(parsed, cidr) continue } return fmt.Errorf("invalid trusted proxy %q", raw) } trustedMu.Lock() trustedCIDRs = parsed trustedMu.Unlock() return nil } // ClientIP 依次枚举 CDN/代理请求头,仅在直连地址可信时采信;否则返回直连 IP。 func ClientIP(c *gin.Context) string { direct := RemoteIP(c) if !isTrustedPeer(c, direct) { return direct } for _, header := range clientIPHeaders { if ip := normalizeIP(c.GetHeader(header)); ip != "" { return ip } } if ip := pickClientIP(parseForwarded(c.GetHeader("Forwarded"))); ip != "" { return ip } if ip := pickClientIP(parseIPList(c.GetHeader("X-Forwarded-For"))); ip != "" { return ip } return direct } // RemoteIP 返回直连地址(已去端口),不读取任何请求头。 func RemoteIP(c *gin.Context) string { return normalizeIP(c.Request.RemoteAddr) } // pickClientIP 从右往左取第一个非可信代理 IP;全部可信时取最左值。 func pickClientIP(chain []string) string { for i := len(chain) - 1; i >= 0; i-- { if !isTrustedStringIP(chain[i]) { return chain[i] } } if len(chain) > 0 { return chain[0] } return "" } func isTrustedPeer(c *gin.Context, direct string) bool { if isUnixSocket(c) { return true } ip := net.ParseIP(direct) return ip != nil && isTrustedIP(ip) } func isTrustedIP(ip net.IP) bool { trustedMu.RLock() defer trustedMu.RUnlock() for _, cidr := range trustedCIDRs { if cidr.Contains(ip) { return true } } return false } func isTrustedStringIP(value string) bool { ip := net.ParseIP(value) return ip != nil && isTrustedIP(ip) } func isUnixSocket(c *gin.Context) bool { addr, ok := c.Request.Context().Value(http.LocalAddrContextKey).(net.Addr) return ok && strings.HasPrefix(addr.Network(), "unix") } // normalizeIP 清理引号与端口后校验为合法 IP,非法返回空串。 func normalizeIP(raw string) string { value := strings.TrimSpace(raw) if value == "" { return "" } value = strings.Trim(value, `"`) if host, _, err := net.SplitHostPort(value); err == nil { value = host } value = strings.Trim(value, "[]") ip := net.ParseIP(value) if ip == nil { return "" } return ip.String() } func parseIPList(value string) []string { if strings.TrimSpace(value) == "" { return nil } parts := strings.Split(value, ",") ips := make([]string, 0, len(parts)) for _, part := range parts { if ip := normalizeIP(part); ip != "" { ips = append(ips, ip) } } return ips } // parseForwarded 解析 RFC 7239 Forwarded 头中的 for= 参数,按出现顺序返回合法 IP。 func parseForwarded(value string) []string { if strings.TrimSpace(value) == "" { return nil } var ips []string for _, element := range strings.Split(value, ",") { for _, param := range strings.Split(element, ";") { key, val, ok := strings.Cut(strings.TrimSpace(param), "=") if !ok || !strings.EqualFold(strings.TrimSpace(key), "for") { continue } candidate := strings.TrimSpace(val) if candidate == "" || candidate == "_hidden" || strings.EqualFold(candidate, "unknown") { continue } if ip := normalizeIP(candidate); ip != "" { ips = append(ips, ip) } } } return ips }