slackhq/nebula · error

tunnel message counter is exhausted

Error message

tunnel message counter is exhausted

What it means

The relay tunnel's Noise message counter (NextMessageCounter) returned !ok, meaning the 64-bit sequence space or window is exhausted, so no further encrypted relay messages can be sent on that tunnel. The packet is dropped and the error surfaces to the caller.

Source

Thrown at inside.go:459

func (f *Interface) prepareSendVia(via *HostInfo,
	relay *Relay,
	ad,
	nb,
	out []byte,
	nocopy bool,
) ([]byte, error) {
	if noiseutil.EncryptLockNeeded {
		// NOTE: for goboring AESGCMTLS we need to lock because of the nonce check
		via.ConnectionState.writeLock.Lock()
	}
	c, ok := via.ConnectionState.NextMessageCounter()
	if !ok {
		if noiseutil.EncryptLockNeeded {
			via.ConnectionState.writeLock.Unlock()
		}
		f.dropExhausted(via, c, "Dropping outbound relay packets, tunnel message counter is exhausted")
		return nil, fmt.Errorf("tunnel message counter is exhausted")
	}

	out = header.Encode(out, header.Version, header.Message, header.MessageRelay, relay.RemoteIndex, c)
	f.connectionManager.OutNoRebind(via)

	// Authenticate the header and payload, but do not encrypt for this message type.
	// The payload consists of the inner, unencrypted Nebula header, as well as the end-to-end encrypted payload.
	if len(out)+len(ad)+via.ConnectionState.eKey.Overhead() > cap(out) {
		if noiseutil.EncryptLockNeeded {
			via.ConnectionState.writeLock.Unlock()
		}
		via.logger(f.l).Error("SendVia out buffer not large enough for relay",
			"outCap", cap(out),
			"payloadLen", len(ad),
			"headerLen", len(out),
			"cipherOverhead", via.ConnectionState.eKey.Overhead(),
		)
		return nil, io.ErrShortBuffer

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Re-handshake the tunnel to create a fresh ConnectionState and reset the counter
  2. Reconnect the affected tunnel (let nebula renegotiate keys via boring-cryptostream or restart)
  3. Investigate why the counter is exhausted (should not happen at normal rates — indicates a stale/long-lived tunnel)
  4. Upgrade nebula if your version lacks automatic key rotation
Defensive patterns

Strategy: fallback

Try / catch

out, err := f.SendVia(via, relay, ...)
if err != nil && errors.Is(err, errCounterExhausted) {
    // trigger re-handshake / tunnel renegotiation, then retry
    f.connectionManager.RequestRebind(via)
}

Prevention

When it happens

Trigger: Calling SendVia / anonymous senders via prepareSendVia after the via connection's message counter has been incremented past its maximum — i.e. an extremely long-lived, high-volume relay tunnel.

Common situations: Long-running tunnels that never re-handshake; a stuck/stale ConnectionState whose counter was not reset; encryption lock path (EncryptLockNeeded) leaving counter consumed repeatedly.

Related errors


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