Files
NixMsg/internal/httpx/clientip.go
T

110 lines
2.4 KiB
Go

package httpx
import (
"net"
"net/http"
"strings"
)
// ClientIP 按 DEVELOPMENT 4.5:仅当对端在 trusted 网段内时采信
// X-Forwarded-For(合并多行,从右往左第一个不在 trusted 内的地址)。
// 遇到无法解析的项立即停下,回退到对端地址。
func ClientIP(r *http.Request, trusted []*net.IPNet) string {
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
host = r.RemoteAddr
}
ip := net.ParseIP(host)
if ip == nil {
return host
}
if !ipInNets(ip, trusted) {
return ip.String()
}
xff := strings.Join(r.Header.Values("X-Forwarded-For"), ",")
if xff == "" {
return ip.String()
}
parts := strings.Split(xff, ",")
for i := len(parts) - 1; i >= 0; i-- {
cand := strings.TrimSpace(parts[i])
if cand == "" {
continue
}
parsed := parseForwardedIP(cand)
if parsed == nil {
return ip.String()
}
if !ipInNets(parsed, trusted) {
return parsed.String()
}
}
return ip.String()
}
func parseForwardedIP(s string) net.IP {
if ip := net.ParseIP(s); ip != nil {
return ip
}
host, _, err := net.SplitHostPort(s)
if err == nil {
return net.ParseIP(host)
}
if strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]") {
return net.ParseIP(s[1 : len(s)-1])
}
return nil
}
// IsHTTPS 判定请求是否视为 HTTPS(直连 TLS 或受信任代理的 X-Forwarded-Proto)。
func IsHTTPS(r *http.Request, trusted []*net.IPNet) bool {
if r.TLS != nil {
return true
}
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
host = r.RemoteAddr
}
ip := net.ParseIP(host)
if ip == nil || !ipInNets(ip, trusted) {
return false
}
proto := strings.ToLower(strings.TrimSpace(r.Header.Get("X-Forwarded-Proto")))
return proto == "https"
}
// ParseCIDRs 解析 CIDR 列表;非法项跳过。
func ParseCIDRs(cidrs []string) []*net.IPNet {
var out []*net.IPNet
for _, c := range cidrs {
c = strings.TrimSpace(c)
if c == "" {
continue
}
_, n, err := net.ParseCIDR(c)
if err != nil {
// 允许单 IP 写成无掩码
if ip := net.ParseIP(c); ip != nil {
if ip.To4() != nil {
_, n, err = net.ParseCIDR(ip.String() + "/32")
} else {
_, n, err = net.ParseCIDR(ip.String() + "/128")
}
}
}
if err == nil && n != nil {
out = append(out, n)
}
}
return out
}
func ipInNets(ip net.IP, nets []*net.IPNet) bool {
for _, n := range nets {
if n.Contains(ip) {
return true
}
}
return false
}