diff --git a/appc/appconnector.go b/appc/appconnector.go index c86bf2d0f..291884065 100644 --- a/appc/appconnector.go +++ b/appc/appconnector.go @@ -12,12 +12,14 @@ package appc import ( "context" "fmt" + "maps" "net/netip" "slices" "strings" "sync" "time" + "tailscale.com/types/appctype" "tailscale.com/types/logger" "tailscale.com/types/views" "tailscale.com/util/clientmetric" @@ -114,19 +116,6 @@ func metricStoreRoutes(rate, nRoutes int64) { recordMetric(nRoutes, metricStoreRoutesNBuckets, metricStoreRoutesN) } -// RouteInfo is a data structure used to persist the in memory state of an AppConnector -// so that we can know, even after a restart, which routes came from ACLs and which were -// learned from domains. -type RouteInfo struct { - // Control is the routes from the 'routes' section of an app connector acl. - Control []netip.Prefix `json:",omitempty"` - // Domains are the routes discovered by observing DNS lookups for configured domains. - Domains map[string][]netip.Addr `json:",omitempty"` - // Wildcards are the configured DNS lookup domains to observe. When a DNS query matches Wildcards, - // its result is added to Domains. - Wildcards []string `json:",omitempty"` -} - // AppConnector is an implementation of an AppConnector that performs // its function as a subsystem inside of a tailscale node. At the control plane // side App Connector routing is configured in terms of domains rather than IP @@ -141,9 +130,12 @@ type AppConnector struct { logf logger.Logf eventBus *eventbus.Bus routeAdvertiser RouteAdvertiser + pubClient *eventbus.Client + updatePub *eventbus.Publisher[appctype.RouteUpdate] + storePub *eventbus.Publisher[appctype.RouteInfo] // storeRoutesFunc will be called to persist routes if it is not nil. - storeRoutesFunc func(*RouteInfo) error + storeRoutesFunc func(*appctype.RouteInfo) error // mu guards the fields that follow mu sync.Mutex @@ -181,11 +173,11 @@ type Config struct { // RouteInfo, if non-nil, use used as the initial set of routes for the // connector. If nil, the connector starts empty. - RouteInfo *RouteInfo + RouteInfo *appctype.RouteInfo // StoreRoutesFunc, if non-nil, is called when the connector's routes // change, to allow the routes to be persisted. - StoreRoutesFunc func(*RouteInfo) error + StoreRoutesFunc func(*appctype.RouteInfo) error } // NewAppConnector creates a new AppConnector. @@ -198,10 +190,14 @@ func NewAppConnector(c Config) *AppConnector { case c.RouteAdvertiser == nil: panic("missing route advertiser") } + ec := c.EventBus.Client("appc.AppConnector") ac := &AppConnector{ logf: logger.WithPrefix(c.Logf, "appc: "), eventBus: c.EventBus, + pubClient: ec, + updatePub: eventbus.Publish[appctype.RouteUpdate](ec), + storePub: eventbus.Publish[appctype.RouteInfo](ec), routeAdvertiser: c.RouteAdvertiser, storeRoutesFunc: c.StoreRoutesFunc, } @@ -228,6 +224,14 @@ func (e *AppConnector) ShouldStoreRoutes() bool { // storeRoutesLocked takes the current state of the AppConnector and persists it func (e *AppConnector) storeRoutesLocked() error { + if e.storePub.ShouldPublish() { + e.storePub.Publish(appctype.RouteInfo{ + // Clone here, as the subscriber will handle these outside our lock. + Control: slices.Clone(e.controlRoutes), + Domains: maps.Clone(e.domains), + Wildcards: slices.Clone(e.wildcards), + }) + } if !e.ShouldStoreRoutes() { return nil } @@ -240,7 +244,8 @@ func (e *AppConnector) storeRoutesLocked() error { e.writeRateMinute.update(numRoutes) e.writeRateDay.update(numRoutes) - return e.storeRoutesFunc(&RouteInfo{ + // TODO(creachdair): Remove this once it's delivered over the event bus. + return e.storeRoutesFunc(&appctype.RouteInfo{ Control: e.controlRoutes, Domains: e.domains, Wildcards: e.wildcards, @@ -283,6 +288,18 @@ func (e *AppConnector) Wait(ctx context.Context) { e.queue.Wait(ctx) } +// Close closes the connector and cleans up resources associated with it. +// It is safe (and a noop) to call Close on nil. +func (e *AppConnector) Close() { + if e == nil { + return + } + e.mu.Lock() + defer e.mu.Unlock() + e.queue.Shutdown() // TODO(creachadair): Should we wait for it too? + e.pubClient.Close() +} + func (e *AppConnector) updateDomains(domains []string) { e.mu.Lock() defer e.mu.Unlock() @@ -323,11 +340,15 @@ func (e *AppConnector) updateDomains(domains []string) { toRemove = append(toRemove, netip.PrefixFrom(a, a.BitLen())) } } - e.queue.Add(func() { - if err := e.routeAdvertiser.UnadvertiseRoute(toRemove...); err != nil { - e.logf("failed to unadvertise routes on domain removal: %v: %v: %v", slicesx.MapKeys(oldDomains), toRemove, err) - } - }) + + if len(toRemove) != 0 { + e.queue.Add(func() { + if err := e.routeAdvertiser.UnadvertiseRoute(toRemove...); err != nil { + e.logf("failed to unadvertise routes on domain removal: %v: %v: %v", slicesx.MapKeys(oldDomains), toRemove, err) + } + }) + e.updatePub.Publish(appctype.RouteUpdate{Unadvertise: toRemove}) + } } e.logf("handling domains: %v and wildcards: %v", slicesx.MapKeys(e.domains), e.wildcards) @@ -377,6 +398,10 @@ nextRoute: e.logf("failed to unadvertise routes: %v: %v", toRemove, err) } }) + e.updatePub.Publish(appctype.RouteUpdate{ + Advertise: routes, + Unadvertise: toRemove, + }) e.controlRoutes = routes if err := e.storeRoutesLocked(); err != nil { @@ -464,6 +489,7 @@ func (e *AppConnector) scheduleAdvertisement(domain string, routes ...netip.Pref e.logf("failed to advertise routes for %s: %v: %v", domain, routes, err) return } + e.updatePub.Publish(appctype.RouteUpdate{Advertise: routes}) e.mu.Lock() defer e.mu.Unlock() diff --git a/appc/appconnector_test.go b/appc/appconnector_test.go index c23908c28..91f0185d0 100644 --- a/appc/appconnector_test.go +++ b/appc/appconnector_test.go @@ -4,6 +4,8 @@ package appc import ( + stdcmp "cmp" + "fmt" "net/netip" "reflect" "slices" @@ -11,9 +13,12 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "golang.org/x/net/dns/dnsmessage" "tailscale.com/appc/appctest" "tailscale.com/tstest" + "tailscale.com/types/appctype" "tailscale.com/util/clientmetric" "tailscale.com/util/eventbus/eventbustest" "tailscale.com/util/mak" @@ -21,7 +26,7 @@ import ( "tailscale.com/util/slicesx" ) -func fakeStoreRoutes(*RouteInfo) error { return nil } +func fakeStoreRoutes(*appctype.RouteInfo) error { return nil } func TestUpdateDomains(t *testing.T) { ctx := t.Context() @@ -33,14 +38,15 @@ func TestUpdateDomains(t *testing.T) { Logf: t.Logf, EventBus: bus, RouteAdvertiser: &appctest.RouteCollector{}, - RouteInfo: &RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { a = NewAppConnector(Config{Logf: t.Logf, EventBus: bus, RouteAdvertiser: &appctest.RouteCollector{}}) } - a.UpdateDomains([]string{"example.com"}) + t.Cleanup(a.Close) + a.UpdateDomains([]string{"example.com"}) a.Wait(ctx) if got, want := a.Domains().AsSlice(), []string{"example.com"}; !slices.Equal(got, want) { t.Errorf("got %v; want %v", got, want) @@ -68,6 +74,7 @@ func TestUpdateRoutes(t *testing.T) { ctx := t.Context() bus := eventbustest.NewBus(t) for _, shouldStore := range []bool{false, true} { + w := eventbustest.NewWatcher(t, bus) rc := &appctest.RouteCollector{} var a *AppConnector if shouldStore { @@ -75,11 +82,14 @@ func TestUpdateRoutes(t *testing.T) { Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, - RouteInfo: &RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, + RouteInfo: &appctype.RouteInfo{}, + StoreRoutesFunc: fakeStoreRoutes, }) } else { a = NewAppConnector(Config{Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc}) } + t.Cleanup(a.Close) + a.updateDomains([]string{"*.example.com"}) // This route should be collapsed into the range @@ -116,6 +126,20 @@ func TestUpdateRoutes(t *testing.T) { if !slices.EqualFunc(rc.RemovedRoutes(), wantRemoved, prefixEqual) { t.Fatalf("unexpected removed routes: %v", rc.RemovedRoutes()) } + + if err := eventbustest.Expect(w, + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("192.0.2.1/32")}), + eventbustest.Type[appctype.RouteInfo](), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("192.0.0.1/32")}), + eventbustest.Type[appctype.RouteInfo](), + eqUpdate(appctype.RouteUpdate{ + Advertise: prefixes("192.0.0.1/32", "192.0.2.0/24"), + Unadvertise: prefixes("192.0.2.1/32"), + }), + eventbustest.Type[appctype.RouteInfo](), + ); err != nil { + t.Error(err) + } } } @@ -123,6 +147,7 @@ func TestUpdateRoutesUnadvertisesContainedRoutes(t *testing.T) { ctx := t.Context() bus := eventbustest.NewBus(t) for _, shouldStore := range []bool{false, true} { + w := eventbustest.NewWatcher(t, bus) rc := &appctest.RouteCollector{} var a *AppConnector if shouldStore { @@ -130,12 +155,14 @@ func TestUpdateRoutesUnadvertisesContainedRoutes(t *testing.T) { Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, - RouteInfo: &RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { a = NewAppConnector(Config{Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc}) } + t.Cleanup(a.Close) + mak.Set(&a.domains, "example.com", []netip.Addr{netip.MustParseAddr("192.0.2.1")}) rc.SetRoutes([]netip.Prefix{netip.MustParsePrefix("192.0.2.1/32")}) routes := []netip.Prefix{netip.MustParsePrefix("192.0.2.0/24")} @@ -145,12 +172,23 @@ func TestUpdateRoutesUnadvertisesContainedRoutes(t *testing.T) { if !slices.EqualFunc(routes, rc.Routes(), prefixEqual) { t.Fatalf("got %v, want %v", rc.Routes(), routes) } + + if err := eventbustest.ExpectExactly(w, + eqUpdate(appctype.RouteUpdate{ + Advertise: prefixes("192.0.2.0/24"), + Unadvertise: prefixes("192.0.2.1/32"), + }), + eventbustest.Type[appctype.RouteInfo](), + ); err != nil { + t.Error(err) + } } } func TestDomainRoutes(t *testing.T) { bus := eventbustest.NewBus(t) for _, shouldStore := range []bool{false, true} { + w := eventbustest.NewWatcher(t, bus) rc := &appctest.RouteCollector{} var a *AppConnector if shouldStore { @@ -158,12 +196,13 @@ func TestDomainRoutes(t *testing.T) { Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, - RouteInfo: &RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { a = NewAppConnector(Config{Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc}) } + t.Cleanup(a.Close) a.updateDomains([]string{"example.com"}) if err := a.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8")); err != nil { t.Errorf("ObserveDNSResponse: %v", err) @@ -177,6 +216,13 @@ func TestDomainRoutes(t *testing.T) { if got := a.DomainRoutes(); !reflect.DeepEqual(got, want) { t.Fatalf("DomainRoutes: got %v, want %v", got, want) } + + if err := eventbustest.ExpectExactly(w, + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("192.0.0.8/32")}), + eventbustest.Type[appctype.RouteInfo](), + ); err != nil { + t.Error(err) + } } } @@ -184,6 +230,7 @@ func TestObserveDNSResponse(t *testing.T) { ctx := t.Context() bus := eventbustest.NewBus(t) for _, shouldStore := range []bool{false, true} { + w := eventbustest.NewWatcher(t, bus) rc := &appctest.RouteCollector{} var a *AppConnector if shouldStore { @@ -191,12 +238,13 @@ func TestObserveDNSResponse(t *testing.T) { Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, - RouteInfo: &RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { a = NewAppConnector(Config{Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc}) } + t.Cleanup(a.Close) // a has no domains configured, so it should not advertise any routes if err := a.ObserveDNSResponse(dnsResponse("example.com.", "192.0.0.8")); err != nil { @@ -273,6 +321,22 @@ func TestObserveDNSResponse(t *testing.T) { if !slices.Contains(a.domains["example.com"], netip.MustParseAddr("192.0.2.1")) { t.Errorf("missing %v from %v", "192.0.2.1", a.domains["exmaple.com"]) } + + if err := eventbustest.ExpectExactly(w, + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("192.0.0.8/32")}), // from initial DNS response, via example.com + eventbustest.Type[appctype.RouteInfo](), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("192.0.0.9/32")}), // from CNAME response + eventbustest.Type[appctype.RouteInfo](), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("192.0.0.10/32")}), // from CNAME response, mid-chain + eventbustest.Type[appctype.RouteInfo](), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("2001:db8::1/128")}), // v6 DNS response + eventbustest.Type[appctype.RouteInfo](), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("192.0.2.0/24")}), // additional prefix + eventbustest.Type[appctype.RouteInfo](), + // N.B. no update for 192.0.2.1 as it is already covered + ); err != nil { + t.Error(err) + } } } @@ -280,6 +344,7 @@ func TestWildcardDomains(t *testing.T) { ctx := t.Context() bus := eventbustest.NewBus(t) for _, shouldStore := range []bool{false, true} { + w := eventbustest.NewWatcher(t, bus) rc := &appctest.RouteCollector{} var a *AppConnector if shouldStore { @@ -287,12 +352,13 @@ func TestWildcardDomains(t *testing.T) { Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, - RouteInfo: &RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { a = NewAppConnector(Config{Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc}) } + t.Cleanup(a.Close) a.updateDomains([]string{"*.example.com"}) if err := a.ObserveDNSResponse(dnsResponse("foo.example.com.", "192.0.0.8")); err != nil { @@ -319,6 +385,13 @@ func TestWildcardDomains(t *testing.T) { if len(a.wildcards) != 1 { t.Errorf("expected only one wildcard domain, got %v", a.wildcards) } + + if err := eventbustest.ExpectExactly(w, + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("192.0.0.8/32")}), + eventbustest.Type[appctype.RouteInfo](), + ); err != nil { + t.Error(err) + } } } @@ -437,6 +510,7 @@ func TestUpdateRouteRouteRemoval(t *testing.T) { ctx := t.Context() bus := eventbustest.NewBus(t) for _, shouldStore := range []bool{false, true} { + w := eventbustest.NewWatcher(t, bus) rc := &appctest.RouteCollector{} assertRoutes := func(prefix string, routes, removedRoutes []netip.Prefix) { @@ -454,12 +528,14 @@ func TestUpdateRouteRouteRemoval(t *testing.T) { Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, - RouteInfo: &RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { a = NewAppConnector(Config{Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc}) } + t.Cleanup(a.Close) + // nothing has yet been advertised assertRoutes("appc init", []netip.Prefix{}, []netip.Prefix{}) @@ -482,6 +558,13 @@ func TestUpdateRouteRouteRemoval(t *testing.T) { wantRemovedRoutes = prefixes("1.2.3.2/32") } assertRoutes("removal", wantRoutes, wantRemovedRoutes) + + if err := eventbustest.Expect(w, + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("1.2.3.1/32", "1.2.3.2/32")}), // no duplicates here + eventbustest.Type[appctype.RouteInfo](), + ); err != nil { + t.Error(err) + } } } @@ -489,6 +572,7 @@ func TestUpdateDomainRouteRemoval(t *testing.T) { ctx := t.Context() bus := eventbustest.NewBus(t) for _, shouldStore := range []bool{false, true} { + w := eventbustest.NewWatcher(t, bus) rc := &appctest.RouteCollector{} assertRoutes := func(prefix string, routes, removedRoutes []netip.Prefix) { @@ -506,12 +590,14 @@ func TestUpdateDomainRouteRemoval(t *testing.T) { Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, - RouteInfo: &RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { a = NewAppConnector(Config{Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc}) } + t.Cleanup(a.Close) + assertRoutes("appc init", []netip.Prefix{}, []netip.Prefix{}) a.UpdateDomainsAndRoutes([]string{"a.example.com", "b.example.com"}, []netip.Prefix{}) @@ -544,6 +630,22 @@ func TestUpdateDomainRouteRemoval(t *testing.T) { wantRemovedRoutes = prefixes("1.2.3.3/32", "1.2.3.4/32") } assertRoutes("removal", wantRoutes, wantRemovedRoutes) + + wantEvents := []any{ + // Each DNS record observed triggers an update. + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("1.2.3.1/32")}), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("1.2.3.2/32")}), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("1.2.3.3/32")}), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("1.2.3.4/32")}), + } + if shouldStore { + wantEvents = append(wantEvents, eqUpdate(appctype.RouteUpdate{ + Unadvertise: prefixes("1.2.3.3/32", "1.2.3.4/32"), + })) + } + if err := eventbustest.Expect(w, wantEvents...); err != nil { + t.Error(err) + } } } @@ -551,6 +653,7 @@ func TestUpdateWildcardRouteRemoval(t *testing.T) { ctx := t.Context() bus := eventbustest.NewBus(t) for _, shouldStore := range []bool{false, true} { + w := eventbustest.NewWatcher(t, bus) rc := &appctest.RouteCollector{} assertRoutes := func(prefix string, routes, removedRoutes []netip.Prefix) { @@ -568,12 +671,14 @@ func TestUpdateWildcardRouteRemoval(t *testing.T) { Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, - RouteInfo: &RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { a = NewAppConnector(Config{Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc}) } + t.Cleanup(a.Close) + assertRoutes("appc init", []netip.Prefix{}, []netip.Prefix{}) a.UpdateDomainsAndRoutes([]string{"a.example.com", "*.b.example.com"}, []netip.Prefix{}) @@ -606,6 +711,22 @@ func TestUpdateWildcardRouteRemoval(t *testing.T) { wantRemovedRoutes = prefixes("1.2.3.3/32", "1.2.3.4/32") } assertRoutes("removal", wantRoutes, wantRemovedRoutes) + + wantEvents := []any{ + // Each DNS record observed triggers an update. + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("1.2.3.1/32")}), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("1.2.3.2/32")}), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("1.2.3.3/32")}), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("1.2.3.4/32")}), + } + if shouldStore { + wantEvents = append(wantEvents, eqUpdate(appctype.RouteUpdate{ + Unadvertise: prefixes("1.2.3.3/32", "1.2.3.4/32"), + })) + } + if err := eventbustest.Expect(w, wantEvents...); err != nil { + t.Error(err) + } } } @@ -708,17 +829,23 @@ func TestMetricBucketsAreSorted(t *testing.T) { // routeAdvertiser, calls to Advertise/UnadvertiseRoutes can end up calling // back into AppConnector via authReconfig. If everything is called // synchronously, this results in a deadlock on AppConnector.mu. +// +// TODO(creachadair, 2025-09-18): Remove this along with the advertiser +// interface once the LocalBackend is switched to use the event bus and the +// tests have been updated not to need it. func TestUpdateRoutesDeadlock(t *testing.T) { ctx := t.Context() bus := eventbustest.NewBus(t) + w := eventbustest.NewWatcher(t, bus) rc := &appctest.RouteCollector{} a := NewAppConnector(Config{ Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, - RouteInfo: &RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) + t.Cleanup(a.Close) advertiseCalled := new(atomic.Bool) unadvertiseCalled := new(atomic.Bool) @@ -762,4 +889,42 @@ func TestUpdateRoutesDeadlock(t *testing.T) { if want := []netip.Prefix{netip.MustParsePrefix("127.0.0.1/32")}; !slices.Equal(slices.Compact(rc.Routes()), want) { t.Fatalf("got %v, want %v", rc.Routes(), want) } + + if err := eventbustest.ExpectExactly(w, + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("127.0.0.1/32", "127.0.0.2/32")}), + eventbustest.Type[appctype.RouteInfo](), + eqUpdate(appctype.RouteUpdate{Advertise: prefixes("127.0.0.1/32"), Unadvertise: prefixes("127.0.0.2/32")}), + eventbustest.Type[appctype.RouteInfo](), + ); err != nil { + t.Error(err) + } +} + +type textUpdate struct { + Advertise []string + Unadvertise []string +} + +func routeUpdateToText(u appctype.RouteUpdate) textUpdate { + var out textUpdate + for _, p := range u.Advertise { + out.Advertise = append(out.Advertise, p.String()) + } + for _, p := range u.Unadvertise { + out.Unadvertise = append(out.Unadvertise, p.String()) + } + return out +} + +// eqUpdate generates an eventbus test filter that matches a appctype.RouteUpdate +// message equal to want, or reports an error giving a human-readable diff. +func eqUpdate(want appctype.RouteUpdate) func(appctype.RouteUpdate) error { + return func(got appctype.RouteUpdate) error { + if diff := cmp.Diff(routeUpdateToText(got), routeUpdateToText(want), + cmpopts.SortSlices(stdcmp.Less[string]), + ); diff != "" { + return fmt.Errorf("wrong update (-got, +want):\n%s", diff) + } + return nil + } } diff --git a/client/local/local.go b/client/local/local.go index 8da8f57e5..9faeadca3 100644 --- a/client/local/local.go +++ b/client/local/local.go @@ -27,7 +27,6 @@ import ( "sync" "time" - "tailscale.com/appc" "tailscale.com/client/tailscale/apitype" "tailscale.com/drive" "tailscale.com/envknob" @@ -40,6 +39,7 @@ import ( "tailscale.com/paths" "tailscale.com/safesocket" "tailscale.com/tailcfg" + "tailscale.com/types/appctype" "tailscale.com/types/dnstype" "tailscale.com/types/key" "tailscale.com/util/eventbus" @@ -1387,10 +1387,10 @@ func (lc *Client) ShutdownTailscaled(ctx context.Context) error { return err } -func (lc *Client) GetAppConnectorRouteInfo(ctx context.Context) (appc.RouteInfo, error) { +func (lc *Client) GetAppConnectorRouteInfo(ctx context.Context) (appctype.RouteInfo, error) { body, err := lc.get200(ctx, "/localapi/v0/appc-route-info") if err != nil { - return appc.RouteInfo{}, err + return appctype.RouteInfo{}, err } - return decodeJSON[appc.RouteInfo](body) + return decodeJSON[appctype.RouteInfo](body) } diff --git a/cmd/derper/depaware.txt b/cmd/derper/depaware.txt index 0628afd63..278d54b1f 100644 --- a/cmd/derper/depaware.txt +++ b/cmd/derper/depaware.txt @@ -74,7 +74,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa google.golang.org/protobuf/runtime/protoimpl from github.com/prometheus/client_model/go+ google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/client/local 💣 tailscale.com/atomicfile from tailscale.com/cmd/derper+ tailscale.com/client/local from tailscale.com/derp/derpserver tailscale.com/client/tailscale/apitype from tailscale.com/client/local @@ -124,6 +123,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa tailscale.com/tsweb from tailscale.com/cmd/derper+ tailscale.com/tsweb/promvarz from tailscale.com/cmd/derper tailscale.com/tsweb/varz from tailscale.com/tsweb+ + tailscale.com/types/appctype from tailscale.com/client/local tailscale.com/types/dnstype from tailscale.com/tailcfg+ tailscale.com/types/empty from tailscale.com/ipn tailscale.com/types/ipproto from tailscale.com/tailcfg+ @@ -140,14 +140,13 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa tailscale.com/types/tkatype from tailscale.com/client/local+ tailscale.com/types/views from tailscale.com/ipn+ tailscale.com/util/cibuild from tailscale.com/health - tailscale.com/util/clientmetric from tailscale.com/net/netmon+ + tailscale.com/util/clientmetric from tailscale.com/net/netmon tailscale.com/util/cloudenv from tailscale.com/hostinfo+ tailscale.com/util/ctxkey from tailscale.com/tsweb+ 💣 tailscale.com/util/deephash from tailscale.com/util/syspolicy/setting L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics tailscale.com/util/dnsname from tailscale.com/hostinfo+ tailscale.com/util/eventbus from tailscale.com/net/netmon+ - tailscale.com/util/execqueue from tailscale.com/appc 💣 tailscale.com/util/hashx from tailscale.com/util/deephash tailscale.com/util/lineiter from tailscale.com/hostinfo+ tailscale.com/util/mak from tailscale.com/health+ @@ -183,7 +182,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa golang.org/x/exp/constraints from tailscale.com/util/winutil+ golang.org/x/exp/maps from tailscale.com/util/syspolicy/setting L golang.org/x/net/bpf from github.com/mdlayher/netlink+ - golang.org/x/net/dns/dnsmessage from tailscale.com/appc+ + golang.org/x/net/dns/dnsmessage from tailscale.com/net/dnscache golang.org/x/net/idna from golang.org/x/crypto/acme/autocert golang.org/x/net/internal/socks from golang.org/x/net/proxy golang.org/x/net/proxy from tailscale.com/net/netns diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index eae1354a1..be9ac3a08 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -679,7 +679,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ sigs.k8s.io/yaml from k8s.io/apimachinery/pkg/runtime/serializer/json+ sigs.k8s.io/yaml/goyaml.v2 from sigs.k8s.io/yaml+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal 💣 tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/local from tailscale.com/client/tailscale+ tailscale.com/client/tailscale from tailscale.com/cmd/k8s-operator+ @@ -802,7 +802,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/ tailscale.com/tstime/rate from tailscale.com/wgengine/filter tailscale.com/tsweb from tailscale.com/util/eventbus tailscale.com/tsweb/varz from tailscale.com/util/usermetric+ - tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal + tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal+ tailscale.com/types/bools from tailscale.com/tsnet tailscale.com/types/dnstype from tailscale.com/ipn/ipnlocal+ tailscale.com/types/empty from tailscale.com/ipn+ diff --git a/cmd/tailscale/cli/appcroutes.go b/cmd/tailscale/cli/appcroutes.go index 83443f56c..4a1ba87e3 100644 --- a/cmd/tailscale/cli/appcroutes.go +++ b/cmd/tailscale/cli/appcroutes.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/peterbourgon/ff/v3/ffcli" - "tailscale.com/appc" + "tailscale.com/types/appctype" ) var appcRoutesArgs struct { @@ -51,7 +51,7 @@ https://tailscale.com/kb/1281/app-connectors `), } -func getAllOutput(ri *appc.RouteInfo) (string, error) { +func getAllOutput(ri *appctype.RouteInfo) (string, error) { domains, err := json.MarshalIndent(ri.Domains, " ", " ") if err != nil { return "", err @@ -76,7 +76,7 @@ type domainCount struct { count int } -func getSummarizeLearnedOutput(ri *appc.RouteInfo) string { +func getSummarizeLearnedOutput(ri *appctype.RouteInfo) string { x := make([]domainCount, len(ri.Domains)) i := 0 maxDomainWidth := 0 diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 6facd19f9..7b32fc2b4 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -73,7 +73,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep software.sslmate.com/src/go-pkcs12 from tailscale.com/cmd/tailscale/cli software.sslmate.com/src/go-pkcs12/internal/rc2 from software.sslmate.com/src/go-pkcs12 tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/client/local+ 💣 tailscale.com/atomicfile from tailscale.com/cmd/tailscale/cli+ tailscale.com/client/local from tailscale.com/client/tailscale+ L tailscale.com/client/systray from tailscale.com/cmd/tailscale/cli @@ -150,6 +149,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/tstime/rate from tailscale.com/cmd/tailscale/cli tailscale.com/tsweb from tailscale.com/util/eventbus tailscale.com/tsweb/varz from tailscale.com/util/usermetric+ + tailscale.com/types/appctype from tailscale.com/client/local+ tailscale.com/types/dnstype from tailscale.com/tailcfg+ tailscale.com/types/empty from tailscale.com/ipn tailscale.com/types/ipproto from tailscale.com/ipn+ @@ -175,7 +175,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+ tailscale.com/util/eventbus from tailscale.com/client/local+ - tailscale.com/util/execqueue from tailscale.com/appc tailscale.com/util/groupmember from tailscale.com/client/web 💣 tailscale.com/util/hashx from tailscale.com/util/deephash tailscale.com/util/httpm from tailscale.com/client/tailscale+ @@ -232,7 +231,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep L golang.org/x/image/math/f64 from github.com/fogleman/gg+ L golang.org/x/image/math/fixed from github.com/fogleman/gg+ golang.org/x/net/bpf from github.com/mdlayher/netlink+ - golang.org/x/net/dns/dnsmessage from tailscale.com/appc+ + golang.org/x/net/dns/dnsmessage from tailscale.com/cmd/tailscale/cli+ golang.org/x/net/http/httpproxy from tailscale.com/net/tshttpproxy golang.org/x/net/icmp from tailscale.com/net/ping golang.org/x/net/idna from golang.org/x/net/http/httpproxy+ diff --git a/cmd/tailscaled/depaware-min.txt b/cmd/tailscaled/depaware-min.txt index be13c7b68..ba35ecd4a 100644 --- a/cmd/tailscaled/depaware-min.txt +++ b/cmd/tailscaled/depaware-min.txt @@ -35,7 +35,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 💣 go4.org/mem from tailscale.com/control/controlbase+ go4.org/netipx from tailscale.com/ipn/ipnlocal+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/tailscale/apitype from tailscale.com/ipn/ipnauth+ tailscale.com/cmd/tailscaled/childproc from tailscale.com/cmd/tailscaled @@ -126,7 +126,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/tstime/mono from tailscale.com/net/tstun+ tailscale.com/tstime/rate from tailscale.com/wgengine/filter tailscale.com/tsweb/varz from tailscale.com/cmd/tailscaled+ - tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal + tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal+ tailscale.com/types/dnstype from tailscale.com/client/tailscale/apitype+ tailscale.com/types/empty from tailscale.com/ipn+ tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index a91aa8afd..e98c0da48 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -48,7 +48,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 💣 go4.org/mem from tailscale.com/control/controlbase+ go4.org/netipx from tailscale.com/ipn/ipnlocal+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/local from tailscale.com/client/tailscale+ tailscale.com/client/tailscale from tailscale.com/internal/client/tailscale @@ -151,7 +151,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/tstime/mono from tailscale.com/net/tstun+ tailscale.com/tstime/rate from tailscale.com/wgengine/filter tailscale.com/tsweb/varz from tailscale.com/cmd/tailscaled+ - tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal + tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal+ tailscale.com/types/dnstype from tailscale.com/client/tailscale/apitype+ tailscale.com/types/empty from tailscale.com/ipn+ tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 00c1a0ac4..21e333af7 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -240,7 +240,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de gvisor.dev/gvisor/pkg/tcpip/transport/udp from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal 💣 tailscale.com/atomicfile from tailscale.com/ipn+ LD tailscale.com/chirp from tailscale.com/cmd/tailscaled tailscale.com/client/local from tailscale.com/client/web+ @@ -387,7 +387,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/tstime/rate from tailscale.com/wgengine/filter tailscale.com/tsweb from tailscale.com/util/eventbus tailscale.com/tsweb/varz from tailscale.com/cmd/tailscaled+ - tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal + tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal+ tailscale.com/types/dnstype from tailscale.com/ipn/ipnlocal+ tailscale.com/types/empty from tailscale.com/ipn+ tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled diff --git a/cmd/tsidp/depaware.txt b/cmd/tsidp/depaware.txt index 4ddc5eda1..dfd338410 100644 --- a/cmd/tsidp/depaware.txt +++ b/cmd/tsidp/depaware.txt @@ -121,7 +121,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar gvisor.dev/gvisor/pkg/tcpip/transport/udp from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal 💣 tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/local from tailscale.com/client/web+ tailscale.com/client/tailscale from tailscale.com/internal/client/tailscale @@ -229,7 +229,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar tailscale.com/tstime/rate from tailscale.com/wgengine/filter tailscale.com/tsweb from tailscale.com/util/eventbus tailscale.com/tsweb/varz from tailscale.com/tsweb+ - tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal + tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal+ tailscale.com/types/bools from tailscale.com/tsnet tailscale.com/types/dnstype from tailscale.com/client/local+ tailscale.com/types/empty from tailscale.com/ipn+ diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 9e2fbb999..2af78b2be 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1108,6 +1108,7 @@ func (b *LocalBackend) Shutdown() { if b.notifyCancel != nil { b.notifyCancel() } + b.appConnector.Close() b.mu.Unlock() b.webClientShutdown() @@ -4783,25 +4784,28 @@ func (b *LocalBackend) reconfigAppConnectorLocked(nm *netmap.NetworkMap, prefs i }() if !prefs.AppConnector().Advertise { + b.appConnector.Close() // clean up a previous connector (safe on nil) b.appConnector = nil return } shouldAppCStoreRoutes := b.ControlKnobs().AppCStoreRoutes.Load() if b.appConnector == nil || b.appConnector.ShouldStoreRoutes() != shouldAppCStoreRoutes { - var ri *appc.RouteInfo - var storeFunc func(*appc.RouteInfo) error + var ri *appctype.RouteInfo + var storeFunc func(*appctype.RouteInfo) error if shouldAppCStoreRoutes { var err error ri, err = b.readRouteInfoLocked() if err != nil { - ri = &appc.RouteInfo{} + ri = &appctype.RouteInfo{} if err != ipn.ErrStateNotExist { b.logf("Unsuccessful Read RouteInfo: ", err) } } storeFunc = b.storeRouteInfo } + + b.appConnector.Close() // clean up a previous connector (safe on nil) b.appConnector = appc.NewAppConnector(appc.Config{ Logf: b.logf, EventBus: b.sys.Bus.Get(), @@ -6988,7 +6992,7 @@ func namespaceKeyForCurrentProfile(pm *profileManager, key ipn.StateKey) ipn.Sta const routeInfoStateStoreKey ipn.StateKey = "_routeInfo" -func (b *LocalBackend) storeRouteInfo(ri *appc.RouteInfo) error { +func (b *LocalBackend) storeRouteInfo(ri *appctype.RouteInfo) error { if !buildfeatures.HasAppConnectors { return feature.ErrUnavailable } @@ -7005,16 +7009,16 @@ func (b *LocalBackend) storeRouteInfo(ri *appc.RouteInfo) error { return b.pm.WriteState(key, bs) } -func (b *LocalBackend) readRouteInfoLocked() (*appc.RouteInfo, error) { +func (b *LocalBackend) readRouteInfoLocked() (*appctype.RouteInfo, error) { if !buildfeatures.HasAppConnectors { return nil, feature.ErrUnavailable } if b.pm.CurrentProfile().ID() == "" { - return &appc.RouteInfo{}, nil + return &appctype.RouteInfo{}, nil } key := namespaceKeyForCurrentProfile(b.pm, routeInfoStateStoreKey) bs, err := b.pm.Store().ReadState(key) - ri := &appc.RouteInfo{} + ri := &appctype.RouteInfo{} if err != nil { return nil, err } @@ -7027,7 +7031,7 @@ func (b *LocalBackend) readRouteInfoLocked() (*appc.RouteInfo, error) { // ReadRouteInfo returns the app connector route information that is // stored in prefs to be consistent across restarts. It should be up // to date with the RouteInfo in memory being used by appc. -func (b *LocalBackend) ReadRouteInfo() (*appc.RouteInfo, error) { +func (b *LocalBackend) ReadRouteInfo() (*appctype.RouteInfo, error) { b.mu.Lock() defer b.mu.Unlock() return b.readRouteInfoLocked() diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index 6737266be..a662793db 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -49,6 +49,7 @@ import ( "tailscale.com/tsd" "tailscale.com/tstest" "tailscale.com/tstest/deptest" + "tailscale.com/types/appctype" "tailscale.com/types/dnstype" "tailscale.com/types/ipproto" "tailscale.com/types/key" @@ -74,7 +75,7 @@ import ( "tailscale.com/wgengine/wgcfg" ) -func fakeStoreRoutes(*appc.RouteInfo) error { return nil } +func fakeStoreRoutes(*appctype.RouteInfo) error { return nil } func inRemove(ip netip.Addr) bool { for _, pfx := range removeFromDefaultRoute { @@ -2314,7 +2315,7 @@ func TestOfferingAppConnector(t *testing.T) { rc := &appctest.RouteCollector{} if shouldStore { b.appConnector = appc.NewAppConnector(appc.Config{ - Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, RouteInfo: &appc.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, + Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { b.appConnector = appc.NewAppConnector(appc.Config{Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc}) @@ -2381,7 +2382,7 @@ func TestObserveDNSResponse(t *testing.T) { Logf: t.Logf, EventBus: bus, RouteAdvertiser: rc, - RouteInfo: &appc.RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { @@ -2548,7 +2549,7 @@ func TestBackfillAppConnectorRoutes(t *testing.T) { // Store the test IP in profile data, but not in Prefs.AdvertiseRoutes. b.ControlKnobs().AppCStoreRoutes.Store(true) - if err := b.storeRouteInfo(&appc.RouteInfo{ + if err := b.storeRouteInfo(&appctype.RouteInfo{ Domains: map[string][]netip.Addr{ "example.com": {ip}, }, @@ -5501,10 +5502,10 @@ func TestReadWriteRouteInfo(t *testing.T) { b.pm.currentProfile = prof1.View() // set up routeInfo - ri1 := &appc.RouteInfo{} + ri1 := &appctype.RouteInfo{} ri1.Wildcards = []string{"1"} - ri2 := &appc.RouteInfo{} + ri2 := &appctype.RouteInfo{} ri2.Wildcards = []string{"2"} // read before write diff --git a/ipn/ipnlocal/peerapi_test.go b/ipn/ipnlocal/peerapi_test.go index 43b3c49fc..a16d55b8c 100644 --- a/ipn/ipnlocal/peerapi_test.go +++ b/ipn/ipnlocal/peerapi_test.go @@ -23,6 +23,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/tsd" "tailscale.com/tstest" + "tailscale.com/types/appctype" "tailscale.com/types/logger" "tailscale.com/types/netmap" "tailscale.com/util/eventbus/eventbustest" @@ -261,7 +262,7 @@ func TestPeerAPIPrettyReplyCNAME(t *testing.T) { Logf: t.Logf, EventBus: sys.Bus.Get(), RouteAdvertiser: &appctest.RouteCollector{}, - RouteInfo: &appc.RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { @@ -346,7 +347,7 @@ func TestPeerAPIReplyToDNSQueriesAreObserved(t *testing.T) { Logf: t.Logf, EventBus: sys.Bus.Get(), RouteAdvertiser: rc, - RouteInfo: &appc.RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { @@ -419,7 +420,7 @@ func TestPeerAPIReplyToDNSQueriesAreObservedWithCNAMEFlattening(t *testing.T) { Logf: t.Logf, EventBus: sys.Bus.Get(), RouteAdvertiser: rc, - RouteInfo: &appc.RouteInfo{}, + RouteInfo: &appctype.RouteInfo{}, StoreRoutesFunc: fakeStoreRoutes, }) } else { diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index b07df8b02..723081e62 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -23,7 +23,6 @@ import ( "time" "golang.org/x/net/dns/dnsmessage" - "tailscale.com/appc" "tailscale.com/client/tailscale/apitype" "tailscale.com/envknob" "tailscale.com/feature" @@ -38,6 +37,7 @@ import ( "tailscale.com/net/netutil" "tailscale.com/tailcfg" "tailscale.com/tstime" + "tailscale.com/types/appctype" "tailscale.com/types/key" "tailscale.com/types/logger" "tailscale.com/types/logid" @@ -1684,7 +1684,7 @@ func (h *Handler) serveGetAppcRouteInfo(w http.ResponseWriter, r *http.Request) res, err := h.b.ReadRouteInfo() if err != nil { if errors.Is(err, ipn.ErrStateNotExist) { - res = &appc.RouteInfo{} + res = &appctype.RouteInfo{} } else { WriteErrorJSON(w, err) return diff --git a/tsnet/depaware.txt b/tsnet/depaware.txt index a0d9f9ebb..739d0b33b 100644 --- a/tsnet/depaware.txt +++ b/tsnet/depaware.txt @@ -117,7 +117,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) gvisor.dev/gvisor/pkg/tcpip/transport/udp from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context+ tailscale.com from tailscale.com/version - tailscale.com/appc from tailscale.com/ipn/ipnlocal+ + tailscale.com/appc from tailscale.com/ipn/ipnlocal 💣 tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/client/local from tailscale.com/client/web+ tailscale.com/client/tailscale from tailscale.com/internal/client/tailscale @@ -224,7 +224,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware) tailscale.com/tstime/rate from tailscale.com/wgengine/filter LDW tailscale.com/tsweb from tailscale.com/util/eventbus tailscale.com/tsweb/varz from tailscale.com/tsweb+ - tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal + tailscale.com/types/appctype from tailscale.com/ipn/ipnlocal+ tailscale.com/types/bools from tailscale.com/tsnet tailscale.com/types/dnstype from tailscale.com/client/local+ tailscale.com/types/empty from tailscale.com/ipn+ diff --git a/types/appctype/appconnector.go b/types/appctype/appconnector.go index f4ced65a4..567ab755f 100644 --- a/types/appctype/appconnector.go +++ b/types/appctype/appconnector.go @@ -73,3 +73,23 @@ type AppConnectorAttr struct { // tag of the form tag:. Connectors []string `json:"connectors,omitempty"` } + +// RouteInfo is a data structure used to persist the in memory state of an AppConnector +// so that we can know, even after a restart, which routes came from ACLs and which were +// learned from domains. +type RouteInfo struct { + // Control is the routes from the 'routes' section of an app connector acl. + Control []netip.Prefix `json:",omitempty"` + // Domains are the routes discovered by observing DNS lookups for configured domains. + Domains map[string][]netip.Addr `json:",omitempty"` + // Wildcards are the configured DNS lookup domains to observe. When a DNS query matches Wildcards, + // its result is added to Domains. + Wildcards []string `json:",omitempty"` +} + +// RouteUpdate records a set of routes that should be advertised and a set of +// routes that should be unadvertised in event bus updates. +type RouteUpdate struct { + Advertise []netip.Prefix + Unadvertise []netip.Prefix +}