summaryrefslogtreecommitdiffhomepage
path: root/ipn/backend.go
blob: 91cf81ca5296265ad5567306873d6a9e2db99f9a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

package ipn

import (
	"fmt"
	"strings"
	"time"

	"tailscale.com/drive"
	"tailscale.com/health"
	"tailscale.com/ipn/ipnstate"
	"tailscale.com/tailcfg"
	"tailscale.com/types/empty"
	"tailscale.com/types/key"
	"tailscale.com/types/netmap"
	"tailscale.com/types/structs"
	"tailscale.com/types/views"
)

type State int

const (
	NoState          State = 0
	InUseOtherUser   State = 1
	NeedsLogin       State = 2
	NeedsMachineAuth State = 3
	Stopped          State = 4
	Starting         State = 5
	Running          State = 6
)

// GoogleIDToken Type is the tailcfg.Oauth2Token.TokenType for the Google
// ID tokens used by the Android client.
const GoogleIDTokenType = "ts_android_google_login"

func (s State) String() string {
	return [...]string{
		"NoState",
		"InUseOtherUser",
		"NeedsLogin",
		"NeedsMachineAuth",
		"Stopped",
		"Starting",
		"Running"}[s]
}

// EngineStatus contains WireGuard engine stats.
type EngineStatus struct {
	RBytes, WBytes int64
	NumLive        int
	LiveDERPs      int // number of active DERP connections
	LivePeers      map[key.NodePublic]ipnstate.PeerStatusLite
}

// NotifyWatchOpt is a bitmask of options about what type of Notify messages
// to subscribe to.
type NotifyWatchOpt uint64

// NotifyWatchOpt values.
//
// These aren't declared using Go's iota because they're not purely internal to
// the process and iota should not be used for values that are serialized to
// disk or network. In this case, these values come over the network via the
// LocalAPI, a mostly stable API.
const (
	// NotifyWatchEngineUpdates, if set, causes Engine updates to be sent to the
	// client either regularly or when they change, without having to ask for
	// each one via Engine.RequestStatus.
	NotifyWatchEngineUpdates NotifyWatchOpt = 1 << 0

	NotifyInitialState  NotifyWatchOpt = 1 << 1 // if set, the first Notify message (sent immediately) will contain the current State + BrowseToURL + SessionID
	NotifyInitialPrefs  NotifyWatchOpt = 1 << 2 // if set, the first Notify message (sent immediately) will contain the current Prefs
	NotifyInitialNetMap NotifyWatchOpt = 1 << 3 // if set, the first Notify message (sent immediately) will contain the current NetMap

	NotifyNoPrivateKeys        NotifyWatchOpt = 1 << 4 // if set, private keys that would normally be sent in updates are zeroed out
	NotifyInitialDriveShares   NotifyWatchOpt = 1 << 5 // if set, the first Notify message (sent immediately) will contain the current Taildrive Shares
	NotifyInitialOutgoingFiles NotifyWatchOpt = 1 << 6 // if set, the first Notify message (sent immediately) will contain the current Taildrop OutgoingFiles

	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

	NotifyHealthActions NotifyWatchOpt = 1 << 9 // if set, include PrimaryActions in health.State. Otherwise append the action URL to the text

	NotifyInitialSuggestedExitNode NotifyWatchOpt = 1 << 10 // if set, the first Notify message (sent immediately) will contain the current SuggestedExitNode if available
)

// Notify is a communication from a backend (e.g. tailscaled) to a frontend
// (cmd/tailscale, iOS, macOS, Win Tasktray).
// In any given notification, any or all of these may be nil, meaning
// that they have not changed.
// They are JSON-encoded on the wire, despite the lack of struct tags.
type Notify struct {
	_       structs.Incomparable
	Version string // version number of IPN backend

	// SessionID identifies the unique WatchIPNBus session.
	// This field is only set in the first message when requesting
	// NotifyInitialState. Clients must store it on their side as
	// following notifications will not include this field.
	SessionID string `json:",omitzero"`

	// ErrMessage, if non-nil, contains a critical error message.
	// 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

	// FilesWaiting if non-nil means that files are buffered in
	// the Tailscale daemon and ready for local transfer to the
	// user's preferred storage location.
	//
	// Deprecated: use LocalClient.AwaitWaitingFiles instead.
	FilesWaiting *empty.Message `json:",omitzero"`

	// IncomingFiles, if non-nil, specifies which files are in the
	// process of being received. A nil IncomingFiles means this
	// Notify should not update the state of file transfers. A non-nil
	// but empty IncomingFiles means that no files are in the middle
	// of being transferred.
	//
	// Deprecated: use LocalClient.AwaitWaitingFiles instead.
	IncomingFiles []PartialFile `json:",omitzero"`

	// OutgoingFiles, if non-nil, tracks which files are in the process of
	// being sent via TailDrop, including files that finished, whether
	// successful or failed. This slice is sorted by Started time, then Name.
	OutgoingFiles []*OutgoingFile `json:",omitzero"`

	// LocalTCPPort, if non-nil, informs the UI frontend which
	// (non-zero) localhost TCP port it's listening on.
	// This is currently only used by Tailscale when run in the
	// macOS Network Extension.
	LocalTCPPort *uint16 `json:",omitzero"`

	// ClientVersion, if non-nil, describes whether a client version update
	// is available.
	ClientVersion *tailcfg.ClientVersion `json:",omitzero"`

	// DriveShares tracks the full set of current DriveShares that we're
	// publishing. Some client applications, like the MacOS and Windows clients,
	// will listen for updates to this and handle serving these shares under
	// the identity of the unprivileged user that is running the application. A
	// nil value here means that we're not broadcasting shares information, an
	// empty value means that there are no shares.
	DriveShares views.SliceView[*drive.Share, drive.ShareView]

	// Health is the last-known health state of the backend. When this field is
	// non-nil, a change in health verified, and the API client should surface
	// any changes to the user in the UI.
	Health *health.State `json:",omitzero"`

	// SuggestedExitNode, if non-nil, is the node that the backend has determined to
	// be the best exit node for the current network conditions.
	SuggestedExitNode *tailcfg.StableNodeID `json:",omitzero"`

	// type is mirrored in xcode/IPN/Core/LocalAPI/Model/LocalAPIModel.swift
}

