// Package ipinfo 识别 IP 的网络归属(CDN / 云厂商 / WAF)。 // 基于 projectdiscovery/cdncheck 的离线签名(编译期嵌入,上游每周更新指纹), // 判定过程零外部网络请求,适合 1C1G 小型服务器与告警链路使用。 // 当前用于安全告警邮件:展示扫描来源的网络归属,帮助管理员判断告警严重性。 package ipinfo import ( "fmt" "net" "strings" "sync" "github.com/projectdiscovery/cdncheck" ) var ( clientOnce sync.Once client *cdncheck.Client ) // getClient 懒加载判定客户端(签名数据编译期嵌入,New 无错误返回)。 func getClient() *cdncheck.Client { clientOnce.Do(func() { client = cdncheck.New() }) return client } // Lookup 判定 IP 的网络归属。 // 返回: // - label:展示用标签,如 "Cloudflare(CDN)"、"亚马逊(云厂商)"; // 未命中返回 "未识别(普通网络)",输入非法或判定出错返回 "识别失败"。 // - proxied:IP 是否属于 CDN/WAF/代理网络(用于提示"封禁可能无法拦截真实来源")。 // // 任何内部错误都被折叠为展示标签,不向调用方传播——告警链路不应因归属识别失败而中断。 func Lookup(ipStr string) (label string, proxied bool) { ip := net.ParseIP(strings.TrimSpace(ipStr)) if ip == nil { return "识别失败(IP 格式异常)", false } matched, value, itemType, err := getClient().Check(ip) if err != nil { return "识别失败", false } if !matched || value == "" { return "未识别(普通网络)", false } return fmt.Sprintf("%s(%s)", title(value), typeLabel(itemType)), true } // typeLabel 将 cdncheck 的 itemType 翻译为中文展示。 func typeLabel(itemType string) string { switch itemType { case "cdn": return "CDN" case "waf": return "WAF" case "cloud": return "云厂商" default: return "未知类型" } } // acronymProviders 需要保持全大写的提供商缩写(cdncheck 返回全小写,如 aws → AWS)。 var acronymProviders = map[string]bool{ "aws": true, "gcp": true, "oci": true, "gds": true, "ovh": true, } // title 将提供商名转为展示形式:缩写全大写(aws → AWS),其余首字母大写 // (cloudflare → Cloudflare)。 func title(s string) string { if s == "" { return s } lower := strings.ToLower(s) if acronymProviders[lower] { return strings.ToUpper(lower) } r := []rune(lower) return strings.ToUpper(string(r[0])) + string(r[1:]) }