state: compare peer-visible Hostinfo through views

Field by field: no allocation, no reflection.

Updates #3531
This commit is contained in:
Kristoffer Dalby authored and Kristoffer Dalby committed 2026-10-09 11:44:37 +02:00
1 parent 9c764e5a99
commit 49b848a83d
3 files changed
+100 -24

No files matched your search

+47 -20
View File
@@ -10,6 +10,7 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"tailscale.com/tailcfg"
"tailscale.com/types/views"
)
// mapRequestDelta carries the classified facts extracted from one MapRequest
@@ -154,30 +155,56 @@ func hostinfoEqual(oldHI, newHI *tailcfg.Hostinfo) bool {
return oldCopy.Equal(&newCopy)
}
// peerHostinfo keeps the Hostinfo fields another node's client reads from
// a peer: what tailscale status shows, PeerAPI services, SSH known hosts,
// exit-node location, app-connector eligibility, and the routes this server
// feeds into policy. Everything else is stored but never fanned out, and a
// peer's NetInfo is never read at all.
func peerHostinfo(hi *tailcfg.Hostinfo) *tailcfg.Hostinfo {
if hi == nil {
return nil
// peerHostinfoEqual reports whether a and b agree on the Hostinfo fields
// another node's client reads from a peer: what tailscale status shows, PeerAPI
// services, SSH known hosts, exit-node location, app-connector eligibility, and
// the routes this server feeds into policy. Everything else is stored but never
// fanned out, and a peer's NetInfo is never read at all.
func peerHostinfoEqual(a, b tailcfg.HostinfoView) bool {
if !a.Valid() || !b.Valid() {
return a.Valid() == b.Valid()
}
return &tailcfg.Hostinfo{
Hostname: hi.Hostname,
OS: hi.OS,
Services: hi.Services,
SSH_HostKeys: hi.SSH_HostKeys,
Location: hi.Location,
AppConnector: hi.AppConnector,
RoutableIPs: hi.RoutableIPs,
}
return a.Hostname() == b.Hostname() &&
a.OS() == b.OS() &&
servicesEqual(a.Services(), b.Services()) &&
views.SliceEqual(a.SSH_HostKeys(), b.SSH_HostKeys()) &&
locationEqual(a.Location(), b.Location()) &&
a.AppConnector() == b.AppConnector() &&
views.SliceEqual(a.RoutableIPs(), b.RoutableIPs())
}
// peerHostinfoEqual reports whether the fields peers read are unchanged.
func peerHostinfoEqual(oldHI, newHI *tailcfg.Hostinfo) bool {
return peerHostinfo(oldHI).Equal(peerHostinfo(newHI))
// servicesEqual compares field by field, as [tailcfg.Service] is
// incomparable.
func servicesEqual(a, b views.Slice[tailcfg.Service]) bool {
if a.Len() != b.Len() {
return false
}
for i := range a.Len() {
x, y := a.At(i), b.At(i)
if x.Proto != y.Proto || x.Port != y.Port || x.Description != y.Description {
return false
}
}
return true
}
// locationEqual compares field by field, as [tailcfg.LocationView] has no
// Equal.
func locationEqual(a, b tailcfg.LocationView) bool {
if !a.Valid() || !b.Valid() {
return a.Valid() == b.Valid()
}
return a.Country() == b.Country() &&
a.CountryCode() == b.CountryCode() &&
a.City() == b.City() &&
a.CityCode() == b.CityCode() &&
a.Latitude() == b.Latitude() &&
a.Longitude() == b.Longitude() &&
a.Priority() == b.Priority()
}
// netInfoEqualIgnoringDERP compares two NetInfo values via