func (n Notify) String() string {
	var sb strings.Builder
	sb.WriteString("Notify{")
	if n.ErrMessage != nil {
		fmt.Fprintf(&sb, "err=%q ", *n.ErrMessage)
	}
	if n.LoginFinished != nil {
		sb.WriteString("LoginFinished ")
	}
	if n.State != nil {
		fmt.Fprintf(&sb, "state=%v ", *n.State)
	}
	if n.Prefs != nil && n.Prefs.Valid() {
		fmt.Fprintf(&sb, "%v ", n.Prefs.Pretty())
	}
	if n.NetMap != nil {
		sb.WriteString("NetMap{...} ")
	}
	if n.Engine != nil {
		fmt.Fprintf(&sb, "wg=%v ", *n.Engine)
	}
	if n.BrowseToURL != nil {
		sb.WriteString("URL=<...> ")
	}
	if n.FilesWaiting != nil {
		sb.WriteString("FilesWaiting ")
	}
	if len(n.IncomingFiles) != 0 {
		sb.WriteString("IncomingFiles ")
	}
	if n.LocalTCPPort != nil {
		fmt.Fprintf(&sb, "tcpport=%v ", n.LocalTCPPort)
	}
	if n.Health != nil {
		sb.WriteString("Health{...} ")
	}
	if n.SuggestedExitNode != nil {
		fmt.Fprintf(&sb, "SuggestedExitNode=%v ", *n.SuggestedExitNode)
	}

	s := sb.String()
	if s == "Notify{" {
		return "Notify{}"
	} else {
		return s[0:len(s)-1] + "}"
	}
}

// PartialFile represents an in-progress incoming file transfer.
type PartialFile struct {
	Name         string    // e.g. "foo.jpg"
	Started      time.Time // time transfer started
	DeclaredSize int64     // or -1 if unknown
	Received     int64     // bytes copied thus far

	// PartialPath is set non-empty in "direct" file mode to the
	// in-progress '*.partial' file's path when the peerapi isn't
	// being used; see LocalBackend.SetDirectFileRoot.
	PartialPath string `json:",omitempty"`
	FinalPath   string `json:",omitempty"`

	// Done is set in "direct" mode when the partial file has been
	// closed and is ready for the caller to rename away the
	// ".partial" suffix.
	Done bool `json:",omitempty"`
}

// OutgoingFile represents an in-progress outgoing file transfer.
type OutgoingFile struct {
	ID           string               `json:",omitempty"` // unique identifier for this transfer (a type 4 UUID)
	PeerID       tailcfg.StableNodeID `json:",omitempty"` // identifier for the peer to which this is being transferred
	Name         string               `json:",omitempty"` // e.g. "foo.jpg"
	Started      time.Time            // time transfer started
	DeclaredSize int64                // or -1 if unknown
	Sent         int64                // bytes copied thus far
	Finished     bool                 // indicates whether or not the transfer finished
	Succeeded    bool                 // for a finished transfer, indicates whether or not it was successful
}

// StateKey is an opaque identifier for a set of LocalBackend state
// (preferences, private keys, etc.). It is also used as a key for
// the various LoginProfiles that the instance may be signed into.
//
// Additionally, the StateKey can be debug setting name:
//
//   - "_debug_magicsock_until" with value being a unix timestamp stringified
//   - "_debug_<component>_until" with value being a unix timestamp stringified
type StateKey string

// DebuggableComponents is a list of components whose debugging can be turned on
// and off individually using the tailscale debug command.
var DebuggableComponents = []string{
	"magicsock",
	"sockstats",
	"syspolicy",
}

type Options struct {
	// FrontendLogID is the public logtail id used by the frontend.
	FrontendLogID string
	// UpdatePrefs, if provided, overrides the Prefs already stored in the
	// backend state, *except* for the Persist member.
	//
	// TODO(apenwarr): Rename this to Prefs, and possibly move Prefs.Persist
	// elsewhere entirely (as it always should have been).
	UpdatePrefs *Prefs
	// AuthKey is an optional node auth key used to authorize a
	// new node key without user interaction.
	AuthKey string
}