slackhq/nebula · info

ErrExistingHostInfo

ErrExistingHostInfo

Error message

existing hostinfo

What it means

ErrExistingHostInfo is returned by CheckAndComplete when the main hostmap already contains a hostinfo entry for the same VPN address, and the existing entry is at least as new (its lastHandshakeTime is greater-or-equal and it is not the initiator). The completed handshake is redundant/stale, so the new hostinfo is rejected in favor of the existing one.

Source

Thrown at handshake_manager.go:414

	if !doTrigger {
		// Add any calculated remotes, and trigger early handshake if one found
		doTrigger = hm.lightHouse.addCalculatedRemotes(vpnAddr)
	}

	if doTrigger {
		select {
		case hm.trigger <- vpnAddr:
		default:
		}
	}

	hm.Unlock()
	hm.lightHouse.QueryServer(vpnAddr)
	return hostinfo
}

var (
	ErrExistingHostInfo    = errors.New("existing hostinfo")
	ErrAlreadySeen         = errors.New("already seen")
	ErrLocalIndexCollision = errors.New("local index collision")
)

// CheckAndComplete checks for any conflicts in the main and pending hostmap
// before adding hostinfo to main. If err is nil, it was added. Otherwise err will be:
//
// ErrAlreadySeen if we already have an entry in the hostmap that has seen the
// exact same handshake packet
//
// ErrExistingHostInfo if we already have an entry in the hostmap for this
// VpnIp and the new handshake was older than the one we currently have
//
// ErrLocalIndexCollision if we already have an entry in the main or pending
// hostmap for the hostinfo.localIndexId.
func (hm *HandshakeManager) CheckAndComplete(hostinfo *HostInfo, handshakePacket uint8, f *Interface) (*HostInfo, error) {
	hm.mainHostMap.Lock()
	defer hm.mainHostMap.Unlock()

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. This is usually benign — the manager logs 'Handshake too old' and keeps the existing connection; no action needed
  2. If it recurs persistently, check for clock skew between hosts (lastHandshakeTime comparison) and symmetric initiation loops (both sides dialing each other)
  3. Ensure lighthouse/roaming config isn't causing both sides to continuously re-initiate
Defensive patterns

Strategy: type-guard

Type guard

func isStaleHandshake(existing, incoming *HostInfo) bool {
    return existing != nil &&
        existing.lastHandshakeTime >= incoming.lastHandshakeTime &&
        !existing.ConnectionState.initiator
}

Try / catch

hi, err := hm.CheckAndComplete(hostinfo)
if errors.Is(err, ErrExistingHostInfo) {
    // benign: existing newer connection kept; log at info and move on
    return hi, nil
}

Prevention

When it happens

Trigger: Completing a handshake for a host whose existing main-hostmap entry has lastHandshakeTime >= the new hostinfo's and the existing entry is not the initiator (handshake_manager.go:448) — i.e. a stale or duplicated handshake completion raced with a newer established connection.

Common situations: Both peers initiating handshakes to each other simultaneously (race); retransmitted/late handshake responses arriving after a newer handshake completed; flapping networks causing rapid re-handshakes.

Related errors


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