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.
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)
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
- newTunFromFd not supported in Windows
- can not load the wintun driver: %w
- generate GUID failed: %w
- create TUN device failed: %w
- no inside interface (tun)
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/69494f8f1c319737.
Report an issue: GitHub.