XTLS/Xray-core · error

failed to resolve ip for target ${domain}

Error message

failed to resolve ip for target ${domain}

What it means

Thrown while dispatching an outbound connection whose target is a domain: the handler tries to pre-resolve the domain to an IP because the sender's domainStrategy (TargetStrategy) demands it, and internet.LookupForIP fails. The error is first logged at info level; it becomes fatal for the connection only when the strategy has ForceIP() set (e.g. domainStrategy forceIP), in which case the error is submitted to the originator and the link is interrupted. Without ForceIP, resolution failure is tolerated and the domain is passed through unresolved.

Source

Thrown at app/proxyman/outbound/handler.go:194

func (h *Handler) Tag() string {
	return h.tag
}

// Dispatch implements proxy.Outbound.Dispatch.
func (h *Handler) Dispatch(ctx context.Context, link *transport.Link) {
	outbounds := session.OutboundsFromContext(ctx)
	ob := outbounds[len(outbounds)-1]
	content := session.ContentFromContext(ctx)
	if h.senderSettings != nil && h.senderSettings.TargetStrategy.HasStrategy() && ob.Target.Address.Family().IsDomain() && (content == nil || !content.SkipDNSResolve) {
		strategy := h.senderSettings.TargetStrategy
		if ob.Target.Network == net.Network_UDP && ob.OriginalTarget.Address != nil {
			strategy = strategy.GetDynamicStrategy(ob.OriginalTarget.Address.Family())
		}
		ips, err := internet.LookupForIP(ob.Target.Address.Domain(), strategy, nil)
		if err != nil {
			errors.LogInfoInner(ctx, err, "failed to resolve ip for target ", ob.Target.Address.Domain())
			if h.senderSettings.TargetStrategy.ForceIP() {
				err := errors.New("failed to resolve ip for target ", ob.Target.Address.Domain()).Base(err)
				session.SubmitOutboundErrorToOriginator(ctx, err)
				common.Interrupt(link.Writer)
				common.Interrupt(link.Reader)
				return
			}

		} else {
			unchangedDomain := ob.Target.Address.Domain()
			ob.Target.Address = net.IPAddress(ips[dice.Roll(len(ips))])
			errors.LogInfo(ctx, "target: ", unchangedDomain, " resolved to: ", ob.Target.Address.String())
		}
	}
	if ob.Target.Network == net.Network_UDP && ob.OriginalTarget.Address != nil && ob.OriginalTarget.Address != ob.Target.Address {
		link.Reader = &buf.EndpointOverrideReader{Reader: link.Reader, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}
		link.Writer = &buf.EndpointOverrideWriter{Writer: link.Writer, Dest: ob.Target.Address, OriginalDest: ob.OriginalTarget.Address}
	}
	if h.mux != nil {
		test := func(err error) {

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Check DNS connectivity and config: verify the configured nameservers respond (e.g. dig @server domain) and that the strategy's address family (IPv4/IPv6) actually has records for the domain.
  2. If the target domain is only resolvable remotely or resolution is unnecessary, relax domainStrategy from forceIP/forceIPv4/forceIPv6 to useIP or asis so failure is non-fatal.
  3. Set SkipDNSResolve on the inbound's sniffing/content when the client already supplies addresses or resolution should be deferred to the remote server.
  4. Fix or exclude the offending domain: correct typos, add a hosts entry for internal domains, or route the domain to an outbound whose remote side resolves it.

Example fix

// before (json config)
"outbounds": [{ "tag": "proxy", "protocol": "vmess",
  "settings": { "vnext": [ /* ... */ ] },
  "streamSettings": { ... } }]
// with policy forcing resolution failure to be fatal via forceIP in dns/sender settings

// after: allow domain pass-through when local resolution fails
"outbounds": [{ "tag": "proxy", "protocol": "vmess",
  "settings": { "vnext": [ /* ... */ ] },
  "streamSettings": { "sockopt": { "domainStrategy": "AsIs" } } }]
// or set "skipDNSResolve" in inbound sniffing options so pre-dispatch lookup is skipped
Defensive patterns

Strategy: fallback

Validate before calling

// Pre-flight the exact resolution the handler will attempt
strategy := senderSettings.TargetStrategy // e.g. dns.IPOption{IPv4Enable:true}
if strategy.HasStrategy() && targetAddr.Family().IsDomain() {
    ips, err := internet.LookupForIP(targetAddr.Domain(), strategy, nil)
    if err != nil && strategy.ForceIP() {
        // dispatch would be fatal: divert to another outbound or fail fast with context
        return fmt.Errorf("pre-check: target %s unresolvable under forceIP", targetAddr.Domain())
    }
}

Type guard

func resolvesUnderStrategy(domain string, s dns.IPOption) bool {
    if !s.HasStrategy() { return true }
    ips, err := internet.LookupForIP(domain, s, nil)
    return err == nil && len(ips) > 0
}

Try / catch

// Dispatch-side: tolerate and reroute rather than propagate
if err := handler.Dispatch(ctx, link); err != nil && strings.Contains(err.Error(), "failed to resolve ip for target") {
    if fb := fallbackHandlerFor(ctx); fb != nil { return fb.Dispatch(ctx, link) }
    return err
}

Prevention

When it happens

Trigger: Calling Dispatch on an outbound handler whose senderSettings.TargetStrategy.HasStrategy() is true, the latest session outbound target is a domain (Family().IsDomain()), and content is nil or content.SkipDNSResolve is false, while the configured DNS server fails or times out on internet.LookupForIP(domain, strategy, nil). Only fatal when TargetStrategy.ForceIP() returns true (forceIP / ForceIP-style strategies); for UDP with an OriginalTarget, the dynamic strategy derived from GetDynamicStrategy is used instead.

Common situations: DNS server in config unreachable or black-holed (blocked port 53, doh endpoint down), a forceIP domainStrategy combined with a domain that has no A/AAAA records (e.g. a typo or an internal .local name), IPv6-only strategy (UseIPv6/forceIPv6) on a host with no IPv6 records/resolved routes, or sniffed domains the local resolver refuses. Also seen when skipDNSResolve is not set on inbounds that carry already-resolved content.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/634dee52f2985983. Report an issue: GitHub.