state: tie-break ResolveNode by GivenName then lowest NodeID
Resolve by GivenName (unique per tailnet) before Hostname (client- reported, may collide); within each pass, pick the lowest NodeID so results are deterministic across NodeStore snapshot iterations. Updates #3157
This commit is contained in:
parent
2530d86f1b
commit
978f1e3947
@ -1,5 +1,12 @@
|
|||||||
// Package state provides core state management for Headscale, coordinating
|
// Package state provides core state management for Headscale,
|
||||||
// between subsystems like database, IP allocation, policy management, and DERP routing.
|
// coordinating between subsystems like database, IP allocation,
|
||||||
|
// policy management, and DERP routing.
|
||||||
|
//
|
||||||
|
// The central type State owns a copy-on-write NodeStore
|
||||||
|
// (node_store.go), a PrimaryRoutes HA ledger, the PolicyManager, and a
|
||||||
|
// pingTracker for PingRequest correlation. Cross-subsystem operations
|
||||||
|
// (node updates, policy evaluation, IP allocation) go through State
|
||||||
|
// rather than directly to the database.
|
||||||
|
|
||||||
package state
|
package state
|
||||||
|
|
||||||
@ -708,8 +715,11 @@ func (s *State) GetNodeByMachineKey(machineKey key.MachinePublic, userID types.U
|
|||||||
return s.nodeStore.GetNodeByMachineKey(machineKey, userID)
|
return s.nodeStore.GetNodeByMachineKey(machineKey, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveNode looks up a node by numeric ID, IPv4/IPv6 address, hostname, or given name.
|
// ResolveNode looks up a node by numeric ID, IPv4/IPv6 address, given
|
||||||
// It tries ID first, then IP, then name matching.
|
// name, or hostname. It tries ID first, then IP, then GivenName
|
||||||
|
// (unique per tailnet), then Hostname (client-reported, may collide).
|
||||||
|
// Within the name passes, the lowest NodeID wins so repeated calls
|
||||||
|
// are deterministic across snapshot iterations.
|
||||||
func (s *State) ResolveNode(query string) (types.NodeView, bool) {
|
func (s *State) ResolveNode(query string) (types.NodeView, bool) {
|
||||||
// Try numeric ID first.
|
// Try numeric ID first.
|
||||||
id, idErr := types.ParseNodeID(query)
|
id, idErr := types.ParseNodeID(query)
|
||||||
@ -720,23 +730,42 @@ func (s *State) ResolveNode(query string) (types.NodeView, bool) {
|
|||||||
// Try IP address.
|
// Try IP address.
|
||||||
addr, addrErr := netip.ParseAddr(query)
|
addr, addrErr := netip.ParseAddr(query)
|
||||||
if addrErr == nil {
|
if addrErr == nil {
|
||||||
|
var match types.NodeView
|
||||||
|
|
||||||
for _, n := range s.ListNodes().All() {
|
for _, n := range s.ListNodes().All() {
|
||||||
if slices.Contains(n.IPs(), addr) {
|
if !slices.Contains(n.IPs(), addr) {
|
||||||
return n, true
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !match.Valid() || n.ID() < match.ID() {
|
||||||
|
match = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.NodeView{}, false
|
return match, match.Valid()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try hostname / given name.
|
// Try GivenName then Hostname, each with a stable tie-break on
|
||||||
|
// lowest NodeID.
|
||||||
|
var givenMatch, hostMatch types.NodeView
|
||||||
|
|
||||||
for _, n := range s.ListNodes().All() {
|
for _, n := range s.ListNodes().All() {
|
||||||
if n.Hostname() == query || n.GivenName() == query {
|
if n.GivenName() == query {
|
||||||
return n, true
|
if !givenMatch.Valid() || n.ID() < givenMatch.ID() {
|
||||||
|
givenMatch = n
|
||||||
|
}
|
||||||
|
} else if n.Hostname() == query {
|
||||||
|
if !hostMatch.Valid() || n.ID() < hostMatch.ID() {
|
||||||
|
hostMatch = n
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.NodeView{}, false
|
if givenMatch.Valid() {
|
||||||
|
return givenMatch, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return hostMatch, hostMatch.Valid()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListNodes retrieves specific nodes by ID, or all nodes if no IDs provided.
|
// ListNodes retrieves specific nodes by ID, or all nodes if no IDs provided.
|
||||||
@ -2404,7 +2433,12 @@ func (s *State) autoApproveNodes() ([]change.Change, error) {
|
|||||||
return cs, nil
|
return cs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateNodeFromMapRequest processes a MapRequest and updates the node.
|
// UpdateNodeFromMapRequest is the sync point where Hostinfo changes,
|
||||||
|
// endpoint updates, and route advertisements from a MapRequest land in
|
||||||
|
// the NodeStore. It produces a change.Change summarising what actually
|
||||||
|
// moved so downstream subsystems (mapper, policy, primary routes) can
|
||||||
|
// react accordingly.
|
||||||
|
//
|
||||||
// TODO(kradalby): This is essentially a patch update that could be sent directly to nodes,
|
// TODO(kradalby): This is essentially a patch update that could be sent directly to nodes,
|
||||||
// which means we could shortcut the whole change thing if there are no other important updates.
|
// which means we could shortcut the whole change thing if there are no other important updates.
|
||||||
// When a field is added to this function, remember to also add it to:
|
// When a field is added to this function, remember to also add it to:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user