XTLS/Xray-core · error

unable to dispatch control connection

Error message

unable to dispatch control connection

What it means

NewPortalWorker fails to establish the control connection: it creates a pipe pair, synthesizes a context whose sole outbound targets the internal domain over UDP port 0, and calls client.Dispatch on the mux client; when Dispatch returns false the worker cannot be created. The control channel is what carries heartbeat/registration for the worker, so the tunnel setup aborts and surfaces (wrapped) as error 92's 'failed to create portal worker'.

Source

Thrown at app/reverse/portal.go:249

	timer    *signal.ActivityTimer
}

func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) {
	opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)}
	uplinkReader, uplinkWriter := pipe.New(opt...)
	downlinkReader, downlinkWriter := pipe.New(opt...)

	ctx := context.Background()
	outbounds := []*session.Outbound{{
		Target: net.UDPDestination(net.DomainAddress(internalDomain), 0),
	}}
	ctx = session.ContextWithOutbounds(ctx, outbounds)
	f := client.Dispatch(ctx, &transport.Link{
		Reader: uplinkReader,
		Writer: downlinkWriter,
	})
	if !f {
		return nil, errors.New("unable to dispatch control connection")
	}
	terminate := func() {
		client.Close()
	}
	w := &PortalWorker{
		client: client,
		reader: downlinkReader,
		writer: uplinkWriter,
		timer:  signal.CancelAfterInactivity(ctx, terminate, 24*time.Hour), // // prevent leak
	}
	w.control = &task.Periodic{
		Execute:  w.heartbeat,
		Interval: time.Second * 2,
	}
	w.control.Start()
	return w, nil
}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. If occasional, rely on the bridge's automatic reconnect (monitor task) and treat as noise; persistent occurrence needs deeper checks.
  2. Match xray-core versions on both ends of the reverse tunnel.
  3. Check the carrier connection stability (NAT timeouts killing the tunnel at ~the same age, TLS issues) — a dying carrier makes Dispatch fail immediately.
Defensive patterns

Strategy: retry

Validate before calling

null // internal to NewPortalWorker; prevent by keeping the carrier link alive

Try / catch

if err := portal.HandleConnection(ctx, link); err != nil {
    cause := deepestCause(err) // unwrap: 'failed to create portal worker' -> 'unable to dispatch control connection'
    if strings.Contains(cause, "unable to dispatch control connection") {
        return bridgeMonitor.ReconnectSoon() // transient race; retry via periodic monitor
    }
    return err
}

Prevention

When it happens

Trigger: mux.ClientWorker.Dispatch rejecting the control stream at construction time — worker already closed, its connection capacity exhausted, or internal mux state refusing new substreams before the worker finishes initializing.

Common situations: Race where the carrier connection to the portal dies between accept and control-dispatch; mux capacity edge conditions under load; version mismatches between bridge and portal cores. Generally transient, since bridges reconnect periodically.

Related errors


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