74 lines
1.6 KiB
Go
74 lines
1.6 KiB
Go
package ipguard
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// ClientIP resolves the real caller IP from the request.
|
|
// When the request arrives via the Caddy reverse proxy, the real client IP is
|
|
// forwarded in the X-Real-IP header (set via "header_up X-Real-IP {remote_host}"
|
|
// in the Caddyfile). RemoteAddr is used as a fallback for direct connections
|
|
// (local development, health checks, etc.).
|
|
func ClientIP(r *http.Request) net.IP {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
// Prefer X-Real-IP set by the trusted Caddy reverse proxy.
|
|
if xri := strings.TrimSpace(r.Header.Get("X-Real-IP")); xri != "" {
|
|
if ip := net.ParseIP(xri); ip != nil {
|
|
return ip
|
|
}
|
|
}
|
|
raw := strings.TrimSpace(r.RemoteAddr)
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
if ip := net.ParseIP(raw); ip != nil {
|
|
return ip
|
|
}
|
|
host, _, err := net.SplitHostPort(raw)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return net.ParseIP(host)
|
|
}
|
|
|
|
func parseCIDRs(raw []string) ([]*net.IPNet, error) {
|
|
blocks := make([]*net.IPNet, 0, len(raw))
|
|
for _, item := range raw {
|
|
clean := strings.TrimSpace(item)
|
|
if clean == "" {
|
|
continue
|
|
}
|
|
_, block, err := net.ParseCIDR(clean)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blocks = append(blocks, block)
|
|
}
|
|
return blocks, nil
|
|
}
|
|
|
|
// Allowed reports whether clientIP is allowed by configured CIDRs.
|
|
// Empty CIDR list means unrestricted access.
|
|
func Allowed(clientIP net.IP, cidrs []string) (bool, error) {
|
|
blocks, err := parseCIDRs(cidrs)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if len(blocks) == 0 {
|
|
return true, nil
|
|
}
|
|
if clientIP == nil {
|
|
return false, nil
|
|
}
|
|
for _, block := range blocks {
|
|
if block.Contains(clientIP) {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|