XTLS/Xray-core · error

Dispatcher: Invalid destination.

Error message

Dispatcher: Invalid destination.

What it means

validatePaddingSchedule rejects a send range that does not fit inside the turn's overall length bounds or is internally unordered. The checks are sendMinLength >= minLength, sendMaxLength >= sendMinLength, and sendMaxLength <= maxLength, where minLength/maxLength come from paddingTurnBounds. The send range picks what this side actually transmits, so it must be a sub-range of the negotiated turn bounds.

Source

Thrown at app/dispatcher/default.go:326

				if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && fkr0.IsIPInIPPool(ob.Target.Address) {
					isFakeIP = true
				}
				if sniffingRequest.RouteOnly && protocol != "fakedns" && protocol != "fakedns+others" && !isFakeIP {
					ob.RouteTarget = destination
				} else {
					ob.Target = destination
				}
			}
			d.routedDispatch(ctx, outbound, destination)
		}()
	}
	return inbound, nil
}

// DispatchLink implements routing.Dispatcher.
func (d *DefaultDispatcher) DispatchLink(ctx context.Context, destination net.Destination, outbound *transport.Link) error {
	if !destination.IsValid() {
		return errors.New("Dispatcher: Invalid destination.")
	}
	outbounds := session.OutboundsFromContext(ctx)
	if len(outbounds) == 0 {
		outbounds = []*session.Outbound{{}}
		ctx = session.ContextWithOutbounds(ctx, outbounds)
	}
	ob := outbounds[len(outbounds)-1]
	ob.OriginalTarget = destination
	ob.Target = destination
	content := session.ContentFromContext(ctx)
	if content == nil {
		content = new(session.Content)
		ctx = session.ContextWithContent(ctx, content)
	}
	outbound = WrapLink(ctx, d.policy, d.stats, outbound)
	sniffingRequest := content.SniffingRequest
	if !sniffingRequest.Enabled {
		d.routedDispatch(ctx, outbound, destination)

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Clamp the send range to [turn.minLength, turn.maxLength]: sendMinLength >= minLength and sendMaxLength <= maxLength
  2. Ensure sendMinLength <= sendMaxLength
  3. If a wider send range is actually needed, raise minLength/maxLength of the turn itself first

Example fix

// before
paddingTurn{
  minLength: 200, maxLength: 1000,
  sendMinLength: 100, sendMaxLength: 2000,
}
// after
paddingTurn{
  minLength: 200, maxLength: 1000,
  sendMinLength: 200, sendMaxLength: 1000,
}
Defensive patterns

Strategy: validation

Validate before calling

func sendRangeFits(t paddingTurn) bool {
    if t.sendMinLength == 0 && t.sendMaxLength == 0 {
        return true
    }
    return t.sendMinLength >= t.minLength &&
        t.sendMaxLength >= t.sendMinLength &&
        t.sendMaxLength <= t.maxLength
}

Prevention

When it happens

Trigger: A turn with sendMinLength/sendMaxLength set where sendMinLength < turn.minLength, sendMaxLength > turn.maxLength, or sendMaxLength < sendMinLength. Fails during validatePaddingSchedule before any bytes are written.

Common situations: Setting sendMaxLength larger than maxLength expecting the send range to widen the turn (it cannot); swapping min/max fields by hand; editing only one end of the range during tuning; stale maxLength after lowering the turn bound.

Related errors


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