control/controlclient, tailcfg: add 6 more patchable Node fields [capver 36]

Change-Id: Iae997a9a98a5dd841bc41fa91227d5a7dd476a25
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2022-08-02 20:42:13 -07:00 committed by Brad Fitzpatrick
parent 52d769d35c
commit 7c7e23d87a
3 changed files with 139 additions and 2 deletions

View File

@ -268,6 +268,24 @@ func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
if ec.Endpoints != nil { if ec.Endpoints != nil {
n.Endpoints = ec.Endpoints n.Endpoints = ec.Endpoints
} }
if ec.Key != nil {
n.Key = *ec.Key
}
if ec.DiscoKey != nil {
n.DiscoKey = *ec.DiscoKey
}
if v := ec.Online; v != nil {
n.Online = ptrCopy(v)
}
if v := ec.LastSeen; v != nil {
n.LastSeen = ptrCopy(v)
}
if v := ec.KeyExpiry; v != nil {
n.KeyExpiry = *v
}
if v := ec.Capabilities; v != nil {
n.Capabilities = *v
}
} }
} }
} }
@ -277,6 +295,16 @@ func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
mapRes.PeersRemoved = nil mapRes.PeersRemoved = nil
} }
// ptrCopy returns a pointer to a newly allocated shallow copy of *v.
func ptrCopy[T any](v *T) *T {
if v == nil {
return nil
}
ret := new(T)
*ret = *v
return ret
}
func nodesSorted(v []*tailcfg.Node) bool { func nodesSorted(v []*tailcfg.Node) bool {
for i, n := range v { for i, n := range v {
if i > 0 && n.ID <= v[i-1].ID { if i > 0 && n.ID <= v[i-1].ID {

View File

@ -12,6 +12,7 @@
"testing" "testing"
"time" "time"
"go4.org/mem"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/netmap" "tailscale.com/types/netmap"
@ -192,7 +193,90 @@ func TestUndeltaPeers(t *testing.T) {
}, },
want: peers(n(1, "foo", withDERP("127.3.3.40:2"), withEP("1.2.3.4:56"))), want: peers(n(1, "foo", withDERP("127.3.3.40:2"), withEP("1.2.3.4:56"))),
}, },
} {
name: "change_key",
prev: peers(n(1, "foo")),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
Key: ptrTo(key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
}},
}, want: peers(&tailcfg.Node{
ID: 1,
Name: "foo",
Key: key.NodePublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
}),
},
{
name: "change_disco_key",
prev: peers(n(1, "foo")),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
DiscoKey: ptrTo(key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A')))),
}},
}, want: peers(&tailcfg.Node{
ID: 1,
Name: "foo",
DiscoKey: key.DiscoPublicFromRaw32(mem.B(append(make([]byte, 31), 'A'))),
}),
},
{
name: "change_online",
prev: peers(n(1, "foo")),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
Online: ptrTo(true),
}},
}, want: peers(&tailcfg.Node{
ID: 1,
Name: "foo",
Online: ptrTo(true),
}),
},
{
name: "change_last_seen",
prev: peers(n(1, "foo")),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
LastSeen: ptrTo(time.Unix(123, 0).UTC()),
}},
}, want: peers(&tailcfg.Node{
ID: 1,
Name: "foo",
LastSeen: ptrTo(time.Unix(123, 0).UTC()),
}),
},
{
name: "change_key_expiry",
prev: peers(n(1, "foo")),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
KeyExpiry: ptrTo(time.Unix(123, 0).UTC()),
}},
}, want: peers(&tailcfg.Node{
ID: 1,
Name: "foo",
KeyExpiry: time.Unix(123, 0).UTC(),
}),
},
{
name: "change_capabilities",
prev: peers(n(1, "foo")),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
Capabilities: ptrTo([]string{"foo"}),
}},
}, want: peers(&tailcfg.Node{
ID: 1,
Name: "foo",
Capabilities: []string{"foo"},
}),
}}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -207,6 +291,10 @@ func TestUndeltaPeers(t *testing.T) {
} }
} }
func ptrTo[T any](v T) *T {
return &v
}
func formatNodes(nodes []*tailcfg.Node) string { func formatNodes(nodes []*tailcfg.Node) string {
var sb strings.Builder var sb strings.Builder
for i, n := range nodes { for i, n := range nodes {

View File

@ -70,7 +70,8 @@
// 32: 2022-04-17: client knows FilterRule.CapMatch // 32: 2022-04-17: client knows FilterRule.CapMatch
// 33: 2022-07-20: added MapResponse.PeersChangedPatch (DERPRegion + Endpoints) // 33: 2022-07-20: added MapResponse.PeersChangedPatch (DERPRegion + Endpoints)
// 34: 2022-08-02: client understands CapabilityFileSharingTarget // 34: 2022-08-02: client understands CapabilityFileSharingTarget
const CurrentCapabilityVersion CapabilityVersion = 34 // 36: 2022-08-02: added PeersChangedPatch.{Key,DiscoKey,Online,LastSeen,KeyExpiry,Capabilities}
const CurrentCapabilityVersion CapabilityVersion = 36
type StableID string type StableID string
@ -1763,6 +1764,26 @@ type PeerChange struct {
// Endpoints, if non-empty, means that NodeID's UDP Endpoints // Endpoints, if non-empty, means that NodeID's UDP Endpoints
// have changed to these. // have changed to these.
Endpoints []string `json:",omitempty"` Endpoints []string `json:",omitempty"`
// Key, if non-nil, means that the NodeID's wireguard public key changed.
Key *key.NodePublic `json:",omitempty"`
// DiscoKey, if non-nil, means that the NodeID's discokey changed.
DiscoKey *key.DiscoPublic `json:",omitempty"`
// Online, if non-nil, means that the NodeID's online status changed.
Online *bool `json:",omitempty"`
// LastSeen, if non-nil, means that the NodeID's online status changed.
LastSeen *time.Time `json:",omitempty"`
// KeyExpiry, if non-nil, changes the NodeID's key expiry.
KeyExpiry *time.Time `json:",omitempty"`
// Capabilities, if non-nil, means that the NodeID's capabilities changed.
// It's a pointer to a slice for "omitempty", to allow differentiating
// a change to empty from no change.
Capabilities *[]string `json:",omitempty"`
} }
// DerpMagicIP is a fake WireGuard endpoint IP address that means to // DerpMagicIP is a fake WireGuard endpoint IP address that means to