diff --git a/hostinfo/hostinfo.go b/hostinfo/hostinfo.go index 50ce6c8966b57..e56eb69a1c77a 100644 --- a/hostinfo/hostinfo.go +++ b/hostinfo/hostinfo.go @@ -17,11 +17,14 @@ import ( "go4.org/mem" "tailscale.com/tailcfg" + "tailscale.com/types/opt" "tailscale.com/util/dnsname" "tailscale.com/util/lineread" "tailscale.com/version" ) +var started = time.Now() + // New returns a partially populated Hostinfo for the current host. func New() *tailcfg.Hostinfo { hostname, _ := os.Hostname() @@ -31,6 +34,7 @@ func New() *tailcfg.Hostinfo { Hostname: hostname, OS: version.OS(), OSVersion: GetOSVersion(), + Desktop: desktop(), Package: packageTypeCached(), GoArch: runtime.GOARCH, DeviceModel: deviceModel(), @@ -97,6 +101,7 @@ func GetEnvType() EnvType { var ( deviceModelAtomic atomic.Value // of string osVersionAtomic atomic.Value // of string + desktopAtomic atomic.Value // of opt.Bool packagingType atomic.Value // of string ) @@ -106,6 +111,9 @@ func SetDeviceModel(model string) { deviceModelAtomic.Store(model) } // SetOSVersion sets the OS version. func SetOSVersion(v string) { osVersionAtomic.Store(v) } +// SetDesktop sets the desktop for use in HostInfo updates. +func SetDesktop(d opt.Bool) { desktopAtomic.Store(d) } + // SetPackage sets the packaging type for the app. // // As of 2022-03-25, this is used by Android ("nogoogle" for the @@ -117,6 +125,31 @@ func deviceModel() string { return s } +func desktop() (ret opt.Bool) { + if runtime.GOOS != "linux" { + return opt.Bool("") + } + if v := desktopAtomic.Load(); v != nil { + v, _ := v.(opt.Bool) + return v + } + + seenDesktop := false + lineread.File("/proc/net/unix", func(line []byte) error { + seenDesktop = seenDesktop || strings.Contains(string(line), " @/tmp/dbus-") + seenDesktop = seenDesktop || strings.Contains(string(line), ".X11-unix") + seenDesktop = seenDesktop || strings.Contains(string(line), "/wayland-1") + return nil + }) + ret.Set(seenDesktop) + + // Only cache after a minute - compositors might not have started yet. + if time.Now().After(started.Add(time.Minute)) { + desktopAtomic.Store(ret) + } + return ret +} + func getEnvType() EnvType { if inKnative() { return KNative diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index c6b556d69278f..b2b64d0991715 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -460,6 +460,7 @@ type Hostinfo struct { BackendLogID string `json:",omitempty"` // logtail ID of backend instance OS string `json:",omitempty"` // operating system the client runs on (a version.OS value) OSVersion string `json:",omitempty"` // operating system version, with optional distro prefix ("Debian 10.4", "Windows 10 Pro 10.0.19041") + Desktop opt.Bool `json:",omitempty"` // if a desktop was detected on Linux Package string `json:",omitempty"` // Tailscale package to disambiguate ("choco", "appstore", etc; "" for unknown) DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone12,3") Hostname string `json:",omitempty"` // name of the host the client runs on diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go index b1a3572ba4104..c94e8d5b4bee5 100644 --- a/tailcfg/tailcfg_clone.go +++ b/tailcfg/tailcfg_clone.go @@ -117,6 +117,7 @@ var _HostinfoCloneNeedsRegeneration = Hostinfo(struct { BackendLogID string OS string OSVersion string + Desktop opt.Bool Package string DeviceModel string Hostname string diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 6d580064b8981..e5135a407a086 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -28,7 +28,7 @@ func fieldsOf(t reflect.Type) (fields []string) { func TestHostinfoEqual(t *testing.T) { hiHandles := []string{ "IPNVersion", "FrontendLogID", "BackendLogID", - "OS", "OSVersion", "Package", "DeviceModel", "Hostname", + "OS", "OSVersion", "Desktop", "Package", "DeviceModel", "Hostname", "ShieldsUp", "ShareeNode", "GoArch", "RoutableIPs", "RequestTags",