148 lines
2.8 KiB
Go
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
|
|
}
|