Files
ip-echo/internal/clientip/resolver.go
2026-05-13 03:05:24 +03:30

148 lines
2.8 KiB
Go

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
}