Skip to content

Commit 262fa8a

Browse files
authored
ipn/ipnlocal: populate peers' capabilities (tailscale#11365)
Populates capabilties field of peers in ipn status. Updates tailscale/corp#17516 Signed-off-by: Claire Wang <claire@tailscale.com>
1 parent 9eaa56d commit 262fa8a

File tree

3 files changed

+108
-0
lines changed

3 files changed

+108
-0
lines changed

ipn/ipnlocal/local.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,14 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
894894
ExitNode: p.StableID() != "" && p.StableID() == exitNodeID,
895895
SSH_HostKeys: p.Hostinfo().SSH_HostKeys().AsSlice(),
896896
Location: p.Hostinfo().Location(),
897+
Capabilities: p.Capabilities().AsSlice(),
898+
}
899+
if cm := p.CapMap(); cm.Len() > 0 {
900+
ps.CapMap = make(tailcfg.NodeCapMap, cm.Len())
901+
cm.Range(func(k tailcfg.NodeCapability, v views.Slice[tailcfg.RawMessage]) bool {
902+
ps.CapMap[k] = v.AsSlice()
903+
return true
904+
})
897905
}
898906
peerStatusFromNode(ps, p)
899907

ipn/ipnlocal/local_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,100 @@ func TestStatusWithoutPeers(t *testing.T) {
736736
}
737737
}
738738

739+
func TestStatusPeerCapabilities(t *testing.T) {
740+
tests := []struct {
741+
name string
742+
peers []tailcfg.NodeView
743+
expectedPeerCapabilities map[tailcfg.StableNodeID][]tailcfg.NodeCapability
744+
expectedPeerCapMap map[tailcfg.StableNodeID]tailcfg.NodeCapMap
745+
}{
746+
{
747+
name: "peers-with-capabilities",
748+
peers: []tailcfg.NodeView{
749+
(&tailcfg.Node{
750+
ID: 1,
751+
StableID: "foo",
752+
IsWireGuardOnly: true,
753+
Hostinfo: (&tailcfg.Hostinfo{}).View(),
754+
Capabilities: []tailcfg.NodeCapability{tailcfg.CapabilitySSH},
755+
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
756+
tailcfg.CapabilitySSH: nil,
757+
}),
758+
}).View(),
759+
(&tailcfg.Node{
760+
ID: 2,
761+
StableID: "bar",
762+
Hostinfo: (&tailcfg.Hostinfo{}).View(),
763+
Capabilities: []tailcfg.NodeCapability{tailcfg.CapabilityAdmin},
764+
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
765+
tailcfg.CapabilityAdmin: {`{"test": "true}`},
766+
}),
767+
}).View(),
768+
},
769+
expectedPeerCapabilities: map[tailcfg.StableNodeID][]tailcfg.NodeCapability{
770+
tailcfg.StableNodeID("foo"): {tailcfg.CapabilitySSH},
771+
tailcfg.StableNodeID("bar"): {tailcfg.CapabilityAdmin},
772+
},
773+
expectedPeerCapMap: map[tailcfg.StableNodeID]tailcfg.NodeCapMap{
774+
tailcfg.StableNodeID("foo"): (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
775+
tailcfg.CapabilitySSH: nil,
776+
}),
777+
tailcfg.StableNodeID("bar"): (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
778+
tailcfg.CapabilityAdmin: {`{"test": "true}`},
779+
}),
780+
},
781+
},
782+
{
783+
name: "peers-without-capabilities",
784+
peers: []tailcfg.NodeView{
785+
(&tailcfg.Node{
786+
ID: 1,
787+
StableID: "foo",
788+
IsWireGuardOnly: true,
789+
Hostinfo: (&tailcfg.Hostinfo{}).View(),
790+
}).View(),
791+
(&tailcfg.Node{
792+
ID: 2,
793+
StableID: "bar",
794+
Hostinfo: (&tailcfg.Hostinfo{}).View(),
795+
}).View(),
796+
},
797+
},
798+
}
799+
b := newTestLocalBackend(t)
800+
801+
var cc *mockControl
802+
b.SetControlClientGetterForTesting(func(opts controlclient.Options) (controlclient.Client, error) {
803+
cc = newClient(t, opts)
804+
805+
t.Logf("ccGen: new mockControl.")
806+
cc.called("New")
807+
return cc, nil
808+
})
809+
b.Start(ipn.Options{})
810+
b.Login(nil)
811+
for _, tt := range tests {
812+
t.Run(tt.name, func(t *testing.T) {
813+
b.setNetMapLocked(&netmap.NetworkMap{
814+
SelfNode: (&tailcfg.Node{
815+
MachineAuthorized: true,
816+
Addresses: ipps("100.101.101.101"),
817+
}).View(),
818+
Peers: tt.peers,
819+
})
820+
got := b.Status()
821+
for _, peer := range got.Peer {
822+
if !reflect.DeepEqual(peer.Capabilities, tt.expectedPeerCapabilities[peer.ID]) {
823+
t.Errorf("peer capabilities: expected %v got %v", tt.expectedPeerCapabilities, peer.Capabilities)
824+
}
825+
if !reflect.DeepEqual(peer.CapMap, tt.expectedPeerCapMap[peer.ID]) {
826+
t.Errorf("peer capmap: expected %v got %v", tt.expectedPeerCapMap, peer.CapMap)
827+
}
828+
}
829+
})
830+
}
831+
}
832+
739833
// legacyBackend was the interface between Tailscale frontends
740834
// (e.g. cmd/tailscale, iOS/MacOS/Windows GUIs) and the tailscale
741835
// backend (e.g. cmd/tailscaled) running on the same machine.

ipn/ipnstate/ipnstate.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,12 @@ func (sb *StatusBuilder) AddPeer(peer key.NodePublic, st *PeerStatus) {
496496
if t := st.KeyExpiry; t != nil {
497497
e.KeyExpiry = ptr.To(*t)
498498
}
499+
if v := st.CapMap; v != nil {
500+
e.CapMap = v
501+
}
502+
if v := st.Capabilities; v != nil {
503+
e.Capabilities = v
504+
}
499505
e.Location = st.Location
500506
}
501507

0 commit comments

Comments
 (0)