summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ipn/backend.go83
-rw-r--r--ipn/ipnlocal/bus.go3
-rw-r--r--ipn/ipnlocal/bus_test.go7
3 files changed, 87 insertions, 6 deletions
diff --git a/ipn/backend.go b/ipn/backend.go
index 3e956f473..9ee3bc651 100644
--- a/ipn/backend.go
+++ b/ipn/backend.go
@@ -81,6 +81,13 @@ const (
NotifyInitialHealthState NotifyWatchOpt = 1 << 7 // if set, the first Notify message (sent immediately) will contain the current health.State of the client
NotifyRateLimit NotifyWatchOpt = 1 << 8 // if set, rate limit spammy netmap updates to every few seconds
+
+ // NotifyOmitLegacyNetmap, if set, causes the backend to not send Notify
+ // mesesages with the [Notify.NetMap] field populated. See that field's docs
+ // for background. Clients should use NodeUpdate, UserUpdate, etc.
+ // At some point in the future (maybe a year or two from 2025-01-04?), this will
+ // become the default and only way.
+ NotifyOmitLegacyNetmap NotifyWatchOpt = 1 << 9
)
// Notify is a communication from a backend (e.g. tailscaled) to a frontend
@@ -102,12 +109,23 @@ type Notify struct {
// For State InUseOtherUser, ErrMessage is not critical and just contains the details.
ErrMessage *string
- LoginFinished *empty.Message // non-nil when/if the login process succeeded
- State *State // if non-nil, the new or current IPN state
- Prefs *PrefsView // if non-nil && Valid, the new or current preferences
- NetMap *netmap.NetworkMap // if non-nil, the new or current netmap
- Engine *EngineStatus // if non-nil, the new or current wireguard stats
- BrowseToURL *string // if non-nil, UI should open a browser right now
+ LoginFinished *empty.Message // non-nil when/if the login process succeeded
+ State *State // if non-nil, the new or current IPN state
+ Prefs *PrefsView // if non-nil && Valid, the new or current preferences
+ Engine *EngineStatus // if non-nil, the new or current wireguard stats
+ BrowseToURL *string // if non-nil, UI should open a browser right now
+
+ // NetMap, if non-nil, the new or current network map.
+ //
+ // If [NotifyOmitLegacyNetmap] is used on an WatchIPNBus subscription, this
+ // field is always nil.
+ //
+ // Deprecated: while sent by default (as of 2025-01-04), it is a colossal
+ // type and on its way out. With large networks with many peers and high
+ // churn, sending this non-stop to GUI clients uses a lot of CPU and
+ // bandwidth. It was a quadratic mistake from day 1; see
+ // tailscale/tailscale#1909 and tailscale/tailscale#13390.
+ NetMap *netmap.NetworkMap // if non-nil, the new or current netmap
// FilesWaiting if non-nil means that files are buffered in
// the Tailscale daemon and ready for local transfer to the
@@ -153,9 +171,62 @@ type Notify struct {
// any changes to the user in the UI.
Health *health.State `json:",omitempty"`
+ // ResetNodesAndUsers, if true, means that all previously mentioned
+ // nodes and users should be forgotten.
+ //
+ // This is set to true when a new network map long poll to the control
+ // plane begins, but before any NodeUpdate or UserUpdate are sent.
+ ResetNodesAndUsers bool `json:",omitempty"`
+
+ // NodeUpdate is a map of node updates.
+ //
+ // The values are always non-nil.
+ NodeUpdate map[tailcfg.StableNodeID]*NodeUpdate `json:",omitempty"`
+
+ // UserUpdate is a map of user updates.
+ //
+ // The values are always non-nil.
+ UserUpdate map[tailcfg.UserID]*UserUpdate `json:",omitempty"`
+
// type is mirrored in xcode/IPN/Core/LocalAPI/Model/LocalAPIModel.swift
}
+// NodeUpdate describes a change to a node in the network map.
+type NodeUpdate struct {
+ // NodeID is the integer form of the Node ID being updated or deleted.
+ NodeID tailcfg.NodeID
+
+ // Deleted is true if the node has been deleted from the network map.
+ Deleted bool `json:",omitempty"`
+
+ // IsSelf is true if this node is the current node.
+ // False or omitted means it's a peer.
+ IsSelf bool `json:",omitempty"`
+
+ // New is the new node information, if the node has been updated.
+ // It maybe omitted for delta updates, depending on the type of update
+ // and the client's WatchIPNBus subscription options.
+ New *tailcfg.Node `json:",omitempty"`
+
+ // Patch, if non-nil, describes the minimal version of the change made to the node.
+ // When Patch and New are both non-nil, the New field represents the state
+ // after the Patch has been applied.
+ Patch *tailcfg.PeerChange `json:",omitempty"`
+}
+
+// UserUpdate describes a change to a user in the network map.
+type UserUpdate struct {
+ UserID tailcfg.UserID
+
+ // Deleted is true if the user has been deleted from the network map.
+ // That is, the user is no longer referenced by any node in the network map.
+ Deleted bool `json:",omitempty"`
+
+ // Profile is the new user profile, if the user has been newly added or
+ // updated. If Delete is true, this is omitted.
+ Profile *tailcfg.UserProfile `json:",omitempty"`
+}
+
func (n Notify) String() string {
var sb strings.Builder
sb.WriteString("Notify{")
diff --git a/ipn/ipnlocal/bus.go b/ipn/ipnlocal/bus.go
index 111a877d8..0b435b7be 100644
--- a/ipn/ipnlocal/bus.go
+++ b/ipn/ipnlocal/bus.go
@@ -154,6 +154,9 @@ func isNotableNotify(n *ipn.Notify) bool {
n.LoginFinished != nil ||
!n.DriveShares.IsNil() ||
n.Health != nil ||
+ n.ResetNodesAndUsers ||
+ n.NodeUpdate != nil ||
+ n.UserUpdate != nil ||
len(n.IncomingFiles) > 0 ||
len(n.OutgoingFiles) > 0 ||
n.FilesWaiting != nil
diff --git a/ipn/ipnlocal/bus_test.go b/ipn/ipnlocal/bus_test.go
index 5c75ac54d..74610200d 100644
--- a/ipn/ipnlocal/bus_test.go
+++ b/ipn/ipnlocal/bus_test.go
@@ -12,6 +12,7 @@ import (
"tailscale.com/drive"
"tailscale.com/ipn"
+ "tailscale.com/tailcfg"
"tailscale.com/tstest"
"tailscale.com/tstime"
"tailscale.com/types/logger"
@@ -45,6 +46,10 @@ func TestIsNotableNotify(t *testing.T) {
continue
case "DriveShares":
n.DriveShares = views.SliceOfViews[*drive.Share, drive.ShareView](make([]*drive.Share, 1))
+ case "NodeUpdate":
+ n.NodeUpdate = map[tailcfg.StableNodeID]*ipn.NodeUpdate{}
+ case "UserUpdate":
+ n.UserUpdate = map[tailcfg.UserID]*ipn.UserUpdate{}
default:
rf := reflect.ValueOf(n).Elem().Field(i)
switch rf.Kind() {
@@ -54,6 +59,8 @@ func TestIsNotableNotify(t *testing.T) {
rf.SetString("foo")
case reflect.Slice:
rf.Set(reflect.MakeSlice(rf.Type(), 1, 1))
+ case reflect.Bool:
+ rf.SetBool(true)
default:
t.Errorf("unhandled field kind %v for %q", rf.Kind(), sf.Name)
}