slackhq/nebula · warning

noise ReadMessage: %w

Error message

noise ReadMessage: %w

What it means

ProcessPacket wraps an error from m.hs.ReadMessage (the Noise library) as 'noise ReadMessage: %w'. A Noise handshake message failed to decrypt/authenticate. The Noise library checkpoints state and rolls back on failure, so the Machine stays alive and the caller may retry with a different packet.

Source

Thrown at handshake/machine.go:233

	if header.MessageSubType(packet[1]) != m.subtype {
		return nil, nil, ErrSubtypeMismatch
	}
	if m.result.Initiator && m.hs.MessageIndex() == 0 {
		m.failed = true
		return nil, nil, ErrInitiateNotCalled
	}

	// The (eKey, dKey) ordering here is correct for IX, where the initiator
	// completes the handshake by reading the responder's stage-2 message.
	// noise returns (cs1, cs2) where cs1 is the initiator->responder cipher.
	// For 3-message patterns where a responder finishes by reading the final
	// message, this ordering would be wrong; revisit when XX/pqIX lands.
	msg, eKey, dKey, err := m.hs.ReadMessage(nil, packet[header.Len:])
	if err != nil {
		// Noise ReadMessage failed. The noise library checkpoints and rolls back
		// on failure, so the Machine is still alive. The caller can retry with
		// a different packet.
		return nil, nil, fmt.Errorf("noise ReadMessage: %w", err)
	}

	// From here on, noise state has advanced. Any error is fatal.
	flags := m.peerMsgFlags()

	if err := m.processPayload(msg, flags); err != nil {
		return nil, nil, err
	}

	// If ReadMessage derived keys, the handshake is complete. Noise should
	// always produce both keys together; asymmetry is a protocol invariant
	// violation.
	if eKey != nil || dKey != nil {
		if eKey == nil || dKey == nil {
			m.failed = true
			return nil, nil, ErrAsymmetricCipherKeys
		}
		if err := m.requireComplete(); err != nil {

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Retransmit/resend the handshake stage — the machine rolled back and can accept a fresh packet
  2. Check for UDP packet loss/fragmentation between peers (MTU tuning, keep handshake packets small)
  3. Clear stale hostmap entries for the peer so a fresh handshake starts
  4. Verify no middlebox is rewriting/duplicating UDP traffic on the Nebula port
Defensive patterns

Strategy: retry

Validate before calling

// optionally validate packet size and header before processing
if len(packet) <= header.Len {
	return fmt.Errorf("packet too short for handshake header")
}

Try / catch

msg, _, _, err := machine.ProcessPacket(pkt)
if err != nil && strings.Contains(err.Error(), "noise ReadMessage:") {
	// state rolled back; safe to retry with a fresh/retransmitted packet
	return retryHandshakeStage(pkt)
}

Prevention

When it happens

Trigger: ProcessPacket receives a packet whose handshake message body does not decrypt under the current handshake state — reordered/duplicated packets during the handshake, a packet from a different peer session, or a truncated/corrupted UDP datagram, while m.msgs[m.i] expects a message at that index.

Common situations: UDP packet loss causing out-of-order delivery during handshake, NAT rebinding mixing sessions, MTU fragmentation corrupting large handshake messages, MITM or wrong lighthouse routing delivering another peer's packet.

Related errors


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