{"record":{"id":"b6e85d6630746d21","repo":"flipped-aurora/gin-vue-admin","slug":"w-b6e85d","errorCode":null,"errorMessage":"解析拨号地址失败: %w","messagePattern":"解析拨号地址失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service/system/sys_timed_task_http.go","lineNumber":30,"sourceCode":"\n// errPrivateAddr SSRF 防护拒绝(错误信息含\"SSRF\"关键字, 供日志/测试识别)\nvar errPrivateAddr = errors.New(\"目标解析为内网/环回/链路本地地址, 已被 SSRF 防护拒绝(可在任务上开启\\\"允许内网\\\"豁免)\")\n\n// 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),","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/service/system/sys_timed_task_http.go#L12-L48","documentation":"ssrfControl is a dialer Control function that runs after DNS resolution, before the connection is established. It splits the dial address into host and port; if net.SplitHostPort fails (malformed address reaching the dialer), the error is wrapped as '解析拨号地址失败'. This indicates the resolved address string could not be parsed, which should not normally happen with a healthy net stack.","triggerScenarios":"An HTTP-executor timed task dials an address whose host:port form is invalid after DNS resolution — e.g. a malformed httpUrl that slipped past validation, or an unusual custom dialer/proxy path producing a non host:port address.","commonSituations":"DNS returning an unusual literal (IPv6 zone id or bracket-mismatched host); proxy or custom resolver injecting a malformed address; internal misconfiguration of the httpUrl (missing port is fine, but stray characters break SplitHostPort).","solutions":["Re-check the task's httpUrl for stray characters, unbalanced brackets, or embedded spaces and fix it","Retry the task — transient resolver glitches can produce bad resolved addresses; persistent occurrences warrant DNS diagnostics","If allowPrivate is intended, enable the task's 'allow private' flag — though note this error precedes the allowPrivate early-return only after the flag check, so confirm the task's actual configuration"],"exampleFix":"// before\n\"httpUrl\": \"http://[192.168.1.10:8080\"  // unbalanced bracket -> SplitHostPort error\n// after\n\"httpUrl\": \"http://192.168.1.10:8080\"","handlingStrategy":"try-catch","validationCode":"u, err := url.Parse(taskHttpUrl)\nif err != nil || u.Host == \"\" {\n    // reject before scheduling the HTTP task\n}\n// prefer IP literals or well-formed hostnames validated with:\nif net.ParseIP(u.Hostname()) == nil && net.ParseIP(u.Port()) != nil {\n    // suspicious mix; review\n}","typeGuard":"func isParseableHostPort(addr string) bool {\n    _, _, err := net.SplitHostPort(addr)\n    return err == nil\n}","tryCatchPattern":"resp, err := client.Do(req)\nif err != nil {\n    if strings.Contains(err.Error(), \"解析拨号地址失败\") {\n        // malformed resolved address: log httpUrl, skip retry (deterministic)\n        return\n    }\n    return err\n}","preventionTips":["Validate httpUrl with url.Parse at task-creation time (the service already does)","Avoid hand-editing URLs in config; use the admin UI validation","Keep DNS/resolver stack healthy; test resolution in the deployment environment"],"tags":["network","ssrf","http-client","go"],"backgroundTag":"address-parse-failed","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}