diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index d877c8d03..37be49ca5 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1544,6 +1544,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error { hostinfo.FrontendLogID = opts.FrontendLogID hostinfo.Userspace.Set(b.sys.IsNetstack()) hostinfo.UserspaceRouter.Set(b.sys.IsNetstackRouter()) + hostinfo.AppConnector.Set(b.appConnector != nil) b.logf.JSON(1, "Hostinfo", hostinfo) // TODO(apenwarr): avoid the need to reinit controlclient. @@ -3270,6 +3271,11 @@ func (b *LocalBackend) blockEngineUpdates(block bool) { // b.mu must be held. func (b *LocalBackend) reconfigAppConnectorLocked(nm *netmap.NetworkMap, prefs ipn.PrefsView) { const appConnectorCapName = "tailscale.com/app-connectors" + defer func() { + if b.hostinfo != nil { + b.hostinfo.AppConnector.Set(b.appConnector != nil) + } + }() if !prefs.AppConnector().Advertise { var old *appc.AppConnector diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index 22f3b89f3..6be74caab 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -1263,6 +1263,9 @@ func TestReconfigureAppConnector(t *testing.T) { if !foundAppConnectorService { t.Fatalf("expected app connector service") } + if v, _ := b.hostinfo.AppConnector.Get(); !v { + t.Fatalf("expected app connector service") + } // disable the connector in order to assert that the service is removed b.EditPrefs(&ipn.MaskedPrefs{ @@ -1288,6 +1291,9 @@ func TestReconfigureAppConnector(t *testing.T) { if foundAppConnectorService { t.Fatalf("expected no app connector service") } + if v, _ := b.hostinfo.AppConnector.Get(); v { + t.Fatalf("expected no app connector service") + } } func hasAppConnectorService(b *LocalBackend) bool { diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 54f3b24f7..c25d4aaca 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -624,11 +624,12 @@ func (h *Hostinfo) CheckRequestTags() error { type ServiceProto string const ( - TCP = ServiceProto("tcp") - UDP = ServiceProto("udp") - PeerAPI4 = ServiceProto("peerapi4") - PeerAPI6 = ServiceProto("peerapi6") - PeerAPIDNS = ServiceProto("peerapi-dns-proxy") + TCP = ServiceProto("tcp") + UDP = ServiceProto("udp") + PeerAPI4 = ServiceProto("peerapi4") + PeerAPI6 = ServiceProto("peerapi6") + PeerAPIDNS = ServiceProto("peerapi-dns-proxy") + // Deprecated: use the field on HostInfo instead. AppConnector = ServiceProto("app-connector") ) @@ -650,9 +651,9 @@ type Service struct { // being a DNS proxy (when the node is an exit // node). For this service, the Port number is really // the version number of the service. - // * "app-connector": the local app-connector service is - // available. For this service, the Port number is - // really the version number of the service. + // * "app-connector": (deprecated) the local app-connector + // service is available. For this service, the Port number + // is really the version number of the service. Proto ServiceProto // Port is the port number. @@ -748,6 +749,7 @@ type Hostinfo struct { Cloud string `json:",omitempty"` Userspace opt.Bool `json:",omitempty"` // if the client is running in userspace (netstack) mode UserspaceRouter opt.Bool `json:",omitempty"` // if the client's subnet router is running in userspace (netstack) mode + AppConnector opt.Bool `json:",omitempty"` // if the client is running the app-connector service // Location represents geographical location data about a // Tailscale host. Location is optional and only set if diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go index c652fcb1f..122e57edc 100644 --- a/tailcfg/tailcfg_clone.go +++ b/tailcfg/tailcfg_clone.go @@ -177,6 +177,7 @@ var _HostinfoCloneNeedsRegeneration = Hostinfo(struct { Cloud string Userspace opt.Bool UserspaceRouter opt.Bool + AppConnector opt.Bool Location *Location }{}) diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 261008fc6..142c6e5b8 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -17,6 +17,7 @@ import ( . "tailscale.com/tailcfg" "tailscale.com/types/key" + "tailscale.com/types/opt" "tailscale.com/types/ptr" "tailscale.com/util/must" ) @@ -64,6 +65,7 @@ func TestHostinfoEqual(t *testing.T) { "Cloud", "Userspace", "UserspaceRouter", + "AppConnector", "Location", } if have := fieldsOf(reflect.TypeOf(Hostinfo{})); !reflect.DeepEqual(have, hiHandles) { @@ -228,6 +230,16 @@ func TestHostinfoEqual(t *testing.T) { &Hostinfo{App: "golink"}, true, }, + { + &Hostinfo{AppConnector: opt.Bool("true")}, + &Hostinfo{AppConnector: opt.Bool("true")}, + true, + }, + { + &Hostinfo{AppConnector: opt.Bool("true")}, + &Hostinfo{AppConnector: opt.Bool("false")}, + false, + }, } for i, tt := range tests { got := tt.a.Equal(tt.b) diff --git a/tailcfg/tailcfg_view.go b/tailcfg/tailcfg_view.go index c374abf81..ac4d58030 100644 --- a/tailcfg/tailcfg_view.go +++ b/tailcfg/tailcfg_view.go @@ -317,6 +317,7 @@ func (v HostinfoView) SSH_HostKeys() views.Slice[string] { return views.Sli func (v HostinfoView) Cloud() string { return v.ж.Cloud } func (v HostinfoView) Userspace() opt.Bool { return v.ж.Userspace } func (v HostinfoView) UserspaceRouter() opt.Bool { return v.ж.UserspaceRouter } +func (v HostinfoView) AppConnector() opt.Bool { return v.ж.AppConnector } func (v HostinfoView) Location() *Location { if v.ж.Location == nil { return nil @@ -363,6 +364,7 @@ var _HostinfoViewNeedsRegeneration = Hostinfo(struct { Cloud string Userspace opt.Bool UserspaceRouter opt.Bool + AppConnector opt.Bool Location *Location }{})