{"record":{"id":"b773b91eb49d842f","repo":"flipped-aurora/gin-vue-admin","slug":"ip-s","errorCode":null,"errorMessage":"非法拨号 IP: %s","messagePattern":"非法拨号 IP: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service/system/sys_timed_task_http.go","lineNumber":34,"sourceCode":"// isDisallowedIP 内网/环回/链路本地/未指定地址判定\nfunc isDisallowedIP(ip net.IP) bool {\n\treturn ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsUnspecified()\n}\n\n// ssrfControl 在拨号阶段(DNS 解析后、连接建立前)校验目标 IP:\n// 每次连接都过检, 天然覆盖重定向与 DNS rebinding(TOCTOU 安全)。\nfunc ssrfControl(allowPrivate bool) func(network, address string, c syscall.RawConn) error {\n\treturn func(_ string, address string, _ syscall.RawConn) error {\n\t\tif allowPrivate {\n\t\t\treturn nil\n\t\t}\n\t\thost, _, err := net.SplitHostPort(address)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"解析拨号地址失败: %w\", err)\n\t\t}\n\t\tip := net.ParseIP(host)\n\t\tif ip == nil {\n\t\t\treturn fmt.Errorf(\"非法拨号 IP: %s\", host)\n\t\t}\n\t\tif isDisallowedIP(ip) {\n\t\t\treturn fmt.Errorf(\"%w: %s\", errPrivateAddr, ip)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// newTimedTaskHTTPClient 定时任务专用 HTTP 客户端:\n// 整体超时 + 禁用环境代理(防经代理绕过 IP 校验) + 拨号层 SSRF 防护\nfunc newTimedTaskHTTPClient(allowPrivate bool, timeout time.Duration) *http.Client {\n\tdialer := &net.Dialer{\n\t\tTimeout: 10 * time.Second,\n\t\tControl: ssrfControl(allowPrivate),\n\t}\n\ttransport := &http.Transport{\n\t\tProxy:       nil, // 显式禁用代理\n\t\tDialContext: dialer.DialContext,","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/service/system/sys_timed_task_http.go#L16-L52","documentation":"The SSRF dial-control parses the resolved host as an IP; if net.ParseIP fails, the address is not a valid IP literal and the dial is rejected with '非法拨号 IP: <host>'. Since the control runs post-DNS, the resolved address should always be an IP — failure signals a malformed or unexpected resolved value.","triggerScenarios":"DNS resolution yields a non-IP host string to the Control hook (unusual resolver behavior, hosts-file oddities), or a custom address format reaches the dialer.","commonSituations":"Broken/patched DNS resolvers returning malformed answers; /etc/hosts entries with invalid literals; running tasks against hostnames that resolve through custom networking plugins (VPN, service mesh) that rewrite addresses.","solutions":["Verify with nslookup/dig that the target hostname resolves to a valid IP literal","Check /etc/hosts and resolver configuration for malformed entries","Fix the task's httpUrl if it contains a bad literal (e.g. 'http://999.1.1.1')","If inside a mesh/VPN that rewrites addresses, whitelist the task's network path or enable allowPrivate after a security review"],"exampleFix":"// before\n\"httpUrl\": \"http://internal.host.invalid:80\" // resolves to garbage via broken resolver\n// after\n\"httpUrl\": \"http://10.0.0.5:80\" // use a literal after verifying reachability","handlingStrategy":"validation","validationCode":"host := u.Hostname()\nip := net.ParseIP(host)\nif ip == nil {\n    if addrs, err := net.LookupHost(host); err != nil || len(addrs) == 0 {\n        // hostname does not resolve to a valid IP; fix before scheduling\n    }\n}","typeGuard":"func isIPStr(s string) bool { return net.ParseIP(s) != nil }","tryCatchPattern":"resp, err := client.Do(req)\nif err != nil {\n    if strings.Contains(err.Error(), \"非法拨号 IP\") {\n        // bad resolved address: fix DNS/hosts, don't blind-retry\n        return\n    }\n    return err\n}","preventionTips":["Use IP literals or verified hostnames in task httpUrl","Audit /etc/hosts and resolver config in the deploy environment","Pin hostnames to IPs when DNS is unreliable"],"tags":["network","ssrf","dns","go"],"backgroundTag":"invalid-ip-address","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}