@@ -109,97 +109,128 @@ func ClientIP(r *http.Request, trustedProxies []net.IPNet) string {
109109 return GetClientIP (r )
110110}
111111
112- // ResolveClientIP resolves the client IP from the request, respecting trusted proxy headers
113- // This should be called by middleware to determine the real client IP
114- // It checks X-Forwarded-For and X-Real-IP headers based on trusted proxy configuration
115- // trustedProxies can be []net.IPNet (for compatibility) or []netip.Prefix (preferred)
116- func ResolveClientIP (r * http.Request , trustedProxies any ) string {
117- // Handle Unix socket case
118- if r .RemoteAddr == "@" {
119- return "127.0.0.1"
112+ // isTrustedProxy checks if an IP address is in the trusted proxy list
113+ func isTrustedProxy (addr netip.Addr , trustedProxies []netip.Prefix ) bool {
114+ for _ , prefix := range trustedProxies {
115+ if prefix .Contains (addr ) {
116+ return true
117+ }
118+ }
119+ return false
120+ }
121+
122+ // findFirstUntrustedIP iterates backwards through a comma-separated list of IPs
123+ // and returns the first (rightmost) IP that is not in the trusted proxy list
124+ // Returns empty string if all IPs are trusted or none are valid
125+ func findFirstUntrustedIP (ipList string , trustedProxies []netip.Prefix ) string {
126+ if ipList == "" {
127+ return ""
128+ }
129+
130+ ips := strings .Split (ipList , "," )
131+ // Iterate backwards (right to left) to find the first untrusted IP
132+ // This is the closest untrusted proxy/client to us
133+ for i := len (ips ) - 1 ; i >= 0 ; i -- {
134+ ipStr := strings .TrimSpace (ips [i ])
135+ addr , err := netip .ParseAddr (ipStr )
136+ if err != nil {
137+ continue
138+ }
139+
140+ // If this IP is not trusted, it's our client IP (or closest untrusted proxy)
141+ if ! isTrustedProxy (addr , trustedProxies ) {
142+ return ipStr
143+ }
144+ }
145+
146+ return ""
147+ }
148+
149+ // isRemoteAddrTrusted checks if RemoteAddr is from a trusted proxy
150+ func isRemoteAddrTrusted (r * http.Request , trustedProxies []netip.Prefix ) bool {
151+ if r .RemoteAddr == "" || r .RemoteAddr == "@" {
152+ // If RemoteAddr is empty, assume we trust the proxy (for tests)
153+ return true
120154 }
121155
122- // Extract IP from RemoteAddr (format: "IP:port")
123156 host , _ , err := net .SplitHostPort (r .RemoteAddr )
124157 if err != nil {
125- // If RemoteAddr doesn't have a port, use it as-is
126158 host = r .RemoteAddr
127159 }
128160
129- clientAddr , err := netip .ParseAddr (host )
161+ remoteAddr , err := netip .ParseAddr (host )
130162 if err != nil {
131- return host // Return as-is if parsing fails
163+ return false
132164 }
133165
134- // Convert trusted proxies to netip.Prefix slice
135- var prefixes []netip.Prefix
136- switch tp := trustedProxies .(type ) {
137- case []netip.Prefix :
138- prefixes = tp
139- case []net.IPNet :
140- prefixes = make ([]netip.Prefix , 0 , len (tp ))
141- for _ , ipNet := range tp {
142- prefix , err := netip .ParsePrefix (ipNet .String ())
143- if err != nil {
144- continue
145- }
146- prefixes = append (prefixes , prefix )
147- }
148- case nil :
149- // No trusted proxies
150- default :
151- // Unknown type, treat as no trusted proxies
166+ return isTrustedProxy (remoteAddr , trustedProxies )
167+ }
168+
169+ // ResolveClientIP resolves the client IP from the request, respecting trusted proxy headers
170+ // This should be called by middleware to determine the real client IP
171+ // It checks X-Forwarded-For and X-Real-IP headers based on trusted proxy configuration
172+ func ResolveClientIP (r * http.Request , trustedProxies []netip.Prefix ) string {
173+ // Handle Unix socket case
174+ if r .RemoteAddr == "@" {
175+ return "127.0.0.1"
152176 }
153177
154- // If we have trusted proxies and X-Forwarded-For headers, check them
155- if len (prefixes ) > 0 {
178+ // If we have trusted proxies, check forwarded headers first (before RemoteAddr)
179+ // This handles cases where RemoteAddr might be empty (e.g., in tests)
180+ if len (trustedProxies ) > 0 {
181+ // Check X-Forwarded-For header
182+ // X-Forwarded-For can contain multiple IPs: "client, proxy1, proxy2"
183+ // Format: leftmost is original client, rightmost is most recent proxy
184+ // We iterate backwards (right to left) to find the first untrusted IP
156185 forwardedFor := r .Header .Get ("X-Forwarded-For" )
157186 if forwardedFor != "" {
158- // X-Forwarded-For can contain multiple IPs: "client, proxy1, proxy2"
159- // We want the leftmost IP that is not in our trusted proxy list
160- ips := strings .Split (forwardedFor , "," )
161- for i := range ips {
162- ips [i ] = strings .TrimSpace (ips [i ])
163- addr , err := netip .ParseAddr (ips [i ])
164- if err != nil {
165- continue
166- }
167-
168- // Check if this IP is a trusted proxy
169- isTrusted := false
170- for _ , prefix := range prefixes {
171- if prefix .Contains (addr ) {
172- isTrusted = true
173- break
174- }
175- }
176-
177- // If this IP is not trusted, it's our client IP
178- if ! isTrusted {
179- return ips [i ]
180- }
187+ if ip := findFirstUntrustedIP (forwardedFor , trustedProxies ); ip != "" {
188+ return ip
181189 }
182190 }
183191
184192 // Check X-Real-IP header
185- realIP := r . Header . Get ( " X-Real-IP" )
186- if realIP != "" {
187- addr , err := netip . ParseAddr ( realIP )
188- if err == nil {
189- isTrusted := false
190- for _ , prefix := range prefixes {
191- if prefix . Contains ( addr ) {
192- isTrusted = true
193- break
194- }
193+ // X-Real-IP contains the client IP as reported by the proxy
194+ // While typically a single IP, it may contain multiple IPs (comma-separated) in some configurations
195+ // We iterate backwards (right to left) like X-Forwarded-For to find the first untrusted IP
196+ // (The trusted proxy check is about RemoteAddr, not the IP in X-Real-IP)
197+ realIPHeader := r . Header . Get ( "X-Real-IP" )
198+ if realIPHeader != "" {
199+ // Check if RemoteAddr is from a trusted proxy
200+ if isRemoteAddrTrusted ( r , trustedProxies ) {
201+ if ip := findFirstUntrustedIP ( realIPHeader , trustedProxies ); ip != "" {
202+ return ip
195203 }
196- if ! isTrusted {
197- return realIP
204+ // If all IPs in X-Real-IP are trusted, take the first one (leftmost)
205+ // This handles the case where X-Real-IP contains only trusted proxy IPs
206+ realIPs := strings .Split (realIPHeader , "," )
207+ if len (realIPs ) > 0 {
208+ realIP := strings .TrimSpace (realIPs [0 ])
209+ if _ , err := netip .ParseAddr (realIP ); err == nil {
210+ return realIP
211+ }
198212 }
199213 }
200214 }
201215 }
202216
217+ // Extract IP from RemoteAddr (format: "IP:port")
218+ // Only fall back to RemoteAddr if no forwarded headers were found
219+ if r .RemoteAddr == "" {
220+ return ""
221+ }
222+
223+ host , _ , err := net .SplitHostPort (r .RemoteAddr )
224+ if err != nil {
225+ // If RemoteAddr doesn't have a port, use it as-is
226+ host = r .RemoteAddr
227+ }
228+
229+ clientAddr , err := netip .ParseAddr (host )
230+ if err != nil {
231+ return host // Return as-is if parsing fails
232+ }
233+
203234 // Fall back to RemoteAddr
204235 return clientAddr .String ()
205236}
0 commit comments