hostinfo, tailcfg: add desktop detection on Linux to hostinfo

From the machines tab its hard to differenciate desktop Linux installs from
server Linux installs. Transmitting this information should make this
determination a lot easier.

Due to the reality that tailscaled is likely a system process, the standard
checks based on XDG_SESSION_TYPE or DISPLAY environment variables are not
possible (those variables won't be set). Instead, we look for listening
unix sockets that are typical of desktop installs.

Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
Tom DNetto 2022-04-07 11:42:48 -07:00 committed by Brad Fitzpatrick
parent 9f1dd716e8
commit 24cd26534f
4 changed files with 33 additions and 1 deletions

View File

@ -17,11 +17,14 @@
"go4.org/mem" "go4.org/mem"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/opt"
"tailscale.com/util/dnsname" "tailscale.com/util/dnsname"
"tailscale.com/util/lineread" "tailscale.com/util/lineread"
"tailscale.com/version" "tailscale.com/version"
) )
var started = time.Now()
// New returns a partially populated Hostinfo for the current host. // New returns a partially populated Hostinfo for the current host.
func New() *tailcfg.Hostinfo { func New() *tailcfg.Hostinfo {
hostname, _ := os.Hostname() hostname, _ := os.Hostname()
@ -31,6 +34,7 @@ func New() *tailcfg.Hostinfo {
Hostname: hostname, Hostname: hostname,
OS: version.OS(), OS: version.OS(),
OSVersion: GetOSVersion(), OSVersion: GetOSVersion(),
Desktop: desktop(),
Package: packageTypeCached(), Package: packageTypeCached(),
GoArch: runtime.GOARCH, GoArch: runtime.GOARCH,
DeviceModel: deviceModel(), DeviceModel: deviceModel(),
@ -97,6 +101,7 @@ func GetEnvType() EnvType {
var ( var (
deviceModelAtomic atomic.Value // of string deviceModelAtomic atomic.Value // of string
osVersionAtomic atomic.Value // of string osVersionAtomic atomic.Value // of string
desktopAtomic atomic.Value // of opt.Bool
packagingType atomic.Value // of string packagingType atomic.Value // of string
) )
@ -117,6 +122,31 @@ func deviceModel() string {
return s 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 || mem.Contains(mem.B(line), mem.S(" @/tmp/dbus-"))
seenDesktop = seenDesktop || mem.Contains(mem.B(line), mem.S(".X11-unix"))
seenDesktop = seenDesktop || mem.Contains(mem.B(line), mem.S("/wayland-1"))
return nil
})
ret.Set(seenDesktop)
// Only cache after a minute - compositors might not have started yet.
if time.Since(started) > time.Minute {
desktopAtomic.Store(ret)
}
return ret
}
func getEnvType() EnvType { func getEnvType() EnvType {
if inKnative() { if inKnative() {
return KNative return KNative

View File

@ -460,6 +460,7 @@ type Hostinfo struct {
BackendLogID string `json:",omitempty"` // logtail ID of backend instance BackendLogID string `json:",omitempty"` // logtail ID of backend instance
OS string `json:",omitempty"` // operating system the client runs on (a version.OS value) 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") 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) Package string `json:",omitempty"` // Tailscale package to disambiguate ("choco", "appstore", etc; "" for unknown)
DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone12,3") DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone12,3")
Hostname string `json:",omitempty"` // name of the host the client runs on Hostname string `json:",omitempty"` // name of the host the client runs on

View File

@ -117,6 +117,7 @@ func (src *Hostinfo) Clone() *Hostinfo {
BackendLogID string BackendLogID string
OS string OS string
OSVersion string OSVersion string
Desktop opt.Bool
Package string Package string
DeviceModel string DeviceModel string
Hostname string Hostname string

View File

@ -28,7 +28,7 @@ func fieldsOf(t reflect.Type) (fields []string) {
func TestHostinfoEqual(t *testing.T) { func TestHostinfoEqual(t *testing.T) {
hiHandles := []string{ hiHandles := []string{
"IPNVersion", "FrontendLogID", "BackendLogID", "IPNVersion", "FrontendLogID", "BackendLogID",
"OS", "OSVersion", "Package", "DeviceModel", "Hostname", "OS", "OSVersion", "Desktop", "Package", "DeviceModel", "Hostname",
"ShieldsUp", "ShareeNode", "ShieldsUp", "ShareeNode",
"GoArch", "GoArch",
"RoutableIPs", "RequestTags", "RoutableIPs", "RequestTags",