XTLS/Xray-core · error

failed to write raw udp packet back to stack

Error message

failed to write raw udp packet back to stack

What it means

The gVisor netstack rejected WriteRawPacket when the tun handler tried to inject a synthesized UDP/IP packet back toward the client (defaultNIC). The packet was rebuilt with a fresh IPv4/IPv6 header (checksum recomputed, hop limit 64) and pushed into the stack; rejection means the stack considered the NIC invalid or the packet malformed.

Source

Thrown at proxy/tun/stack_gvisor.go:187

			SrcAddr:     srcIP,
			DstAddr:     dstIP,
		})
		ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
	} else {
		ipHdr := header.IPv6(pkt.NetworkHeader().Push(header.IPv6MinimumSize))
		ipHdr.Encode(&header.IPv6Fields{
			PayloadLength:     uint16(udpLen),
			TransportProtocol: header.UDPProtocolNumber,
			HopLimit:          64,
			SrcAddr:           srcIP,
			DstAddr:           dstIP,
		})
	}

	// dispatch the packet
	err := t.stack.WriteRawPacket(defaultNIC, ipProtocol, buffer.MakeWithView(pkt.ToView()))
	if err != nil {
		return errors.New("failed to write raw udp packet back to stack", err)
	}

	return nil
}

// Close is called by Handler to shut down the stack
func (t *stackGVisor) Close() error {
	if t.stack == nil {
		return nil
	}
	t.endpoint.Attach(nil)
	t.stack.Close()
	for _, endpoint := range t.stack.CleanupEndpoints() {
		endpoint.Abort()
	}

	return nil
}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. If seen around shutdown/restart of the tun interface, treat as benign teardown race
  2. Verify the tun inbound's mtu matches the system interface (e.g. 1500/9000) and no offload mismatch
  3. Upgrade xray-core: WriteRawPacket error handling in the tun path has had fixes across versions
  4. Capture on the tun device to confirm outbound replies otherwise flow normally

Example fix

// json: align mtu with the created interface
"inbounds": [{
  "protocol": "tun",
  "settings": {"mtu": 9000, "address": ["172.19.0.1/30", "fdfe:dcba:9876::1/126"]}
}]
Defensive patterns

Strategy: try-catch

Validate before calling

// check NIC liveness before injecting the reply packet
if t.stack == nil {
    return errors.New("stack closed")
}
// optionally guard with the endpoint's attached state if exposed by your gVisor version

Try / catch

if err := t.stack.WriteRawPacket(defaultNIC, ipProtocol, buffer.MakeWithView(pkt.ToView())); err != nil {
    // reply raced stack shutdown or MTU bounds: drop the packet, do not crash the handler
    return errors.New("failed to write raw udp packet back to stack", err)
}

Prevention

When it happens

Trigger: The reply path after a tun-dispatched UDP flow: stack.WriteRawPacket returns non-nil because the NIC/endpoint was detached (Close racing an in-flight reply), the packet buffer exceeded NIC MTU, or the stack was already shutting down.

Common situations: Sessions torn down while DNS/UDP replies are still arriving (shutdown or interface flap), MTU mismatch between tun mtu config and packets produced, or gVisor version behavior changes on WriteRawPacket error semantics.

Related errors


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