package util import ( "context" "fmt" "net" "strings" "git.imall.cloud/openim/chat/pkg/common/mctx" "github.com/gin-gonic/gin" "github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/log" ) type Api struct { ImUserID string ProxyHeader string ChatAdminUserID string } func (o *Api) WithAdminUser(ctx context.Context) context.Context { return mctx.WithAdminUser(ctx, o.ChatAdminUserID) } // isPrivateIP 检查IP是否是内网IP func isPrivateIP(ip net.IP) bool { if ip == nil { return false } // 检查是否是内网IP范围 // 10.0.0.0/8 // 172.16.0.0/12 // 192.168.0.0/16 // 127.0.0.0/8 (localhost) if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() { return true } ip4 := ip.To4() if ip4 == nil { return false } // 10.0.0.0/8 if ip4[0] == 10 { return true } // 172.16.0.0/12 if ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31 { return true } // 192.168.0.0/16 if ip4[0] == 192 && ip4[1] == 168 { return true } return false } // parseForwardedHeader 解析标准的 Forwarded 头(RFC 7239) // 格式:Forwarded: for=192.0.2.60;proto=http;by=203.0.113.43 func parseForwardedHeader(forwarded string) string { // 查找 for= 后面的IP地址 parts := strings.Split(forwarded, ";") for _, part := range parts { part = strings.TrimSpace(part) if strings.HasPrefix(strings.ToLower(part), "for=") { ip := strings.TrimPrefix(part, "for=") ip = strings.TrimPrefix(ip, "For=") ip = strings.TrimPrefix(ip, "FOR=") // 移除可能的引号和端口号 ip = strings.Trim(ip, `"`) if idx := strings.Index(ip, ":"); idx != -1 { ip = ip[:idx] } if parsedIP := net.ParseIP(ip); parsedIP != nil { return ip } } } return "" } // GetClientIP 获取客户端真实IP地址 // 支持多层反向代理和CDN场景 // 优先级: // 1. CDN特定头(CF-Connecting-IP, True-Client-IP等)- 最可靠 // 2. 标准 Forwarded 头(RFC 7239)- 现代标准 // 3. X-Real-IP - 通常由第一层代理设置,比X-Forwarded-For更可靠 // 4. X-Forwarded-For - 取最左边的IP(客户端真实IP),格式:client_ip, proxy1_ip, proxy2_ip, ... // 5. 其他常见头(X-Client-IP, True-Client-IP等) // 6. 自定义ProxyHeader // 7. RemoteAddr - 最后回退 func (o *Api) GetClientIP(c *gin.Context) (string, error) { // 记录所有相关的HTTP头用于调试 headers := map[string]string{ "Forwarded": c.Request.Header.Get("Forwarded"), // RFC 7239 标准头 "X-Forwarded-For": c.Request.Header.Get("X-Forwarded-For"), "X-Real-IP": c.Request.Header.Get("X-Real-IP"), "CF-Connecting-IP": c.Request.Header.Get("CF-Connecting-IP"), // Cloudflare - 真实客户端IP "CF-Ray": c.Request.Header.Get("CF-Ray"), // Cloudflare - 请求ID,用于验证来自CF "True-Client-IP": c.Request.Header.Get("True-Client-IP"), // Cloudflare Enterprise, Akamai "X-Client-IP": c.Request.Header.Get("X-Client-IP"), // 一些代理 "X-Forwarded": c.Request.Header.Get("X-Forwarded"), // 一些代理 "Forwarded-For": c.Request.Header.Get("Forwarded-For"), // 非标准,但有些代理使用 "X-Cluster-Client-IP": c.Request.Header.Get("X-Cluster-Client-IP"), // Kubernetes "RemoteAddr": c.Request.RemoteAddr, } if o.ProxyHeader != "" { headers["Custom-"+o.ProxyHeader] = c.Request.Header.Get(o.ProxyHeader) } // 打印所有能获取到的IP相关信息用于调试 customProxyHeader := "" if o.ProxyHeader != "" { customProxyHeader = headers["Custom-"+o.ProxyHeader] } log.ZInfo(c, "GetClientIP 调试信息 - 所有HTTP头", "Forwarded", headers["Forwarded"], "X-Forwarded-For", headers["X-Forwarded-For"], "X-Real-IP", headers["X-Real-IP"], "CF-Connecting-IP", headers["CF-Connecting-IP"], "CF-Ray", headers["CF-Ray"], "True-Client-IP", headers["True-Client-IP"], "X-Client-IP", headers["X-Client-IP"], "X-Forwarded", headers["X-Forwarded"], "Forwarded-For", headers["Forwarded-For"], "X-Cluster-Client-IP", headers["X-Cluster-Client-IP"], "RemoteAddr", headers["RemoteAddr"], "Custom-ProxyHeader", customProxyHeader) // 1. 优先检查 Cloudflare 的 CF-Connecting-IP(最可靠,Cloudflare 直接设置真实客户端IP) // 检查是否来自 Cloudflare(通过 CF-Ray 头判断) cfRay := c.Request.Header.Get("CF-Ray") cfConnectingIP := c.Request.Header.Get("CF-Connecting-IP") if cfConnectingIP != "" { // 特别记录 Cloudflare 相关信息 log.ZInfo(c, "GetClientIP detected Cloudflare request", "CF-Connecting-IP", cfConnectingIP, "CF-Ray", cfRay) ip := strings.TrimSpace(cfConnectingIP) parsedIP := net.ParseIP(ip) if parsedIP != nil { // Cloudflare 的 CF-Connecting-IP 总是包含真实客户端IP,无论公网还是内网 // 如果同时有 CF-Ray,说明确实来自 Cloudflare,更加可靠 if cfRay != "" { log.ZInfo(c, "GetClientIP 最终选择IP", "ip", ip, "source", "CF-Connecting-IP", "cfRay", cfRay, "isPrivateIP", isPrivateIP(parsedIP)) } else { log.ZInfo(c, "GetClientIP 最终选择IP", "ip", ip, "source", "CF-Connecting-IP", "isPrivateIP", isPrivateIP(parsedIP)) } return ip, nil } } // 2. 检查其他CDN特定的头 cdnHeaders := []string{ "True-Client-IP", // Cloudflare Enterprise, Akamai "X-Client-IP", // 一些CDN和代理 "X-Cluster-Client-IP", // Kubernetes Ingress } for _, headerName := range cdnHeaders { ipStr := c.Request.Header.Get(headerName) if ipStr != "" { ip := strings.TrimSpace(ipStr) parsedIP := net.ParseIP(ip) if parsedIP != nil { // CDN头通常包含真实客户端IP,优先返回公网IP if !isPrivateIP(parsedIP) { log.ZInfo(c, "GetClientIP 最终选择IP", "ip", ip, "source", headerName, "isPrivateIP", false) return ip, nil } // 即使是内网IP,CDN头也相对可靠 log.ZInfo(c, "GetClientIP 最终选择IP", "ip", ip, "source", headerName, "isPrivateIP", true) return ip, nil } } } // 3. 检查标准 Forwarded 头(RFC 7239)- 现代标准,最可靠 forwarded := c.Request.Header.Get("Forwarded") if forwarded != "" { ip := parseForwardedHeader(forwarded) if ip != "" { parsedIP := net.ParseIP(ip) if parsedIP != nil { log.ZInfo(c, "GetClientIP 最终选择IP", "ip", ip, "source", "Forwarded", "isPrivateIP", isPrivateIP(parsedIP)) return ip, nil } } } // 4. 检查 X-Real-IP 头(通常由第一层代理设置,比X-Forwarded-For更可靠) xRealIP := c.Request.Header.Get("X-Real-IP") if xRealIP != "" { ip := strings.TrimSpace(xRealIP) parsedIP := net.ParseIP(ip) if parsedIP != nil { log.ZInfo(c, "GetClientIP 最终选择IP", "ip", ip, "source", "X-Real-IP", "isPrivateIP", isPrivateIP(parsedIP)) return ip, nil } } // 5. 检查 X-Forwarded-For 头(格式:client_ip, proxy1_ip, proxy2_ip, ...) // 重要:在多层代理中,最左边的IP是客户端真实IP,应该优先取第一个IP xForwardedFor := c.Request.Header.Get("X-Forwarded-For") if xForwardedFor != "" { ips := strings.Split(xForwardedFor, ",") // 取最左边的IP(第一个IP),这是客户端真实IP // 格式:client_ip, proxy1_ip, proxy2_ip, ... if len(ips) > 0 { ip := strings.TrimSpace(ips[0]) parsedIP := net.ParseIP(ip) if parsedIP != nil { log.ZInfo(c, "GetClientIP 最终选择IP", "ip", ip, "source", "X-Forwarded-For", "totalIPs", len(ips), "isPrivateIP", isPrivateIP(parsedIP), "note", "取最左边的IP(客户端真实IP)") return ip, nil } } } // 6. 检查其他常见头 otherHeaders := []string{"X-Client-IP", "X-Forwarded", "Forwarded-For"} for _, headerName := range otherHeaders { ipStr := c.Request.Header.Get(headerName) if ipStr != "" { ip := strings.TrimSpace(ipStr) parsedIP := net.ParseIP(ip) if parsedIP != nil { log.ZInfo(c, "GetClientIP 最终选择IP", "ip", ip, "source", headerName, "isPrivateIP", isPrivateIP(parsedIP)) return ip, nil } } } // 7. 如果配置了自定义的 ProxyHeader,检查它 if o.ProxyHeader != "" { customHeaderIP := c.Request.Header.Get(o.ProxyHeader) if customHeaderIP != "" { ip := strings.TrimSpace(customHeaderIP) parsedIP := net.ParseIP(ip) if parsedIP != nil { log.ZInfo(c, "GetClientIP 最终选择IP", "ip", ip, "source", "Custom-"+o.ProxyHeader, "isPrivateIP", isPrivateIP(parsedIP)) return ip, nil } } } // 8. 最后使用 RemoteAddr(在集群环境中可能是代理服务器的IP) remoteAddr := c.Request.RemoteAddr ip, _, err := net.SplitHostPort(remoteAddr) if err != nil { log.ZError(c, "GetClientIP failed to parse RemoteAddr", err, "RemoteAddr", remoteAddr) return "", errs.ErrInternalServer.WrapMsg(fmt.Sprintf("failed to parse RemoteAddr: %v", err)) } parsedIP := net.ParseIP(ip) log.ZInfo(c, "GetClientIP 最终选择IP", "ip", ip, "source", "RemoteAddr", "isPrivateIP", parsedIP != nil && isPrivateIP(parsedIP)) return ip, nil } func (o *Api) GetDefaultIMAdminUserID() string { return o.ImUserID }