change: drop untargeted pings when collapsing to a full

The ping ID in the URL is the only authentication on the answer, so
rescuing an untargeted ping for every node would let any node answer
for another. Rescue a ping only for the node it targets.
This commit is contained in:
Kristoffer Dalby authored and Kristoffer Dalby committed 2026-10-07 15:25:25 +02:00
1 parent d4948da301
commit 85587e2e29
2 files changed
+8 -6

No files matched your search

+4 -3
View File
@@ -232,14 +232,15 @@ func HasFull(rs []Change) bool {
// CollapseToFull returns what nodeID receives when a full update supersedes // CollapseToFull returns what nodeID receives when a full update supersedes
// changes: one [FullUpdate], then a ping-only [PingNode] for every // changes: one [FullUpdate], then a ping-only [PingNode] for every
// [Change.PingRequest] addressed to nodeID, in order. A full renders state at // [Change.PingRequest] targeted at nodeID, in order. A full renders state at
// drain time, so it covers every state change; a ping is a one-shot command // drain time, so it covers every state change; a ping is a one-shot command
// the full cannot carry. // the full cannot carry. An untargeted ping is dropped: the ID in its URL is
// all that authenticates the answer, so any node could answer for another.
func CollapseToFull(nodeID types.NodeID, changes []Change) []Change { func CollapseToFull(nodeID types.NodeID, changes []Change) []Change {
out := []Change{FullUpdate()} out := []Change{FullUpdate()}
for _, c := range changes { for _, c := range changes {
if c.PingRequest != nil && c.ShouldSendToNode(nodeID) { if c.PingRequest != nil && c.TargetNode == nodeID {
out = append(out, PingNode(nodeID, c.PingRequest)) out = append(out, PingNode(nodeID, c.PingRequest))
} }
} }
+4 -3
View File
@@ -67,7 +67,8 @@ func TestChange_FullUpdateSubsumesAllButPing(t *testing.T) {
isCarried, ok := carried[field.Name] isCarried, ok := carried[field.Name]
require.True(t, ok, "field %s is not classified", field.Name) require.True(t, ok, "field %s is not classified", field.Name)
var c Change // Addressed to self, so only the field under test decides.
c := Change{TargetNode: self}
v := reflect.ValueOf(&c).Elem().FieldByIndex(field.Index) v := reflect.ValueOf(&c).Elem().FieldByIndex(field.Index)
setNonZero(t, v, self) setNonZero(t, v, self)
@@ -701,9 +702,9 @@ func TestCollapseToFullKeepsPings(t *testing.T) {
want: []Change{FullUpdate(), PingNode(self, prA)}, want: []Change{FullUpdate(), PingNode(self, prA)},
}, },
{ {
name: "untargeted ping is addressed to every node", name: "untargeted ping is dropped",
changes: []Change{{Reason: "broadcast ping", PingRequest: prA}}, changes: []Change{{Reason: "broadcast ping", PingRequest: prA}},
want: []Change{FullUpdate(), PingNode(self, prA)}, want: []Change{FullUpdate()},
}, },
{ {
name: "fulls never stack", name: "fulls never stack",