package link import ( "context" "errors" "testing" ) // Supersession as a check rather than a memory (design 25 §3). // // Holding a message in memory let the controller drop an older report when a newer one for the // same node arrived. On the bus being built the message belongs to the server and comes back // whatever happened meanwhile — so the older report is redelivered *after* the newer was applied, // and acting on it would undo the newer. // // The answer was already in the message: a report carries the digest of the declaration it is // about, so "is this the past?" is a question the message answers. // sentAndHeard records reports and knows what was last sent, which is the pair the check needs. type sentAndHeard struct { sent string heard []Report err error } func (s *sentAndHeard) Heard(_ context.Context, r Report) (bool, error) { if s.err != nil { return false, s.err } s.heard = append(s.heard, r) return true, nil } func (s *sentAndHeard) Outstanding(context.Context, string) (string, error) { return s.sent, nil } // A report about a declaration the mesh has moved past is settled and not acted on. Settled rather // than dropped, because there is nothing wrong with the message — it is simply the past, and // redelivering it for ever is worse than letting it go. func TestAReportAboutASupersededDeclarationIsNotActedOn(t *testing.T) { store := &sentAndHeard{sent: "d2"} s, in := serving() s.listener = store to := &settled{} s.act(context.Background(), in.sends(t, to, KindReport, aReport("anchor", "d1"))) if len(store.heard) != 0 { t.Fatalf("a report about a superseded declaration was acted on: %+v", store.heard) } if !to.acked { t.Fatalf("a superseded report was not settled, so it comes back for ever: %+v", *to) } } // The report about the declaration that *is* outstanding is acted on, and so is one from a node // the mesh has no digest for — an older host that says nothing about which declaration it applied // has nothing to be judged against, and refusing it would silence every node built before reports // carried the digest. func TestAReportAboutTheOutstandingDeclarationIsActedOn(t *testing.T) { for _, c := range []struct{ what, sent, declared string }{ {"the one outstanding", "d2", "d2"}, {"a report that says nothing about which", "d2", ""}, {"a node nothing was ever sent", "", "d1"}, } { store := &sentAndHeard{sent: c.sent} s, in := serving() s.listener = store to := &settled{} s.act(context.Background(), in.sends(t, to, KindReport, aReport("anchor", c.declared))) if len(store.heard) != 1 || !to.acked { t.Errorf("%s: was not acted on and acknowledged: heard %+v, settled %+v", c.what, store.heard, *to) } } } // **Staleness is decided before the store is waited on**, not after: a redelivery that lost its // race is not worth holding a slot in the window that a current message needs. func TestASupersededReportIsNotHeldForTheStore(t *testing.T) { store := &sentAndHeard{sent: "d2", err: errors.Join(ErrTryAgain, errors.New("starting up"))} s, in := serving() s.listener = store to := &settled{} s.act(context.Background(), in.sends(t, to, KindReport, aReport("anchor", "d1"))) if !to.acked || len(in.held) != 0 { t.Fatalf("a superseded report waited for the store: %+v, %d held", *to, len(in.held)) } } // **Half of a report is not about a declaration, and that half is never stale.** // // What the machine *is* — the tunnel it took over, the ports its own bundle holds, what an adopted // node found and is keeping, a node moving its overlay key — reaches the mesh on a report and // nowhere else. A rekey set aside as stale is a node whose overlay key never moves, and no retry is // coming, because the node said it once. So a report carrying any of these is acted on whenever it // arrives, however far the mesh has moved on. func TestAReportCarryingWhatOnlyTheNodeKnowsIsActedOnHoweverOldItIs(t *testing.T) { for _, c := range []struct { what string report Report }{ {"a rekey", Report{Node: "anchor", Declared: "d1", Rekey: &Rekey{Previous: "k1", OverlayKey: "k2"}}}, {"the tunnel it carried", Report{Node: "anchor", Declared: "d1", Tunnel: &CarriedTunnel{Interface: "wg0", State: "taken"}}}, {"what an adopted node holds", Report{Node: "anchor", Declared: "d1", Held: []Held{{ID: "conf", Module: "web", Kind: "file"}}}}, {"the firewall it found", Report{Node: "anchor", Declared: "d1", Firewall: "ufw"}}, {"what is reachable on it", Report{Node: "anchor", Declared: "d1", Reachable: []Reach{{Protocol: "tcp", Port: 443}}}}, {"the ports its own bundle holds", Report{Node: "anchor", Declared: "d1", Carried: []int{5432}}}, } { store := &sentAndHeard{sent: "d9"} s, in := serving() s.listener = store to := &settled{} s.act(context.Background(), in.sends(t, to, KindReport, c.report)) if len(store.heard) != 1 { t.Errorf("%s was set aside as stale, and the mesh will never hear it again: %+v", c.what, *to) } } } // A heartbeat is not held for the store: the next one is a minute away, and one kept for two // minutes to be written late says nothing the one after it will not say better. func TestAHeartbeatIsNotHeldForTheStore(t *testing.T) { s, in := serving() s.listener = heardWith{err: errors.Join(ErrTryAgain, errors.New("starting up"))} to := &settled{} s.act(context.Background(), in.sends(t, to, KindHeartbeat, Alive{Node: "anchor"})) if !to.acked || len(in.held) != 0 { t.Fatalf("a heartbeat was held for the store: %+v, %d held", *to, len(in.held)) } }