{"record":{"id":"c815d08dbf86bef3","repo":"slackhq/nebula","slug":"tunnel-message-counter-is-exhausted","errorCode":null,"errorMessage":"tunnel message counter is exhausted","messagePattern":"tunnel message counter is exhausted","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"inside.go","lineNumber":459,"sourceCode":"\nfunc (f *Interface) prepareSendVia(via *HostInfo,\n\trelay *Relay,\n\tad,\n\tnb,\n\tout []byte,\n\tnocopy bool,\n) ([]byte, error) {\n\tif noiseutil.EncryptLockNeeded {\n\t\t// NOTE: for goboring AESGCMTLS we need to lock because of the nonce check\n\t\tvia.ConnectionState.writeLock.Lock()\n\t}\n\tc, ok := via.ConnectionState.NextMessageCounter()\n\tif !ok {\n\t\tif noiseutil.EncryptLockNeeded {\n\t\t\tvia.ConnectionState.writeLock.Unlock()\n\t\t}\n\t\tf.dropExhausted(via, c, \"Dropping outbound relay packets, tunnel message counter is exhausted\")\n\t\treturn nil, fmt.Errorf(\"tunnel message counter is exhausted\")\n\t}\n\n\tout = header.Encode(out, header.Version, header.Message, header.MessageRelay, relay.RemoteIndex, c)\n\tf.connectionManager.OutNoRebind(via)\n\n\t// Authenticate the header and payload, but do not encrypt for this message type.\n\t// The payload consists of the inner, unencrypted Nebula header, as well as the end-to-end encrypted payload.\n\tif len(out)+len(ad)+via.ConnectionState.eKey.Overhead() > cap(out) {\n\t\tif noiseutil.EncryptLockNeeded {\n\t\t\tvia.ConnectionState.writeLock.Unlock()\n\t\t}\n\t\tvia.logger(f.l).Error(\"SendVia out buffer not large enough for relay\",\n\t\t\t\"outCap\", cap(out),\n\t\t\t\"payloadLen\", len(ad),\n\t\t\t\"headerLen\", len(out),\n\t\t\t\"cipherOverhead\", via.ConnectionState.eKey.Overhead(),\n\t\t)\n\t\treturn nil, io.ErrShortBuffer","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/inside.go#L441-L477","documentation":"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.","triggerScenarios":"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.","commonSituations":"Long-running tunnels that never re-handshake; a stuck/stale ConnectionState whose counter was not reset; encryption lock path (EncryptLockNeeded) leaving counter consumed repeatedly.","solutions":["Re-handshake the tunnel to create a fresh ConnectionState and reset the counter","Reconnect the affected tunnel (let nebula renegotiate keys via boring-cryptostream or restart)","Investigate why the counter is exhausted (should not happen at normal rates — indicates a stale/long-lived tunnel)","Upgrade nebula if your version lacks automatic key rotation"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"out, err := f.SendVia(via, relay, ...)\nif err != nil && errors.Is(err, errCounterExhausted) {\n    // trigger re-handshake / tunnel renegotiation, then retry\n    f.connectionManager.RequestRebind(via)\n}","preventionTips":["Monitor tunnel age and force periodic re-handshakes","Alert on 'message counter is exhausted' drops — they indicate stale long-lived tunnels","Keep nebula updated so key rotation resets counters"],"tags":["relay","tunnel","counter-exhaustion"],"backgroundTag":"message-counter-exhausted","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}