package clientip import ( "net" "net/http" "net/netip" "strings" ) type Resolver struct { TrustProxyHeaders bool TrustedProxies []netip.Prefix } func (r Resolver) FromRequest(req *http.Request) string { if req == nil { return "" } if r.TrustsProxyHeaders(req) { if addr, ok := firstForwarded(headerValues(req.Header, "Forwarded")); ok { return addr.String() } if addr, ok := firstForwardedFor(headerValues(req.Header, "X-Forwarded-For")); ok { return addr.String() } if addr, ok := firstAddress(headerValues(req.Header, "X-Real-IP")); ok { return addr.String() } } if addr, ok := parseAddress(req.RemoteAddr); ok { return addr.String() } return "" } func (r Resolver) TrustsProxyHeaders(req *http.Request) bool { if !r.TrustProxyHeaders || req == nil { return false } if len(r.TrustedProxies) == 0 { return true } remoteAddr, ok := parseAddress(req.RemoteAddr) if !ok { return false } for _, trustedProxy := range r.TrustedProxies { if trustedProxy.Contains(remoteAddr) { return true } } return false } func headerValues(header http.Header, name string) []string { values := header.Values(name) if len(values) > 0 { return values } for key, values := range header { if strings.EqualFold(key, name) { return values } } return nil } func firstForwarded(values []string) (netip.Addr, bool) { for _, value := range values { for _, element := range strings.Split(value, ",") { for _, pair := range strings.Split(element, ";") { key, rawValue, ok := strings.Cut(pair, "=") if !ok || !strings.EqualFold(strings.TrimSpace(key), "for") { continue } if addr, ok := parseAddress(rawValue); ok { return addr, true } } } } return netip.Addr{}, false } func firstForwardedFor(values []string) (netip.Addr, bool) { for _, value := range values { for _, part := range strings.Split(value, ",") { if addr, ok := parseAddress(part); ok { return addr, true } } } return netip.Addr{}, false } func firstAddress(values []string) (netip.Addr, bool) { for _, value := range values { if addr, ok := parseAddress(value); ok { return addr, true } } return netip.Addr{}, false } func parseAddress(value string) (netip.Addr, bool) { value = strings.TrimSpace(value) value = strings.Trim(value, `"`) if addr, ok := parseBareAddress(value); ok { return addr, true } host, _, err := net.SplitHostPort(value) if err != nil { return netip.Addr{}, false } return parseBareAddress(host) } func parseBareAddress(value string) (netip.Addr, bool) { if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") { value = strings.TrimPrefix(strings.TrimSuffix(value, "]"), "[") } addr, err := netip.ParseAddr(value) if err != nil { return netip.Addr{}, false } return addr.Unmap(), true }