micro/go-micro · error

push callback: cannot parse dial address %q

Error message

push callback: cannot parse dial address %q

What it means

pushDialControl validates the address a push-callback HTTP request is about to dial. It splits the address into host and port and parses the host as an IP; if the host is not a valid IP literal (e.g. a hostname like 'example.com:8080'), it rejects the dial. This guard ensures push callbacks only ever connect to explicit IP addresses that the SSRF policy can evaluate.

Source

Thrown at gateway/a2a/pushsecurity.go:88

		ip.IsPrivate() ||
		ip.IsLinkLocalUnicast() ||
		ip.IsLinkLocalMulticast() ||
		ip.IsInterfaceLocalMulticast() ||
		ip.IsMulticast() ||
		ip.IsUnspecified()
}

// pushDialControl runs after DNS resolution, immediately before connect, on the
// resolved address — so it blocks a host that passed URL validation but was
// rebound to an internal IP (DNS rebinding).
func pushDialControl(_, address string, _ syscall.RawConn) error {
	host, _, err := net.SplitHostPort(address)
	if err != nil {
		return err
	}
	ip := net.ParseIP(host)
	if ip == nil {
		return fmt.Errorf("push callback: cannot parse dial address %q", address)
	}
	if blockedPushIP(ip) {
		return fmt.Errorf("push callback: refusing to connect to blocked address %s", ip)
	}
	return nil
}

// pushGuardClient is the HTTP client used for default-policy push delivery. Its
// dialer refuses connections to blocked addresses at connect time.
var pushGuardClient = &http.Client{
	Timeout: 10 * time.Second,
	Transport: &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout: 5 * time.Second,
			Control: pushDialControl,
		}).DialContext,
	},

View on GitHub (pinned to 24529f1404)

Solutions

  1. Use an IP-literal callback URL (e.g. http://10.0.0.5:8080/callback) instead of a hostname.
  2. Resolve the hostname to an IP beforehand if a custom dialer/policy is in place, and configure the callback with the resolved IP.
  3. If hostname callbacks are legitimately required, supply a custom dial control / AllowPushURL policy that resolves and validates hostnames before dialing.

Example fix

// before
config.PushCallbackURL = "https://worker.example.com/callback"
// after
config.PushCallbackURL = "https://203.0.113.10/callback" // IP literal accepted by pushDialControl
Defensive patterns

Strategy: validation

Validate before calling

host, _, err := net.SplitHostPort(strings.TrimPrefix(callbackURL, "http://"))
if err != nil || net.ParseIP(host) == nil {
    return fmt.Errorf("push callback host must be an IP literal, got %q", host)
}

Type guard

func isIPLiteral(rawURL string) bool {
    u, err := url.Parse(rawURL)
    if err != nil { return false }
    host := u.Hostname()
    return net.ParseIP(host) != nil
}

Prevention

When it happens

Trigger: Delivering a push notification whose resolved dial address has a host part that is a DNS name rather than an IP literal, so net.SplitHostPort succeeds but net.ParseIP returns nil.

Common situations: Configuring a push callback URL with a hostname (https://worker.example.com/callback) instead of an IP; a custom AllowPushURL policy permitting hostnames but the dialer requiring IPs; DNS-based service names in the callback URL.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/51bc36fe48106743. Report an issue: GitHub.