109 lines
1.9 KiB
Go
109 lines
1.9 KiB
Go
// ©AngelaMos | 2026
|
|
// realip.go
|
|
|
|
package middleware
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"sync/atomic"
|
|
)
|
|
|
|
const (
|
|
headerCFConnectingIP = "CF-Connecting-IP"
|
|
headerXForwardedFor = "X-Forwarded-For"
|
|
headerXRealIP = "X-Real-IP"
|
|
)
|
|
|
|
type proxyTrustState struct {
|
|
cidrs []*net.IPNet
|
|
}
|
|
|
|
var trustedProxies atomic.Pointer[proxyTrustState]
|
|
|
|
func SetTrustedProxyCIDRs(cidrs []string) error {
|
|
parsed := make([]*net.IPNet, 0, len(cidrs))
|
|
for _, c := range cidrs {
|
|
trimmed := strings.TrimSpace(c)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
_, n, err := net.ParseCIDR(trimmed)
|
|
if err != nil {
|
|
return fmt.Errorf("trusted proxy cidr %q: %w", c, err)
|
|
}
|
|
parsed = append(parsed, n)
|
|
}
|
|
trustedProxies.Store(&proxyTrustState{cidrs: parsed})
|
|
return nil
|
|
}
|
|
|
|
func ClearTrustedProxyCIDRs() {
|
|
trustedProxies.Store(nil)
|
|
}
|
|
|
|
func isTrustedProxy(remoteAddr string) bool {
|
|
state := trustedProxies.Load()
|
|
if state == nil {
|
|
return true
|
|
}
|
|
host, _, err := net.SplitHostPort(remoteAddr)
|
|
if err != nil {
|
|
host = remoteAddr
|
|
}
|
|
ip := net.ParseIP(host)
|
|
if ip == nil {
|
|
return false
|
|
}
|
|
for _, n := range state.cidrs {
|
|
if n.Contains(ip) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func RealIP(r *http.Request) string {
|
|
if isTrustedProxy(r.RemoteAddr) {
|
|
if v := strings.TrimSpace(
|
|
r.Header.Get(headerCFConnectingIP),
|
|
); v != "" {
|
|
return v
|
|
}
|
|
if v := firstNonEmptyXFF(
|
|
r.Header.Get(headerXForwardedFor),
|
|
); v != "" {
|
|
return v
|
|
}
|
|
if v := strings.TrimSpace(r.Header.Get(headerXRealIP)); v != "" {
|
|
return v
|
|
}
|
|
}
|
|
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
|
return host
|
|
}
|
|
return r.RemoteAddr
|
|
}
|
|
|
|
func OptionalHeader(v string) *string {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
return nil
|
|
}
|
|
return &v
|
|
}
|
|
|
|
func firstNonEmptyXFF(header string) string {
|
|
if header == "" {
|
|
return ""
|
|
}
|
|
for _, p := range strings.Split(header, ",") {
|
|
if v := strings.TrimSpace(p); v != "" {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|