Skip to content

Commit 3d8533b

Browse files
bradfitznickkhyl
andcommitted
ipn/{ipnext,ipnlocal}: add a SafeBackend interface
Updates tailscale#12614 Change-Id: I197e673666e86ea74c19e3935ed71aec269b6c94 Co-authored-by: Nick Khyl <nickk@tailscale.com> Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
1 parent 25c4dc5 commit 3d8533b

File tree

8 files changed

+76
-32
lines changed

8 files changed

+76
-32
lines changed

feature/relayserver/relayserver.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"tailscale.com/ipn/ipnlocal"
2121
"tailscale.com/net/udprelay"
2222
"tailscale.com/tailcfg"
23-
"tailscale.com/tsd"
2423
"tailscale.com/types/key"
2524
"tailscale.com/types/logger"
2625
"tailscale.com/types/ptr"
@@ -40,7 +39,7 @@ func init() {
4039
// newExtension is an [ipnext.NewExtensionFn] that creates a new relay server
4140
// extension. It is registered with [ipnext.RegisterExtension] if the package is
4241
// imported.
43-
func newExtension(logf logger.Logf, _ *tsd.System) (ipnext.Extension, error) {
42+
func newExtension(logf logger.Logf, _ ipnext.SafeBackend) (ipnext.Extension, error) {
4443
return &extension{logf: logger.WithPrefix(logf, featureName+": ")}, nil
4544
}
4645

feature/taildrop/ext.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,22 @@ import (
77
"tailscale.com/ipn/ipnext"
88
"tailscale.com/ipn/ipnlocal"
99
"tailscale.com/taildrop"
10-
"tailscale.com/tsd"
1110
"tailscale.com/types/logger"
1211
)
1312

1413
func init() {
1514
ipnext.RegisterExtension("taildrop", newExtension)
1615
}
1716

18-
func newExtension(logf logger.Logf, _ *tsd.System) (ipnext.Extension, error) {
17+
func newExtension(logf logger.Logf, b ipnext.SafeBackend) (ipnext.Extension, error) {
1918
return &extension{
2019
logf: logger.WithPrefix(logf, "taildrop: "),
2120
}, nil
2221
}
2322

2423
type extension struct {
2524
logf logger.Logf
26-
lb *ipnlocal.LocalBackend
25+
sb ipnext.SafeBackend
2726
mgr *taildrop.Manager
2827
}
2928

@@ -32,11 +31,6 @@ func (e *extension) Name() string {
3231
}
3332

3433
func (e *extension) Init(h ipnext.Host) error {
35-
type I interface {
36-
Backend() ipnlocal.Backend
37-
}
38-
e.lb = h.(I).Backend().(*ipnlocal.LocalBackend)
39-
4034
// TODO(bradfitz): move init of taildrop.Manager from ipnlocal/peerapi.go to
4135
// here
4236
e.mgr = nil
@@ -45,7 +39,11 @@ func (e *extension) Init(h ipnext.Host) error {
4539
}
4640

4741
func (e *extension) Shutdown() error {
48-
if mgr, err := e.lb.TaildropManager(); err == nil {
42+
lb, ok := e.sb.(*ipnlocal.LocalBackend)
43+
if !ok {
44+
return nil
45+
}
46+
if mgr, err := lb.TaildropManager(); err == nil {
4947
mgr.Shutdown()
5048
} else {
5149
e.logf("taildrop: failed to shutdown taildrop manager: %v", err)

ipn/auditlog/extension.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"tailscale.com/ipn/ipnauth"
1717
"tailscale.com/ipn/ipnext"
1818
"tailscale.com/tailcfg"
19-
"tailscale.com/tsd"
2019
"tailscale.com/types/lazy"
2120
"tailscale.com/types/logger"
2221
)
@@ -52,7 +51,7 @@ type extension struct {
5251

5352
// newExtension is an [ipnext.NewExtensionFn] that creates a new audit log extension.
5453
// It is registered with [ipnext.RegisterExtension] if the package is imported.
55-
func newExtension(logf logger.Logf, _ *tsd.System) (ipnext.Extension, error) {
54+
func newExtension(logf logger.Logf, _ ipnext.SafeBackend) (ipnext.Extension, error) {
5655
return &extension{logf: logger.WithPrefix(logf, featureName+": ")}, nil
5756
}
5857

ipn/desktop/extension.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"tailscale.com/feature"
1818
"tailscale.com/ipn"
1919
"tailscale.com/ipn/ipnext"
20-
"tailscale.com/tsd"
2120
"tailscale.com/types/logger"
2221
"tailscale.com/util/syspolicy"
2322
)
@@ -53,7 +52,7 @@ type desktopSessionsExt struct {
5352
// newDesktopSessionsExt returns a new [desktopSessionsExt],
5453
// or an error if a [SessionManager] cannot be created.
5554
// It is registered with [ipnext.RegisterExtension] if the package is imported.
56-
func newDesktopSessionsExt(logf logger.Logf, sys *tsd.System) (ipnext.Extension, error) {
55+
func newDesktopSessionsExt(logf logger.Logf, _ ipnext.SafeBackend) (ipnext.Extension, error) {
5756
logf = logger.WithPrefix(logf, featureName+": ")
5857
sm, err := NewSessionManager(logf)
5958
if err != nil {

ipn/ipnext/ipnext.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"tailscale.com/ipn"
1414
"tailscale.com/ipn/ipnauth"
1515
"tailscale.com/tsd"
16+
"tailscale.com/tstime"
1617
"tailscale.com/types/logger"
1718
"tailscale.com/types/views"
1819
"tailscale.com/util/mak"
@@ -52,7 +53,7 @@ type Extension interface {
5253
// If the extension should be skipped at runtime, it must return either [SkipExtension]
5354
// or a wrapped [SkipExtension]. Any other error returned is fatal and will prevent
5455
// the LocalBackend from starting.
55-
type NewExtensionFn func(logger.Logf, *tsd.System) (Extension, error)
56+
type NewExtensionFn func(logger.Logf, SafeBackend) (Extension, error)
5657

5758
// SkipExtension is an error returned by [NewExtensionFn] to indicate that the extension
5859
// should be skipped rather than prevent the LocalBackend from starting.
@@ -78,8 +79,8 @@ func (d *Definition) Name() string {
7879
}
7980

8081
// MakeExtension instantiates the extension.
81-
func (d *Definition) MakeExtension(logf logger.Logf, sys *tsd.System) (Extension, error) {
82-
ext, err := d.newFn(logf, sys)
82+
func (d *Definition) MakeExtension(logf logger.Logf, sb SafeBackend) (Extension, error) {
83+
ext, err := d.newFn(logf, sb)
8384
if err != nil {
8485
return nil, err
8586
}
@@ -130,7 +131,7 @@ func Extensions() views.Slice[*Definition] {
130131
func DefinitionForTest(ext Extension) *Definition {
131132
return &Definition{
132133
name: ext.Name(),
133-
newFn: func(logger.Logf, *tsd.System) (Extension, error) { return ext, nil },
134+
newFn: func(logger.Logf, SafeBackend) (Extension, error) { return ext, nil },
134135
}
135136
}
136137

@@ -140,7 +141,7 @@ func DefinitionForTest(ext Extension) *Definition {
140141
func DefinitionWithErrForTest(name string, err error) *Definition {
141142
return &Definition{
142143
name: name,
143-
newFn: func(logger.Logf, *tsd.System) (Extension, error) { return nil, err },
144+
newFn: func(logger.Logf, SafeBackend) (Extension, error) { return nil, err },
144145
}
145146
}
146147

@@ -203,6 +204,19 @@ type Host interface {
203204
// It is a runtime error to register a nil provider or call after the host
204205
// has been initialized.
205206
RegisterControlClientCallback(NewControlClientCallback)
207+
208+
// SendNotifyAsync sends a notification to the IPN bus,
209+
// typically to the GUI client.
210+
SendNotifyAsync(ipn.Notify)
211+
}
212+
213+
// SafeBackend is a subset of the [ipnlocal.LocalBackend] type's methods that
214+
// are safe to call from extension hooks at any time (even hooks called while
215+
// LocalBackend's internal mutex is held).
216+
type SafeBackend interface {
217+
Sys() *tsd.System
218+
Clock() tstime.Clock
219+
TailscaleVarRoot() string
206220
}
207221

208222
// ExtensionServices provides access to the [Host]'s extension management services,

ipn/ipnlocal/extension_host.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"tailscale.com/ipn/ipnauth"
2121
"tailscale.com/ipn/ipnext"
2222
"tailscale.com/tailcfg"
23-
"tailscale.com/tsd"
2423
"tailscale.com/types/logger"
2524
"tailscale.com/util/execqueue"
2625
"tailscale.com/util/testenv"
@@ -131,15 +130,32 @@ type Backend interface {
131130
// SwitchToBestProfile switches to the best profile for the current state of the system.
132131
// The reason indicates why the profile is being switched.
133132
SwitchToBestProfile(reason string)
133+
134+
SendNotify(ipn.Notify)
135+
ipnext.SafeBackend
134136
}
135137

136138
// NewExtensionHost returns a new [ExtensionHost] which manages registered extensions for the given backend.
137139
// The extensions are instantiated, but are not initialized until [ExtensionHost.Init] is called.
138140
// It returns an error if instantiating any extension fails.
141+
func NewExtensionHost(logf logger.Logf, b Backend) (*ExtensionHost, error) {
142+
return newExtensionHost(logf, b)
143+
}
144+
145+
func NewExtensionHostForTest(logf logger.Logf, b Backend, overrideExts ...*ipnext.Definition) (*ExtensionHost, error) {
146+
if !testenv.InTest() {
147+
panic("use outside of test")
148+
}
149+
return newExtensionHost(logf, b, overrideExts...)
150+
}
151+
152+
// newExtensionHost is the shared implementation of [NewExtensionHost] and
153+
// [NewExtensionHostForTest].
139154
//
140-
// If overrideExts is non-nil, the registered extensions are ignored and the provided extensions are used instead.
141-
// Overriding extensions is primarily used for testing.
142-
func NewExtensionHost(logf logger.Logf, sys *tsd.System, b Backend, overrideExts ...*ipnext.Definition) (_ *ExtensionHost, err error) {
155+
// If overrideExts is non-nil, the registered extensions are ignored and the
156+
// provided extensions are used instead. Overriding extensions is primarily used
157+
// for testing.
158+
func newExtensionHost(logf logger.Logf, b Backend, overrideExts ...*ipnext.Definition) (_ *ExtensionHost, err error) {
143159
host := &ExtensionHost{
144160
b: b,
145161
logf: logger.WithPrefix(logf, "ipnext: "),
@@ -172,7 +188,7 @@ func NewExtensionHost(logf logger.Logf, sys *tsd.System, b Backend, overrideExts
172188

173189
host.allExtensions = make([]ipnext.Extension, 0, numExts)
174190
for _, d := range exts {
175-
ext, err := d.MakeExtension(logf, sys)
191+
ext, err := d.MakeExtension(logf, b)
176192
if errors.Is(err, ipnext.SkipExtension) {
177193
// The extension wants to be skipped.
178194
host.logf("%q: %v", d.Name(), err)
@@ -334,12 +350,14 @@ func (h *ExtensionHost) SwitchToBestProfileAsync(reason string) {
334350
})
335351
}
336352

337-
// Backend returns the [Backend] used by the extension host.
338-
func (h *ExtensionHost) Backend() Backend {
353+
// SendNotifyAsync implements [ipnext.Host].
354+
func (h *ExtensionHost) SendNotifyAsync(n ipn.Notify) {
339355
if h == nil {
340-
return nil
356+
return
341357
}
342-
return h.b
358+
h.enqueueBackendOperation(func(b Backend) {
359+
b.SendNotify(n)
360+
})
343361
}
344362

345363
// addFuncHook appends non-nil fn to hooks.

ipn/ipnlocal/extension_host_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ import (
2727
"tailscale.com/tailcfg"
2828
"tailscale.com/tsd"
2929
"tailscale.com/tstest"
30+
"tailscale.com/tstime"
3031
"tailscale.com/types/key"
32+
"tailscale.com/types/lazy"
3133
"tailscale.com/types/persist"
3234
"tailscale.com/util/must"
3335
)
@@ -284,7 +286,7 @@ func TestNewExtensionHost(t *testing.T) {
284286
t.Run(tt.name, func(t *testing.T) {
285287
t.Parallel()
286288
logf := tstest.WhileTestRunningLogger(t)
287-
h, err := NewExtensionHost(logf, tsd.NewSystem(), &testBackend{}, tt.defs...)
289+
h, err := NewExtensionHostForTest(logf, &testBackend{}, tt.defs...)
288290
if gotErr := err != nil; gotErr != tt.wantErr {
289291
t.Errorf("NewExtensionHost: gotErr %v(%v); wantErr %v", gotErr, err, tt.wantErr)
290292
}
@@ -1095,7 +1097,7 @@ func newExtensionHostForTest[T ipnext.Extension](t *testing.T, b Backend, initia
10951097
}
10961098
defs[i] = ipnext.DefinitionForTest(ext)
10971099
}
1098-
h, err := NewExtensionHost(logf, tsd.NewSystem(), b, defs...)
1100+
h, err := NewExtensionHostForTest(logf, b, defs...)
10991101
if err != nil {
11001102
t.Fatalf("NewExtensionHost: %v", err)
11011103
}
@@ -1320,6 +1322,7 @@ func (q *testExecQueue) Wait(context.Context) error { return nil }
13201322
// testBackend implements [ipnext.Backend] for testing purposes
13211323
// by calling the provided hooks when its methods are called.
13221324
type testBackend struct {
1325+
lazySys lazy.SyncValue[*tsd.System]
13231326
switchToBestProfileHook func(reason string)
13241327

13251328
// mu protects the backend state.
@@ -1328,6 +1331,13 @@ type testBackend struct {
13281331
mu sync.Mutex
13291332
}
13301333

1334+
func (b *testBackend) Clock() tstime.Clock { return tstime.StdClock{} }
1335+
func (b *testBackend) Sys() *tsd.System {
1336+
return b.lazySys.Get(tsd.NewSystem)
1337+
}
1338+
func (b *testBackend) SendNotify(ipn.Notify) { panic("not implemented") }
1339+
func (b *testBackend) TailscaleVarRoot() string { panic("not implemented") }
1340+
13311341
func (b *testBackend) SwitchToBestProfile(reason string) {
13321342
b.mu.Lock()
13331343
defer b.mu.Unlock()

ipn/ipnlocal/local.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
525525
}
526526
}
527527

528-
if b.extHost, err = NewExtensionHost(logf, sys, b); err != nil {
528+
if b.extHost, err = NewExtensionHost(logf, b); err != nil {
529529
return nil, fmt.Errorf("failed to create extension host: %w", err)
530530
}
531531
b.pm.SetExtensionHost(b.extHost)
@@ -589,6 +589,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
589589
}
590590

591591
func (b *LocalBackend) Clock() tstime.Clock { return b.clock }
592+
func (b *LocalBackend) Sys() *tsd.System { return b.sys }
592593

593594
// FindExtensionByName returns an active extension with the given name,
594595
// or nil if no such extension exists.
@@ -3187,6 +3188,12 @@ func (b *LocalBackend) send(n ipn.Notify) {
31873188
b.sendTo(n, allClients)
31883189
}
31893190

3191+
// SendNotify sends a notification to the IPN bus,
3192+
// typically to the GUI client.
3193+
func (b *LocalBackend) SendNotify(n ipn.Notify) {
3194+
b.send(n)
3195+
}
3196+
31903197
// notificationTarget describes a notification recipient.
31913198
// A zero value is valid and indicate that the notification
31923199
// should be broadcast to all active [watchSession]s.

0 commit comments

Comments
 (0)