package middleware import ( "net" "strings" "sync" "github.com/gin-gonic/gin" ) var ( trustedMu sync.RWMutex trustedProxies []*net.IPNet trustedIPs []net.IP ) // ConfigureTrustedProxies sets CIDRs/IPs allowed to set X-Forwarded-For / X-Real-IP. // When empty, only the direct remote address is used. func ConfigureTrustedProxies(cidrs []string) { trustedMu.Lock() defer trustedMu.Unlock() trustedProxies = nil trustedIPs = nil for _, cidr := range cidrs { cidr = strings.TrimSpace(cidr) if cidr == "" { continue } if strings.Contains(cidr, "/") { _, network, err := net.ParseCIDR(cidr) if err == nil { trustedProxies = append(trustedProxies, network) } continue } if ip := net.ParseIP(cidr); ip != nil { trustedIPs = append(trustedIPs, ip) } } } func isTrustedProxy(ip net.IP) bool { trustedMu.RLock() defer trustedMu.RUnlock() if len(trustedProxies) == 0 && len(trustedIPs) == 0 { return false } for _, tip := range trustedIPs { if tip.Equal(ip) { return true } } for _, network := range trustedProxies { if network.Contains(ip) { return true } } return false } func directRemoteIP(ctx *gin.Context) string { host, _, err := net.SplitHostPort(ctx.Request.RemoteAddr) if err != nil { return strings.TrimSpace(ctx.Request.RemoteAddr) } return host } // ClientIP returns the client IP. Forwarded headers are honored only when the // direct remote address is a configured trusted proxy. func ClientIP(ctx *gin.Context) string { remote := directRemoteIP(ctx) remoteIP := net.ParseIP(remote) if remoteIP == nil || !isTrustedProxy(remoteIP) { if remoteIP != nil { return remoteIP.String() } return ctx.ClientIP() } if xff := ctx.GetHeader("X-Forwarded-For"); xff != "" { parts := strings.Split(xff, ",") return strings.TrimSpace(parts[0]) } if xri := ctx.GetHeader("X-Real-IP"); xri != "" { return strings.TrimSpace(xri) } return remoteIP.String() }