XTLS/Xray-core · error
connection closed
Error message
connection closed
What it means
The tun inbound handler logs this when dispatcher.DispatchLink fails for a connection extracted from the TUN/gVisor stack. 'connection closed' is the wrapper; the real cause (Base err) is the dispatch failure — invalid destination, routing block, or outbound dial error. The client conn is then closed by the deferred Close.
Source
Thrown at proxy/tun/handler.go:211
ctx = session.ContextWithContent(ctx, &session.Content{
SniffingRequest: t.sniffingRequest,
})
ctx = session.SubContextFromMuxInbound(ctx)
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: inbound.Source,
To: destination,
Status: log.AccessAccepted,
Reason: "",
})
errors.LogInfo(ctx, "processing from ", source, " to ", destination)
link := &transport.Link{
Reader: &buf.TimeoutWrapperReader{Reader: buf.NewReader(conn)},
Writer: buf.NewWriter(conn),
}
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
errors.LogError(ctx, errors.New("connection closed").Base(err))
}
}
// Close implements common.Closable.
func (t *Handler) Close() error {
return errors.Combine(common.CloseIfExists(t.stack), common.CloseIfExists(t.tun))
}
// Network implements proxy.Inbound
// and exists only to comply to proxy interface, declaring it doesn't listen on any network,
// making the process not open any port for this inbound (input will be network interface)
func (t *Handler) Network() []net.Network {
return []net.Network{}
}
// Process implements proxy.Inbound
// and exists only to comply to proxy interface, which should never get any inputs due to no listening ports
func (t *Handler) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {View on GitHub (pinned to 7d214f8b09)
Solutions
- Read the Base error in the same log line to get the dispatch failure reason
- Check routing rules for unintended matches (geoip:private, broadcast/multicast ranges) and route them to direct/block explicitly
- Verify the outbound referenced by the matched rule is alive and can resolve/dial
- Enable routing debug logs to see which rule matched the failing destination
Example fix
// json: keep local/multicast traffic off the proxy while using tun
"routing": {"rules": [
{"type": "field", "ip": ["geoip:private", "224.0.0.0/3", "ff00::/8"], "outboundTag": "direct"}
]} Defensive patterns
Strategy: try-catch
Validate before calling
// guard dispatch of tun connections
if !validDest(destination) {
errors.LogInfo(ctx, "dropping invalid destination ", destination)
return
} Try / catch
if err := t.dispatcher.DispatchLink(ctx, destination, link); err != nil {
// per-flow failure: log with cause and let deferred conn.Close() clean up
errors.LogError(ctx, errors.New("connection closed").Base(err))
} Prevention
- Route private/multicast/link-local ranges to direct or block explicitly before generic rules
- Validate outbounds at startup (tag existence, dialability) when running tun mode
- Monitor these per-flow logs at debug level; a flood indicates routing/outbound misconfiguration, not per-connection bugs
When it happens
Trigger: An app on the tun interface connects to a destination that routing blocks or an outbound cannot dial; DNS resolution inside dispatch fails; the destination derived from the packet is invalid (e.g. unroutable literal).
Common situations: tun-mode (TUN mode) with routing rules sending everything to a dead outbound; system apps probing link-local or multicast addresses that reach the tun stack; misconfigured sockopt/interface rules after enabling tun.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- XUDP new
- failed to dispatch request.
- failed to dispatch request
- failed to dispatch request to ${destination}
- invalid system route {cidr}
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/17538df60018649a.
Report an issue: GitHub.