flipped-aurora/gin-vue-admin · error

非法拨号 IP: %s

Error message

非法拨号 IP: %s

What it means

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.

Source

Thrown at server/service/system/sys_timed_task_http.go:34

// isDisallowedIP 内网/环回/链路本地/未指定地址判定
func isDisallowedIP(ip net.IP) bool {
	return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsUnspecified()
}

// ssrfControl 在拨号阶段(DNS 解析后、连接建立前)校验目标 IP:
// 每次连接都过检, 天然覆盖重定向与 DNS rebinding(TOCTOU 安全)。
func ssrfControl(allowPrivate bool) func(network, address string, c syscall.RawConn) error {
	return func(_ string, address string, _ syscall.RawConn) error {
		if allowPrivate {
			return nil
		}
		host, _, err := net.SplitHostPort(address)
		if err != nil {
			return fmt.Errorf("解析拨号地址失败: %w", err)
		}
		ip := net.ParseIP(host)
		if ip == nil {
			return fmt.Errorf("非法拨号 IP: %s", host)
		}
		if isDisallowedIP(ip) {
			return fmt.Errorf("%w: %s", errPrivateAddr, ip)
		}
		return nil
	}
}

// newTimedTaskHTTPClient 定时任务专用 HTTP 客户端:
// 整体超时 + 禁用环境代理(防经代理绕过 IP 校验) + 拨号层 SSRF 防护
func newTimedTaskHTTPClient(allowPrivate bool, timeout time.Duration) *http.Client {
	dialer := &net.Dialer{
		Timeout: 10 * time.Second,
		Control: ssrfControl(allowPrivate),
	}
	transport := &http.Transport{
		Proxy:       nil, // 显式禁用代理
		DialContext: dialer.DialContext,

View on GitHub (pinned to 3136500ef3)

Solutions

  1. Verify with nslookup/dig that the target hostname resolves to a valid IP literal
  2. Check /etc/hosts and resolver configuration for malformed entries
  3. Fix the task's httpUrl if it contains a bad literal (e.g. 'http://999.1.1.1')
  4. If inside a mesh/VPN that rewrites addresses, whitelist the task's network path or enable allowPrivate after a security review

Example fix

// before
"httpUrl": "http://internal.host.invalid:80" // resolves to garbage via broken resolver
// after
"httpUrl": "http://10.0.0.5:80" // use a literal after verifying reachability
Defensive patterns

Strategy: validation

Validate before calling

host := u.Hostname()
ip := net.ParseIP(host)
if ip == nil {
    if addrs, err := net.LookupHost(host); err != nil || len(addrs) == 0 {
        // hostname does not resolve to a valid IP; fix before scheduling
    }
}

Type guard

func isIPStr(s string) bool { return net.ParseIP(s) != nil }

Try / catch

resp, err := client.Do(req)
if err != nil {
    if strings.Contains(err.Error(), "非法拨号 IP") {
        // bad resolved address: fix DNS/hosts, don't blind-retry
        return
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of flipped-aurora/gin-vue-admin@3136500ef3 (2026-08-31). Data as JSON: /api/errors/b773b91eb49d842f. Report an issue: GitHub.