ipn/ipnlocal: protect populatePeerStatusLocked from nil Hostinfo (#20150)

ipnlocal.LocalBackend.populatePeerStatusLocked assumed that Hostinfo
was always valid, but that’s not always true, especially in tests.
ipnlocal.peerAPIPorts suffered from a similar assumption.

This patch checks for NodeView.Valid and Hostinfo.Valid; assuming the
zero value as a safe default.

Updates #8948
Updates #12542

Signed-off-by: Simon Law <sfllaw@tailscale.com>
This commit is contained in:
Simon Law
2026-06-15 13:14:12 -07:00
committed by GitHub
parent 6596d237a3
commit eddd019ee4

View File

@@ -1611,6 +1611,7 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
sb.AddUser(id, up)
}
exitNodeID := b.pm.CurrentPrefs().ExitNodeID()
blankHostinfo := new(tailcfg.Hostinfo).View()
for _, p := range cn.Peers() {
tailscaleIPs := make([]netip.Addr, 0, p.Addresses().Len())
for i := range p.Addresses().Len() {
@@ -1619,20 +1620,24 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
tailscaleIPs = append(tailscaleIPs, addr.Addr())
}
}
hostinfo := p.Hostinfo()
if !hostinfo.Valid() {
hostinfo = blankHostinfo
}
ps := &ipnstate.PeerStatus{
InNetworkMap: true,
UserID: p.User(),
AltSharerUserID: p.Sharer(),
TailscaleIPs: tailscaleIPs,
HostName: p.Hostinfo().Hostname(),
HostName: hostinfo.Hostname(),
DNSName: p.Name(),
OS: p.Hostinfo().OS(),
OS: hostinfo.OS(),
LastSeen: p.LastSeen().Get(),
Online: p.Online().Get(),
ShareeNode: p.Hostinfo().ShareeNode(),
ShareeNode: hostinfo.ShareeNode(),
ExitNode: p.StableID() != "" && p.StableID() == exitNodeID,
SSH_HostKeys: p.Hostinfo().SSH_HostKeys().AsSlice(),
Location: p.Hostinfo().Location().AsStruct(),
SSH_HostKeys: hostinfo.SSH_HostKeys().AsSlice(),
Location: hostinfo.Location().AsStruct(),
Capabilities: p.Capabilities().AsSlice(),
}
for _, f := range b.extHost.Hooks().SetPeerStatus {
@@ -7654,6 +7659,9 @@ func (b *LocalBackend) SetDNS(ctx context.Context, name, value string) error {
}
func peerAPIPorts(peer tailcfg.NodeView) (p4, p6 uint16) {
if !peer.Valid() || !peer.Hostinfo().Valid() {
return
}
svcs := peer.Hostinfo().Services()
for _, s := range svcs.All() {
switch s.Proto {