ip.go 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. // Package ip 提供客户端 IP 地址提取工具。
  2. package ip
  3. import (
  4. "net"
  5. "strings"
  6. "github.com/gin-gonic/gin"
  7. )
  8. const forwardedIPSettingsKey = "sub2api.forwarded_ip_settings"
  9. type forwardedIPSettings struct {
  10. trustForwarded bool
  11. headers []string
  12. }
  13. // SetForwardedIPSettings snapshots the forwarded-IP mode and custom header list
  14. // for this request.
  15. func SetForwardedIPSettings(c *gin.Context, enabled bool, headers []string) {
  16. if c == nil {
  17. return
  18. }
  19. c.Set(forwardedIPSettingsKey, forwardedIPSettings{
  20. trustForwarded: enabled,
  21. headers: append([]string(nil), headers...),
  22. })
  23. }
  24. // SetLegacyForwardedIPTrust records whether raw forwarding headers override
  25. // Gin's server.trusted_proxies chain for this request.
  26. func SetLegacyForwardedIPTrust(c *gin.Context, enabled bool) {
  27. SetForwardedIPSettings(c, enabled, nil)
  28. }
  29. func requestForwardedIPSettings(c *gin.Context) (forwardedIPSettings, bool) {
  30. if c == nil {
  31. return forwardedIPSettings{}, false
  32. }
  33. value, ok := c.Get(forwardedIPSettingsKey)
  34. if !ok {
  35. return forwardedIPSettings{}, false
  36. }
  37. settings, ok := value.(forwardedIPSettings)
  38. return settings, ok
  39. }
  40. func requestUsesLegacyForwardedIPTrust(c *gin.Context) bool {
  41. settings, ok := requestForwardedIPSettings(c)
  42. return !ok || settings.trustForwarded
  43. }
  44. // GetClientIP resolves the client address using the legacy forwarding-header
  45. // precedence used before the trusted-proxy hardening. It remains the
  46. // compatibility path for request metadata and usage/error logs; security-
  47. // sensitive callers must use GetTrustedClientIP or GetSecurityClientIP.
  48. func GetClientIP(c *gin.Context) string {
  49. if c == nil {
  50. return ""
  51. }
  52. if !requestUsesLegacyForwardedIPTrust(c) {
  53. return GetTrustedClientIP(c)
  54. }
  55. settings, _ := requestForwardedIPSettings(c)
  56. customIP, customFallback := resolveCustomForwardedClientIP(c, settings.headers)
  57. if customIP != "" {
  58. return customIP
  59. }
  60. // Preserve the historical precedence used by existing reverse-proxy
  61. // deployments, while skipping an internal proxy address when a public XFF
  62. // value is available. This covers Docker/Nginx setups that accidentally
  63. // write the bridge address into X-Real-IP.
  64. legacyIP, legacyFallback := resolveLegacyForwardedHeaderIP(c)
  65. if legacyIP != "" {
  66. return legacyIP
  67. }
  68. if customFallback != "" {
  69. return customFallback
  70. }
  71. if legacyFallback != "" {
  72. return legacyFallback
  73. }
  74. return normalizeIP(c.ClientIP())
  75. }
  76. func resolveCustomForwardedClientIP(c *gin.Context, headers []string) (string, string) {
  77. if c == nil {
  78. return "", ""
  79. }
  80. var fallback string
  81. for _, header := range headers {
  82. for _, value := range c.Request.Header.Values(header) {
  83. for _, candidate := range strings.Split(value, ",") {
  84. parsed := net.ParseIP(strings.TrimSpace(candidate))
  85. if parsed == nil {
  86. continue
  87. }
  88. normalized := parsed.String()
  89. if isPrivateIP(normalized) {
  90. if fallback == "" {
  91. fallback = normalized
  92. }
  93. continue
  94. }
  95. return normalized, fallback
  96. }
  97. }
  98. }
  99. return "", fallback
  100. }
  101. func resolveLegacyForwardedHeaderIP(c *gin.Context) (string, string) {
  102. var fallback string
  103. if forwarded := normalizeValidIP(c.GetHeader("CF-Connecting-IP")); forwarded != "" {
  104. fallback = forwarded
  105. if !isPrivateIP(forwarded) {
  106. return forwarded, fallback
  107. }
  108. }
  109. if realIP := normalizeValidIP(c.GetHeader("X-Real-IP")); realIP != "" {
  110. if fallback == "" {
  111. fallback = realIP
  112. }
  113. if !isPrivateIP(realIP) {
  114. return realIP, fallback
  115. }
  116. }
  117. if xff := c.GetHeader("X-Forwarded-For"); xff != "" {
  118. ips := strings.Split(xff, ",")
  119. for _, candidate := range ips {
  120. candidate = normalizeValidIP(candidate)
  121. if candidate != "" && !isPrivateIP(candidate) {
  122. return candidate, fallback
  123. }
  124. }
  125. if fallback == "" {
  126. for _, candidate := range ips {
  127. if candidate = normalizeValidIP(candidate); candidate != "" {
  128. fallback = candidate
  129. break
  130. }
  131. }
  132. }
  133. }
  134. return "", fallback
  135. }
  136. // GetTrustedClientIP 从 Gin 的可信代理解析链提取客户端 IP。
  137. // 该方法依赖 gin.Engine.SetTrustedProxies 配置,不会优先直接信任原始转发头值。
  138. // 适用于 ACL / 风控等安全敏感场景。
  139. func GetTrustedClientIP(c *gin.Context) string {
  140. if c == nil {
  141. return ""
  142. }
  143. return normalizeIP(c.ClientIP())
  144. }
  145. // GetSecurityClientIP returns the address used by security-sensitive paths.
  146. // When legacy forwarded-IP trust is enabled, raw forwarding headers take over
  147. // client-IP resolution. When disabled, Gin's server.trusted_proxies chain is
  148. // authoritative.
  149. func GetSecurityClientIP(c *gin.Context, trustForwarded bool) string {
  150. if requestSettings, ok := requestForwardedIPSettings(c); ok {
  151. trustForwarded = requestSettings.trustForwarded
  152. }
  153. if trustForwarded {
  154. return GetClientIP(c)
  155. }
  156. return GetTrustedClientIP(c)
  157. }
  158. // normalizeIP 规范化 IP 地址,去除端口号和空格。
  159. func normalizeIP(ip string) string {
  160. ip = strings.TrimSpace(ip)
  161. // 移除端口号(如 "192.168.1.1:8080" -> "192.168.1.1")
  162. if host, _, err := net.SplitHostPort(ip); err == nil {
  163. return host
  164. }
  165. return ip
  166. }
  167. // normalizeValidIP 规范化并验证代理头中的候选值,避免把 unknown、主机名等非法值传给安全服务。
  168. func normalizeValidIP(value string) string {
  169. normalized := normalizeIP(value)
  170. parsed := net.ParseIP(normalized)
  171. if parsed == nil {
  172. return ""
  173. }
  174. return parsed.String()
  175. }
  176. // privateNets contains the private/loopback ranges skipped while selecting a
  177. // public address from a legacy X-Forwarded-For chain.
  178. var privateNets []*net.IPNet
  179. func init() {
  180. for _, cidr := range []string{
  181. "10.0.0.0/8",
  182. "172.16.0.0/12",
  183. "192.168.0.0/16",
  184. "127.0.0.0/8",
  185. "::1/128",
  186. "fc00::/7",
  187. } {
  188. _, block, err := net.ParseCIDR(cidr)
  189. if err != nil {
  190. panic("invalid CIDR: " + cidr)
  191. }
  192. privateNets = append(privateNets, block)
  193. }
  194. }
  195. // CompiledIPRules 表示预编译的 IP 匹配规则。
  196. // PatternCount 记录原始规则数量,用于保留“规则存在但全无效”时的行为语义。
  197. type CompiledIPRules struct {
  198. CIDRs []*net.IPNet
  199. IPs []net.IP
  200. PatternCount int
  201. }
  202. // CompileIPRules 将 IP/CIDR 字符串规则预编译为可复用结构。
  203. // 非法规则会被忽略,但 PatternCount 会保留原始规则条数。
  204. func CompileIPRules(patterns []string) *CompiledIPRules {
  205. compiled := &CompiledIPRules{
  206. CIDRs: make([]*net.IPNet, 0, len(patterns)),
  207. IPs: make([]net.IP, 0, len(patterns)),
  208. PatternCount: len(patterns),
  209. }
  210. for _, pattern := range patterns {
  211. normalized := strings.TrimSpace(pattern)
  212. if normalized == "" {
  213. continue
  214. }
  215. if strings.Contains(normalized, "/") {
  216. _, cidr, err := net.ParseCIDR(normalized)
  217. if err != nil || cidr == nil {
  218. continue
  219. }
  220. compiled.CIDRs = append(compiled.CIDRs, cidr)
  221. continue
  222. }
  223. parsedIP := net.ParseIP(normalized)
  224. if parsedIP == nil {
  225. continue
  226. }
  227. compiled.IPs = append(compiled.IPs, parsedIP)
  228. }
  229. return compiled
  230. }
  231. func matchesCompiledRules(parsedIP net.IP, rules *CompiledIPRules) bool {
  232. if parsedIP == nil || rules == nil {
  233. return false
  234. }
  235. for _, cidr := range rules.CIDRs {
  236. if cidr.Contains(parsedIP) {
  237. return true
  238. }
  239. }
  240. for _, ruleIP := range rules.IPs {
  241. if parsedIP.Equal(ruleIP) {
  242. return true
  243. }
  244. }
  245. return false
  246. }
  247. func isPrivateIP(ipStr string) bool {
  248. ip := net.ParseIP(ipStr)
  249. if ip == nil {
  250. return false
  251. }
  252. for _, block := range privateNets {
  253. if block.Contains(ip) {
  254. return true
  255. }
  256. }
  257. return false
  258. }
  259. // MatchesPattern 检查 IP 是否匹配指定的模式(支持单个 IP 或 CIDR)。
  260. // pattern 可以是:
  261. // - 单个 IP: "192.168.1.100"
  262. // - CIDR 范围: "192.168.1.0/24"
  263. func MatchesPattern(clientIP, pattern string) bool {
  264. ip := net.ParseIP(clientIP)
  265. if ip == nil {
  266. return false
  267. }
  268. // 尝试解析为 CIDR
  269. if strings.Contains(pattern, "/") {
  270. _, cidr, err := net.ParseCIDR(pattern)
  271. if err != nil {
  272. return false
  273. }
  274. return cidr.Contains(ip)
  275. }
  276. // 作为单个 IP 处理
  277. patternIP := net.ParseIP(pattern)
  278. if patternIP == nil {
  279. return false
  280. }
  281. return ip.Equal(patternIP)
  282. }
  283. // MatchesAnyPattern 检查 IP 是否匹配任意一个模式。
  284. func MatchesAnyPattern(clientIP string, patterns []string) bool {
  285. for _, pattern := range patterns {
  286. if MatchesPattern(clientIP, pattern) {
  287. return true
  288. }
  289. }
  290. return false
  291. }
  292. // CheckIPRestriction 检查 IP 是否被 API Key 的 IP 限制允许。
  293. // 返回值:(是否允许, 拒绝原因)
  294. // 逻辑:
  295. // 1. 先检查黑名单,如果在黑名单中则直接拒绝
  296. // 2. 如果白名单不为空,IP 必须在白名单中
  297. // 3. 如果白名单为空,允许访问(除非被黑名单拒绝)
  298. func CheckIPRestriction(clientIP string, whitelist, blacklist []string) (bool, string) {
  299. return CheckIPRestrictionWithCompiledRules(
  300. clientIP,
  301. CompileIPRules(whitelist),
  302. CompileIPRules(blacklist),
  303. )
  304. }
  305. // CheckIPRestrictionWithCompiledRules 使用预编译规则检查 IP 是否允许访问。
  306. func CheckIPRestrictionWithCompiledRules(clientIP string, whitelist, blacklist *CompiledIPRules) (bool, string) {
  307. // 规范化 IP
  308. clientIP = normalizeIP(clientIP)
  309. if clientIP == "" {
  310. return false, "access denied"
  311. }
  312. parsedIP := net.ParseIP(clientIP)
  313. if parsedIP == nil {
  314. return false, "access denied"
  315. }
  316. // 1. 检查黑名单
  317. if blacklist != nil && blacklist.PatternCount > 0 && matchesCompiledRules(parsedIP, blacklist) {
  318. return false, "access denied"
  319. }
  320. // 2. 检查白名单(如果设置了白名单,IP 必须在其中)
  321. if whitelist != nil && whitelist.PatternCount > 0 && !matchesCompiledRules(parsedIP, whitelist) {
  322. return false, "access denied"
  323. }
  324. return true, ""
  325. }
  326. // ValidateIPPattern 验证 IP 或 CIDR 格式是否有效。
  327. func ValidateIPPattern(pattern string) bool {
  328. if strings.Contains(pattern, "/") {
  329. _, _, err := net.ParseCIDR(pattern)
  330. return err == nil
  331. }
  332. return net.ParseIP(pattern) != nil
  333. }
  334. // ValidateIPPatterns 验证多个 IP 或 CIDR 格式。
  335. // 返回无效的模式列表。
  336. func ValidateIPPatterns(patterns []string) []string {
  337. var invalid []string
  338. for _, p := range patterns {
  339. if !ValidateIPPattern(p) {
  340. invalid = append(invalid, p)
  341. }
  342. }
  343. return invalid
  344. }