summaryrefslogtreecommitdiffhomepage
path: root/tsd/tsd.go
blob: bd333bd31b027eb000d9f99d61f5de06a0455566 (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
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

// Package tsd (short for "Tailscale Daemon") contains a System type that
// containing all the subsystems a Tailscale node (tailscaled or platform
// equivalent) uses.
//
// The goal of this package (as of 2023-05-03) is to eventually unify
// initialization across tailscaled, tailscaled as a Windows services, the mac
// GUI, tsnet, wasm, tests, and other places that wire up all the subsystems.
// And doing so without weird optional interface accessors on some subsystems
// that return other subsystems. It's all a work in progress.
//
// This package depends on nearly all parts of Tailscale, so it should not be
// imported by (or thus passed to) any package that does not want to depend on
// the world. In practice this means that only things like cmd/tailscaled,
// ipn/ipnlocal, and ipn/ipnserver should import this package.
package tsd

import (
	"fmt"
	"reflect"

	"tailscale.com/control/controlknobs"
	"tailscale.com/drive"
	"tailscale.com/health"
	"tailscale.com/ipn"
	"tailscale.com/ipn/conffile"
	"tailscale.com/net/dns"
	"tailscale.com/net/netmon"
	"tailscale.com/net/tsdial"
	"tailscale.com/net/tstun"
	"tailscale.com/proxymap"
	"tailscale.com/types/netmap"
	"tailscale.com/util/eventbus"
	"tailscale.com/util/syspolicy/policyclient"
	"tailscale.com/util/usermetric"
	"tailscale.com/wgengine"
	"tailscale.com/wgengine/magicsock"
	"tailscale.com/wgengine/router"
)

// System contains all the subsystems of a Tailscale node (tailscaled, etc.)
//
// A valid System value must always have a non-nil Bus populated.  Callers must
// ensure this before using the value further. Call [NewSystem] to obtain a
// value ready to use.
type System struct {
	Bus            SubSystem[*eventbus.Bus]
	Dialer         SubSystem[*tsdial.Dialer]
	DNSManager     SubSystem[*dns.Manager] // can get its *resolver.Resolver from DNSManager.Resolver
	Engine         SubSystem[wgengine.Engine]
	NetMon         SubSystem[*netmon.Monitor]
	MagicSock      SubSystem[*magicsock.Conn]
	NetstackRouter SubSystem[bool] // using Netstack at all (either entirely or at least for subnets)
	Router         SubSystem[router.Router]
	Tun            SubSystem[*tstun.Wrapper]
	StateStore     SubSystem[ipn.StateStore]
	Netstack       SubSystem[NetstackImpl] // actually a *netstack.Impl
	DriveForLocal  SubSystem[drive.FileSystemForLocal]
	DriveForRemote SubSystem[drive.FileSystemForRemote]
	PolicyClient   SubSystem[policyclient.Client]

	// InitialConfig is initial server config, if any.
	// It is nil if the node is not in declarative mode.
	// This value is never updated after startup.
	// LocalBackend tracks the current config after any reloads.
	InitialConfig *conffile.Config

	// onlyNetstack is whether the Tun value is a fake TUN device
	// and we're using netstack for everything.
	onlyNetstack bool

	controlKnobs controlknobs.Knobs
	proxyMap     proxymap.Mapper

	healthTracker       health.Tracker
	userMetricsRegistry usermetric.Registry
}

// NewSystem constructs a new otherwise-empty [System] with a
// freshly-constructed event bus populated.
func NewSystem() *System {
	sys := new(System)
	sys.Set(eventbus.New())
	return sys
}

// NetstackImpl is the interface that *netstack.Impl implements.
// It's an interface for circular dependency reasons: netstack.Impl
// references LocalBackend, and LocalBackend has a tsd.System.
type NetstackImpl interface {
	UpdateNetstackIPs(*netmap.NetworkMap)
}

// Set is a convenience method to set a subsystem value.
// It panics if the type is unknown or has that type
// has already been set.
func (s *System) Set(v any) {
	switch v := v.(type) {
	case *eventbus.Bus:
		s.Bus.Set(v)
	case *netmon.Monitor:
		s.NetMon.Set(v)
	case *dns.Manager:
		s.DNSManager.Set(v)
	case *tsdial.Dialer:
		s.Dialer.Set(v)
	case wgengine.Engine:
		s.Engine.Set(v)
	case router.Router:
		s.Router.Set(v)
	case *tstun.Wrapper:
		type ft interface {
			IsFakeTun() bool
		}
		if _, ok := v.Unwrap().(ft); ok {
			s.onlyNetstack = true
		}
		s.Tun.Set(v)
	case *magicsock.Conn:
		s.MagicSock.Set(v)
	case ipn.StateStore:
		s.StateStore.Set(v)
	case NetstackImpl:
		s.Netstack.Set(v)
	case drive.FileSystemForLocal:
		s.DriveForLocal.Set(v)
	case drive.FileSystemForRemote:
		s.DriveForRemote.Set(v)
	case policyclient.Client:
		s.PolicyClient.Set(v)
	default:
		panic(fmt.Sprintf("unknown type %T", v))
	}
}

// IsNetstackRouter reports whether Tailscale is either fully netstack based
// (without TUN) or is at least using netstack for routing.
func (s *System) IsNetstackRouter() bool {
	if v, ok := s.NetstackRouter.GetOK(); ok && v {
		return true
	}
	return s.IsNetstack()
}

// IsNetstack reports whether Tailscale is running as a netstack-based TUN-free engine.
func (s *System) IsNetstack() bool {
	return s.onlyNetstack
}

// ControlKnobs returns the control knobs for this node.
func (s *System) ControlKnobs() *controlknobs.Knobs {
	return &s.controlKnobs
}

// ProxyMapper returns the ephemeral ip:port mapper.
func (s *System) ProxyMapper() *proxymap.Mapper {
	return &s.proxyMap
}

// HealthTracker returns the system health tracker.
func (s *System) HealthTracker() *health.Tracker {
	return &s.healthTracker
}

// UserMetricsRegistry returns the system usermetrics.
func (s *System) UserMetricsRegistry() *usermetric.Registry {
	return &s.userMetricsRegistry
}

// PolicyClientOrDefault returns the policy client if set or a no-op default
// otherwise. It always returns a non-nil value.
func (s *System) PolicyClientOrDefault() policyclient.Client {
	if client, ok := s.PolicyClient.GetOK(); ok {
		return client
	}
	return policyclient.Get()
}

// SubSystem represents some subsystem of the Tailscale node daemon.
//
// A subsystem can be set to a value, and then later retrieved. A subsystem
// value tracks whether it's been set and, once set, doesn't allow the value to
// change.
type SubSystem[T any] struct {
	set bool
	v   T
}

// Set sets p to v.
//
// It panics if p is already set to a different value.
//
// Set must not be called concurrently with other Sets or Gets.
func (p *SubSystem[T]) Set(v T) {
	if p.set {
		var oldVal any = p.v
		var newVal any = v
		if oldVal == newVal {
			// Allow setting to the same value.
			// Note we had to box them through "any" to force them to be comparable.
			// We can't set the type constraint T to be "comparable" because the interfaces
			// aren't comparable. (See https://github.com/golang/go/issues/52531 and
			// https://github.com/golang/go/issues/52614 for some background)
			return
		}

		var z *T
		panic(fmt.Sprintf("%v is already set", reflect.TypeOf(z).Elem().String()))
	}
	p.v = v
	p.set = true
}

// Get returns the value of p, panicking if it hasn't been set.
func (p *SubSystem[T]) Get() T {
	if !p.set {
		var z *T
		panic(fmt.Sprintf("%v is not set", reflect.TypeOf(z).Elem().String()))
	}
	return p.v
}

// GetOK returns the value of p (if any) and whether it's been set.
func (p *SubSystem[T]) GetOK() (_ T, ok bool) {
	return p.v, p.set
}