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

  1. Restart the nebula process (or the network interface) to recreate the wintun session and ring buffers
  2. Update the wintun driver (wintun.dll) to the latest version matching your nebula build
  3. Reinstall/repair the wintun driver and reboot to clear stale driver state
  4. 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

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


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/69494f8f1c319737. Report an issue: GitHub.