diff --git a/hscontrol/auth.go b/hscontrol/auth.go index 1aa40c7b..6f5e5938 100644 --- a/hscontrol/auth.go +++ b/hscontrol/auth.go @@ -73,7 +73,10 @@ func (h *Headscale) handleRegister( // When tailscaled restarts, it sends RegisterRequest with Auth=nil and Expiry=zero. // Return the current node state without modification. // See: https://github.com/juanfont/headscale/issues/2862 - if req.Expiry.IsZero() && node.Expiry().Valid() && !node.IsExpired() { + // Note: node.Expiry().Valid() was too strict — tagged nodes have nil expiry, + // which caused zero-time requests to fall through to handleLogout and + // incorrectly set their expiry to 0001-01-01 (Go zero time). + if req.Expiry.IsZero() && !node.IsExpired() { return nodeToRegisterResponse(node), nil } diff --git a/hscontrol/types/node.go b/hscontrol/types/node.go index c699d6df..ece2d47c 100644 --- a/hscontrol/types/node.go +++ b/hscontrol/types/node.go @@ -1097,9 +1097,14 @@ func (nv NodeView) TailNode( derp = nv.Hostinfo().NetInfo().PreferredDERP() } - var keyExpiry time.Time - if nv.Expiry().Valid() { - keyExpiry = nv.Expiry().Get() + // Default to far-future expiry for nodes without an explicit expiry + // (e.g. tagged nodes). A zero time.Time{} causes Tailscale clients to + // spin-loop on the netmap expiry timer (interpreted as math.MinInt64). + // Max safe time for int64 nanoseconds: 2262-04-11T23:47:16.854775807Z. + // We use 2262-01-01 (~4 month buffer). Year 9999 overflows. + keyExpiry := time.Date(2262, 1, 1, 0, 0, 0, 0, time.UTC) + if nv.Expiry().Valid() && !nv.Expiry().Get().IsZero() { + keyExpiry = nv.Expiry().Get() } primaryRoutes := primaryRouteFunc(nv.ID())