tailcfg, control/controlclient: add tailcfg.PeersChangedPatch [capver 33]

This adds a lighter mechanism for endpoint updates from control.

Change-Id: If169c26becb76d683e9877dc48cfb35f90cc5f24
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2022-07-20 13:31:20 -07:00 committed by Brad Fitzpatrick
parent b763a12331
commit d8cb5aae17
4 changed files with 104 additions and 10 deletions

View File

@ -5,6 +5,7 @@
package controlclient
import (
"fmt"
"log"
"sort"
@ -238,7 +239,7 @@ func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
sortNodes(newFull)
}
if len(mapRes.PeerSeenChange) != 0 || len(mapRes.OnlineChange) != 0 {
if len(mapRes.PeerSeenChange) != 0 || len(mapRes.OnlineChange) != 0 || len(mapRes.PeersChangedPatch) != 0 {
peerByID := make(map[tailcfg.NodeID]*tailcfg.Node, len(newFull))
for _, n := range newFull {
peerByID[n.ID] = n
@ -259,6 +260,16 @@ func undeltaPeers(mapRes *tailcfg.MapResponse, prev []*tailcfg.Node) {
n.Online = &online
}
}
for _, ec := range mapRes.PeersChangedPatch {
if n, ok := peerByID[ec.NodeID]; ok {
if ec.DERPRegion != 0 {
n.DERP = fmt.Sprintf("%s:%v", tailcfg.DerpMagicIP, ec.DERPRegion)
}
if ec.Endpoints != nil {
n.Endpoints = ec.Endpoints
}
}
}
}
mapRes.Peers = newFull

View File

@ -34,6 +34,16 @@ func TestUndeltaPeers(t *testing.T) {
n.LastSeen = &t
}
}
withDERP := func(d string) func(*tailcfg.Node) {
return func(n *tailcfg.Node) {
n.DERP = d
}
}
withEP := func(ep string) func(*tailcfg.Node) {
return func(n *tailcfg.Node) {
n.Endpoints = []string{ep}
}
}
n := func(id tailcfg.NodeID, name string, mod ...func(*tailcfg.Node)) *tailcfg.Node {
n := &tailcfg.Node{ID: id, Name: name}
for _, f := range mod {
@ -137,7 +147,53 @@ func TestUndeltaPeers(t *testing.T) {
n(2, "bar", seenAt(time.Unix(123, 0))),
),
},
{
name: "ep_change_derp",
prev: peers(n(1, "foo", withDERP("127.3.3.40:3"))),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
DERPRegion: 4,
}},
},
want: peers(n(1, "foo", withDERP("127.3.3.40:4"))),
},
{
name: "ep_change_udp",
prev: peers(n(1, "foo", withEP("1.2.3.4:111"))),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
Endpoints: []string{"1.2.3.4:56"},
}},
},
want: peers(n(1, "foo", withEP("1.2.3.4:56"))),
},
{
name: "ep_change_udp",
prev: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:111"))),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
Endpoints: []string{"1.2.3.4:56"},
}},
},
want: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:56"))),
},
{
name: "ep_change_both",
prev: peers(n(1, "foo", withDERP("127.3.3.40:3"), withEP("1.2.3.4:111"))),
mapRes: &tailcfg.MapResponse{
PeersChangedPatch: []*tailcfg.PeerChange{{
NodeID: 1,
DERPRegion: 2,
Endpoints: []string{"1.2.3.4:56"},
}},
},
want: peers(n(1, "foo", withDERP("127.3.3.40:2"), withEP("1.2.3.4:56"))),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if !tt.curTime.IsZero() {

View File

@ -67,7 +67,8 @@
// 30: 2022-03-22: client can request id tokens.
// 31: 2022-04-15: PingRequest & PingResponse TSMP & disco support
// 32: 2022-04-17: client knows FilterRule.CapMatch
const CurrentCapabilityVersion CapabilityVersion = 32
// 33: 2022-07-20: added MapResponse.PeersChangedPatch (DERPRegion + Endpoints)
const CurrentCapabilityVersion CapabilityVersion = 33
type StableID string
@ -1237,6 +1238,15 @@ type MapResponse struct {
// PeersRemoved are the NodeIDs that are no longer in the peer list.
PeersRemoved []NodeID `json:",omitempty"`
// PeersChangedPatch, if non-nil, means that node(s) have changed.
// This is a lighter version of the older PeersChanged support that
// only supports certain types of updates
//
// These are applied after Peers* above, but in practice the
// control server should only send these on their own, without
// the Peers* fields also set.
PeersChangedPatch []*PeerChange `json:",omitempty"`
// PeerSeenChange contains information on how to update peers' LastSeen
// times. If the value is false, the peer is gone. If the value is true,
// the LastSeen time is now. Absent means unchanged.
@ -1724,3 +1734,27 @@ type TokenResponse struct {
// `uid` | user ID, if not tagged
IDToken string `json:"id_token"`
}
// PeerChange is an update to a node.
type PeerChange struct {
// NodeID is the node ID being mutated. If the NodeID is not
// known in the current netmap, this update should be
// ignored. (But the server will try not to send such useless
// updates.)
NodeID NodeID
// DERPRegion, if non-zero, means that NodeID's home DERP
// region ID is now this number.
DERPRegion int `json:",omitempty"`
// Endpoints, if non-empty, means that NodeID's UDP Endpoints
// have changed to these.
Endpoints []string `json:",omitempty"`
}
// DerpMagicIP is a fake WireGuard endpoint IP address that means to
// use DERP. When used (in the Node.DERP field), the port number of
// the WireGuard endpoint is the DERP region ID number to use.
//
// Mnemonic: 3.3.40 are numbers above the keys D, E, R, P.
const DerpMagicIP = "127.3.3.40"

View File

@ -442,14 +442,7 @@ func (c *Conn) addDerpPeerRoute(peer key.NodePublic, derpID int, dc *derphttp.Cl
mak.Set(&c.derpRoute, peer, derpRoute{derpID, dc})
}
// DerpMagicIP is a fake WireGuard endpoint IP address that means
// to use DERP. When used, the port number of the WireGuard endpoint
// is the DERP server number to use.
//
// Mnemonic: 3.3.40 are numbers above the keys D, E, R, P.
const DerpMagicIP = "127.3.3.40"
var derpMagicIPAddr = netaddr.MustParseIP(DerpMagicIP)
var derpMagicIPAddr = netaddr.MustParseIP(tailcfg.DerpMagicIP)
// activeDerp contains fields for an active DERP connection.
type activeDerp struct {