hscontrol: fix tag updates not propagating to node self view

When SetNodeTags changed a node's tags, the node's self view wasn't
updated. The bug manifested as: the first SetNodeTags call updates
the server but the client's self view doesn't update until a second
call with the same tag.

Root cause: Three issues combined to prevent self-updates:

1. SetNodeTags returned PolicyChange which doesn't set OriginNode,
   so the mapper's self-update check failed.

2. The Change.Merge function didn't preserve OriginNode, so when
   changes were batched together, OriginNode was lost.

3. generateMapResponse checked OriginNode only in buildFromChange(),
   but PolicyChange uses RequiresRuntimePeerComputation which
   bypasses that code path entirely and calls policyChangeResponse()
   instead.

The fix addresses all three:
- state.go: Set OriginNode on the returned change
- change.go: Preserve OriginNode (and TargetNode) during merge
- batcher.go: Pass isSelfUpdate to policyChangeResponse so the
  origin node gets both self info AND packet filters
- mapper.go: Add includeSelf parameter to policyChangeResponse

Fixes #2978
This commit is contained in:
Kristoffer Dalby
2026-01-15 13:56:48 +00:00
parent 1b6db34b93
commit 3b4b9a4436
5 changed files with 74 additions and 2 deletions

View File

@@ -70,6 +70,18 @@ func (r Change) Merge(other Change) Change {
merged.PeersRemoved = uniqueNodeIDs(append(r.PeersRemoved, other.PeersRemoved...))
merged.PeerPatches = append(r.PeerPatches, other.PeerPatches...)
// Preserve OriginNode for self-update detection.
// If either change has OriginNode set, keep it so the mapper
// can detect self-updates and send the node its own changes.
if merged.OriginNode == 0 {
merged.OriginNode = other.OriginNode
}
// Preserve TargetNode for targeted responses.
if merged.TargetNode == 0 {
merged.TargetNode = other.TargetNode
}
if r.Reason != "" && other.Reason != "" && r.Reason != other.Reason {
merged.Reason = r.Reason + "; " + other.Reason
} else if other.Reason != "" {

View File

@@ -233,6 +233,36 @@ func TestChange_Merge(t *testing.T) {
r2: Change{Reason: "update"},
want: Change{Reason: "update"},
},
{
name: "OriginNode preserved from first",
r1: Change{OriginNode: 42},
r2: Change{IncludePolicy: true},
want: Change{OriginNode: 42, IncludePolicy: true},
},
{
name: "OriginNode preserved from second when first is zero",
r1: Change{IncludePolicy: true},
r2: Change{OriginNode: 42},
want: Change{OriginNode: 42, IncludePolicy: true},
},
{
name: "OriginNode first wins when both set",
r1: Change{OriginNode: 1},
r2: Change{OriginNode: 2},
want: Change{OriginNode: 1},
},
{
name: "TargetNode preserved from first",
r1: Change{TargetNode: 42},
r2: Change{IncludeSelf: true},
want: Change{TargetNode: 42, IncludeSelf: true},
},
{
name: "TargetNode preserved from second when first is zero",
r1: Change{IncludeSelf: true},
r2: Change{TargetNode: 42},
want: Change{TargetNode: 42, IncludeSelf: true},
},
}
for _, tt := range tests {