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
- This is usually benign — the manager logs 'Handshake too old' and keeps the existing connection; no action needed
- If it recurs persistently, check for clock skew between hosts (lastHandshakeTime comparison) and symmetric initiation loops (both sides dialing each other)
- 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
- NTP-sync all hosts to avoid lastHandshakeTime skew
- Avoid symmetric initiation (both peers dialing simultaneously)
- Don't treat this error as fatal; the existing connection is preserved
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
- ErrLocalIndexCollision
- relay hostinfo is no longer in the hostmap
- ErrNoPeerStaticKey
- ErrNoPayload
- ErrInitiateOnResponder
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/2417679459880a91.
Report an issue: GitHub.