slackhq/nebula · critical
Send ring corrupt
Error message
Send ring corrupt
What it means
NativeTun.Read reads packets from the wintun driver via a shared ring buffer. When the Windows overlapped read completes with ERROR_INVALID_DATA, the driver/signaled state indicates the send ring's internal pointers are inconsistent, and nebula returns the sentinel "Send ring corrupt". The TUN device's ring is unusable and traffic through it stops.
Solutions
- Restart the nebula process (or the network interface) to recreate the wintun session and ring buffers
- Update the wintun driver (wintun.dll) to the latest version matching your nebula build
- Reinstall/repair the wintun driver and reboot to clear stale driver state
- Avoid sleep/resume with active tunnels or restart tunnels on resume via a wrapper script
Example fix
// before
n, err := tun.Read(buf)
if err != nil { l.Error(err.Error()) } // "Send ring corrupt" loop
// after
n, err := tun.Read(buf)
if err != nil {
if err.Error() == "Send ring corrupt" {
l.Error("wintun send ring corrupt, recreating tunnel")
return recreateTunnel()
}
l.Error(err.Error())
} Defensive patterns
Strategy: fallback
Validate before calling
if err != nil && err.Error() == "Send ring corrupt" {
// recreate the tunnel/interface instead of retrying reads
} Try / catch
n, err := tun.Read(buf)
if err != nil {
if errors.Is(err, os.ErrClosed) { return }
if err.Error() == "Send ring corrupt" {
l.Error("wintun ring corrupt; restarting interface")
if rErr := restartTunnel(); rErr != nil { l.Error(rErr.Error()) }
return
}
l.Error(fmt.Sprintf("read failed: %v", err))
} Prevention
- Keep wintun.dll and the wintun driver up to date
- Restart tunnels on Windows sleep/resume events
- Watch for repeated ERROR_INVALID_DATA reads as an early signal
- Reboot after driver upgrades to clear stale ring state
When it happens
Trigger: wintun's ring-buffer offsets (head/tail) are out of bounds or mismatched — typically after driver hiccups, system sleep/resume, or memory corruption — surfacing as windows.ERROR_INVALID_DATA in Read's switch statement (wintun/tun.go:146).
Common situations: Windows laptops resuming from sleep while a nebula/wireguard-style TUN is active; wintun driver version mismatches after upgrades; heavy system memory pressure corrupting ring state.
Related errors
- can not load the wintun driver
- create TUN device failed
- generate GUID failed
- newTunFromFd not supported in Windows
- could not initialize winrio
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/69494f8f1c319737.
Report an issue: GitHub.
Appendix: source
Thrown at wintun/tun.go:146
packet, err := tun.session.ReceivePacket()
switch err {
case nil:
packetSize := len(packet)
copy(buff[offset:], packet)
tun.session.ReleaseReceivePacket(packet)
tun.rate.update(uint64(packetSize))
return packetSize, nil
case windows.ERROR_NO_MORE_ITEMS:
if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {
windows.WaitForSingleObject(tun.readWait, windows.INFINITE)
goto retry
}
procyield(1)
continue
case windows.ERROR_HANDLE_EOF:
return 0, os.ErrClosed
case windows.ERROR_INVALID_DATA:
return 0, errors.New("Send ring corrupt")
}
return 0, fmt.Errorf("Read failed: %w", err)
}
}
func (tun *NativeTun) Flush() error {
return nil
}
func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
tun.running.Add(1)
defer tun.running.Done()
if atomic.LoadInt32(&tun.close) == 1 {
return 0, os.ErrClosed
}
packetSize := len(buff) - offset
tun.rate.update(uint64(packetSize))View on GitHub (pinned to dd8f660c0a)