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
- If seen around shutdown/restart of the tun interface, treat as benign teardown race
- Verify the tun inbound's mtu matches the system interface (e.g. 1500/9000) and no offload mismatch
- Upgrade xray-core: WriteRawPacket error handling in the tun path has had fixes across versions
- 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
- Treat failures during interface teardown as races; drain UDP workers before Close
- Keep tun mtu in config equal to the real interface MTU and disable conflicting offloads
- Stay current with xray-core releases; gVisor tun internals change error semantics between versions
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
- XUDP rejected UDP/443 traffic
- packet size too large:
- connection for :: not found in
- unknown udp mode
- UdpIdleTimeout must be between 2 and 600
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/82fb8d597403308f.
Report an issue: GitHub.