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
- Retransmit/resend the handshake stage — the machine rolled back and can accept a fresh packet
- Check for UDP packet loss/fragmentation between peers (MTU tuning, keep handshake packets small)
- Clear stale hostmap entries for the peer so a fresh handshake starts
- 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
- Ensure handshake retransmission timers are enabled (handshakes are retried automatically)
- Keep handshake packets under MTU to avoid fragmentation loss
- Avoid multiple Nebula processes sharing one UDP port with different sessions
- Monitor UDP loss/reordering between sites; tune network or use a reliable underlay for lossy links
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.