forked from baron/baron-sso
88 lines
2.0 KiB
Go
88 lines
2.0 KiB
Go
package utils
|
|
|
|
import (
|
|
"net"
|
|
"strings"
|
|
)
|
|
|
|
// ResolveClientIP selects the best client IP from proxy headers and the remote address.
|
|
// It prefers a public IP from X-Forwarded-For, then X-Real-IP, and finally the remote IP.
|
|
func ResolveClientIP(forwardedFor, realIP, remoteIP string) string {
|
|
forwardedCandidates := splitClientIPs(forwardedFor)
|
|
if ip := firstPublicIP(forwardedCandidates); ip != "" {
|
|
return ip
|
|
}
|
|
if ip := normalizeIP(realIP); ip != "" && !IsPrivateOrReservedIP(ip) {
|
|
return ip
|
|
}
|
|
if ip := normalizeIP(remoteIP); ip != "" && !IsPrivateOrReservedIP(ip) {
|
|
return ip
|
|
}
|
|
if len(forwardedCandidates) > 0 {
|
|
return forwardedCandidates[0]
|
|
}
|
|
if ip := normalizeIP(realIP); ip != "" {
|
|
return ip
|
|
}
|
|
return normalizeIP(remoteIP)
|
|
}
|
|
|
|
// IsPrivateOrReservedIP reports whether the IP is private or from a non-public network range.
|
|
func IsPrivateOrReservedIP(raw string) bool {
|
|
ip := net.ParseIP(strings.TrimSpace(raw))
|
|
if ip == nil {
|
|
return false
|
|
}
|
|
if ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
|
|
return true
|
|
}
|
|
for _, cidr := range []string{
|
|
"100.64.0.0/10",
|
|
"fc00::/7",
|
|
} {
|
|
_, network, err := net.ParseCIDR(cidr)
|
|
if err == nil && network.Contains(ip) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func splitClientIPs(forwardedFor string) []string {
|
|
if strings.TrimSpace(forwardedFor) == "" {
|
|
return nil
|
|
}
|
|
parts := strings.Split(forwardedFor, ",")
|
|
result := make([]string, 0, len(parts))
|
|
for _, part := range parts {
|
|
if ip := normalizeIP(part); ip != "" {
|
|
result = append(result, ip)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func firstPublicIP(candidates []string) string {
|
|
for _, candidate := range candidates {
|
|
if !IsPrivateOrReservedIP(candidate) {
|
|
return candidate
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func normalizeIP(raw string) string {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return ""
|
|
}
|
|
if host, _, err := net.SplitHostPort(raw); err == nil {
|
|
raw = host
|
|
}
|
|
ip := net.ParseIP(raw)
|
|
if ip == nil {
|
|
return ""
|
|
}
|
|
return ip.String()
|
|
}
|