From 47045265b9b340418fbfe08e50297dc875e54ce9 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 20 Aug 2021 10:34:13 -0700 Subject: [PATCH] hostinfo: add SetDeviceModel setter, move remaining code from controlclient Updates tailscale/corp#1959 Signed-off-by: Brad Fitzpatrick --- cmd/tailscaled/depaware.txt | 4 +- control/controlclient/controlclient_test.go | 7 -- control/controlclient/direct.go | 46 +--------- control/controlclient/direct_test.go | 19 +---- control/controlclient/hostinfo_linux.go | 94 --------------------- control/controlclient/hostinfo_windows.go | 39 --------- hostinfo/hostinfo.go | 62 +++++++++++++- ipn/ipnlocal/local.go | 3 +- tailcfg/tailcfg.go | 2 +- 9 files changed, 69 insertions(+), 207 deletions(-) delete mode 100644 control/controlclient/hostinfo_linux.go delete mode 100644 control/controlclient/hostinfo_windows.go diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 403785ee5..8762d12d0 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -157,7 +157,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/dnsname from tailscale.com/ipn/ipnstate+ LW tailscale.com/util/endian from tailscale.com/net/netns+ tailscale.com/util/groupmember from tailscale.com/ipn/ipnserver - tailscale.com/util/lineread from tailscale.com/control/controlclient+ + tailscale.com/util/lineread from tailscale.com/hostinfo+ tailscale.com/util/osshare from tailscale.com/cmd/tailscaled+ tailscale.com/util/pidowner from tailscale.com/ipn/ipnserver tailscale.com/util/racebuild from tailscale.com/logpolicy @@ -165,7 +165,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/uniq from tailscale.com/wgengine/magicsock tailscale.com/util/winutil from tailscale.com/logpolicy+ tailscale.com/version from tailscale.com/cmd/tailscaled+ - tailscale.com/version/distro from tailscale.com/control/controlclient+ + tailscale.com/version/distro from tailscale.com/cmd/tailscaled+ W tailscale.com/wf from tailscale.com/cmd/tailscaled tailscale.com/wgengine from tailscale.com/cmd/tailscaled+ tailscale.com/wgengine/filter from tailscale.com/control/controlclient+ diff --git a/control/controlclient/controlclient_test.go b/control/controlclient/controlclient_test.go index b7ae18570..d7a19d498 100644 --- a/control/controlclient/controlclient_test.go +++ b/control/controlclient/controlclient_test.go @@ -75,10 +75,3 @@ func TestStatusEqual(t *testing.T) { } } } - -func TestOSVersion(t *testing.T) { - if osVersion == nil { - t.Skip("not available for OS") - } - t.Logf("Got: %#q", osVersion()) -} diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 766971291..07ff950b4 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -20,7 +20,6 @@ import ( "net/url" "os" "os/exec" - "path/filepath" "reflect" "runtime" "strconv" @@ -33,6 +32,7 @@ import ( "inet.af/netaddr" "tailscale.com/control/controlknobs" "tailscale.com/health" + "tailscale.com/hostinfo" "tailscale.com/ipn/ipnstate" "tailscale.com/log/logheap" "tailscale.com/net/dnscache" @@ -47,9 +47,7 @@ import ( "tailscale.com/types/opt" "tailscale.com/types/persist" "tailscale.com/types/wgkey" - "tailscale.com/util/dnsname" "tailscale.com/util/systemd" - "tailscale.com/version" "tailscale.com/wgengine/monitor" ) @@ -184,53 +182,13 @@ func NewDirect(opts Options) (*Direct, error) { pinger: opts.Pinger, } if opts.Hostinfo == nil { - c.SetHostinfo(NewHostinfo()) + c.SetHostinfo(hostinfo.New()) } else { c.SetHostinfo(opts.Hostinfo) } return c, nil } -var osVersion func() string // non-nil on some platforms - -func NewHostinfo() *tailcfg.Hostinfo { - hostname, _ := os.Hostname() - hostname = dnsname.FirstLabel(hostname) - var osv string - if osVersion != nil { - osv = osVersion() - } - return &tailcfg.Hostinfo{ - IPNVersion: version.Long, - Hostname: hostname, - OS: version.OS(), - OSVersion: osv, - Package: packageType(), - GoArch: runtime.GOARCH, - } -} - -func packageType() string { - switch runtime.GOOS { - case "windows": - if _, err := os.Stat(`C:\ProgramData\chocolatey\lib\tailscale`); err == nil { - return "choco" - } - case "darwin": - // Using tailscaled or IPNExtension? - exe, _ := os.Executable() - return filepath.Base(exe) - case "linux": - // Report whether this is in a snap. - // See https://snapcraft.io/docs/environment-variables - // We just look at two somewhat arbitrarily. - if os.Getenv("SNAP_NAME") != "" && os.Getenv("SNAP") != "" { - return "snap" - } - } - return "" -} - // SetHostinfo clones the provided Hostinfo and remembers it for the // next update. It reports whether the Hostinfo has changed. func (c *Direct) SetHostinfo(hi *tailcfg.Hostinfo) bool { diff --git a/control/controlclient/direct_test.go b/control/controlclient/direct_test.go index 206ba5e49..49ce695d8 100644 --- a/control/controlclient/direct_test.go +++ b/control/controlclient/direct_test.go @@ -12,13 +12,14 @@ import ( "time" "inet.af/netaddr" + "tailscale.com/hostinfo" "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" "tailscale.com/types/wgkey" ) func TestNewDirect(t *testing.T) { - hi := NewHostinfo() + hi := hostinfo.New() ni := tailcfg.NetInfo{LinkType: "wired"} hi.NetInfo = &ni @@ -60,7 +61,7 @@ func TestNewDirect(t *testing.T) { if changed { t.Errorf("c.SetHostinfo(hi) want false got %v", changed) } - hi = NewHostinfo() + hi = hostinfo.New() hi.Hostname = "different host name" changed = c.SetHostinfo(hi) if !changed { @@ -96,20 +97,8 @@ func fakeEndpoints(ports ...uint16) (ret []tailcfg.Endpoint) { return } -func TestNewHostinfo(t *testing.T) { - hi := NewHostinfo() - if hi == nil { - t.Fatal("no Hostinfo") - } - j, err := json.MarshalIndent(hi, " ", "") - if err != nil { - t.Fatal(err) - } - t.Logf("Got: %s", j) -} - func TestTsmpPing(t *testing.T) { - hi := NewHostinfo() + hi := hostinfo.New() ni := tailcfg.NetInfo{LinkType: "wired"} hi.NetInfo = &ni diff --git a/control/controlclient/hostinfo_linux.go b/control/controlclient/hostinfo_linux.go deleted file mode 100644 index 9b1ceb4e2..000000000 --- a/control/controlclient/hostinfo_linux.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build linux && !android -// +build linux,!android - -package controlclient - -import ( - "bytes" - "fmt" - "io/ioutil" - "strings" - "syscall" - - "tailscale.com/hostinfo" - "tailscale.com/util/lineread" - "tailscale.com/version/distro" -) - -func init() { - osVersion = osVersionLinux -} - -func osVersionLinux() string { - dist := distro.Get() - propFile := "/etc/os-release" - switch dist { - case distro.Synology: - propFile = "/etc.defaults/VERSION" - case distro.OpenWrt: - propFile = "/etc/openwrt_release" - } - - m := map[string]string{} - lineread.File(propFile, func(line []byte) error { - eq := bytes.IndexByte(line, '=') - if eq == -1 { - return nil - } - k, v := string(line[:eq]), strings.Trim(string(line[eq+1:]), `"'`) - m[k] = v - return nil - }) - - var un syscall.Utsname - syscall.Uname(&un) - - var attrBuf strings.Builder - attrBuf.WriteString("; kernel=") - for _, b := range un.Release { - if b == 0 { - break - } - attrBuf.WriteByte(byte(b)) - } - if hostinfo.InContainer() { - attrBuf.WriteString("; container") - } - if env := hostinfo.GetEnvType(); env != "" { - fmt.Fprintf(&attrBuf, "; env=%s", env) - } - attr := attrBuf.String() - - id := m["ID"] - - switch id { - case "debian": - slurp, _ := ioutil.ReadFile("/etc/debian_version") - return fmt.Sprintf("Debian %s (%s)%s", bytes.TrimSpace(slurp), m["VERSION_CODENAME"], attr) - case "ubuntu": - return fmt.Sprintf("Ubuntu %s%s", m["VERSION"], attr) - case "", "centos": // CentOS 6 has no /etc/os-release, so its id is "" - if cr, _ := ioutil.ReadFile("/etc/centos-release"); len(cr) > 0 { // "CentOS release 6.10 (Final) - return fmt.Sprintf("%s%s", bytes.TrimSpace(cr), attr) - } - fallthrough - case "fedora", "rhel", "alpine", "nixos": - // Their PRETTY_NAME is fine as-is for all versions I tested. - fallthrough - default: - if v := m["PRETTY_NAME"]; v != "" { - return fmt.Sprintf("%s%s", v, attr) - } - } - switch dist { - case distro.Synology: - return fmt.Sprintf("Synology %s%s", m["productversion"], attr) - case distro.OpenWrt: - return fmt.Sprintf("OpenWrt %s%s", m["DISTRIB_RELEASE"], attr) - } - return fmt.Sprintf("Other%s", attr) -} diff --git a/control/controlclient/hostinfo_windows.go b/control/controlclient/hostinfo_windows.go deleted file mode 100644 index 07a5d4006..000000000 --- a/control/controlclient/hostinfo_windows.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package controlclient - -import ( - "os/exec" - "strings" - "sync/atomic" - "syscall" -) - -func init() { - osVersion = osVersionWindows -} - -var winVerCache atomic.Value // of string - -func osVersionWindows() string { - if s, ok := winVerCache.Load().(string); ok { - return s - } - cmd := exec.Command("cmd", "/c", "ver") - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - out, _ := cmd.Output() // "\nMicrosoft Windows [Version 10.0.19041.388]\n\n" - s := strings.TrimSpace(string(out)) - s = strings.TrimPrefix(s, "Microsoft Windows [") - s = strings.TrimSuffix(s, "]") - - // "Version 10.x.y.z", with "Version" localized. Keep only stuff after the space. - if sp := strings.Index(s, " "); sp != -1 { - s = s[sp+1:] - } - if s != "" { - winVerCache.Store(s) - } - return s // "10.0.19041.388", ideally -} diff --git a/hostinfo/hostinfo.go b/hostinfo/hostinfo.go index 794b383af..328ec8c2d 100644 --- a/hostinfo/hostinfo.go +++ b/hostinfo/hostinfo.go @@ -4,20 +4,64 @@ // Package hostinfo answers questions about the host environment that Tailscale is // running on. -// -// TODO(bradfitz): move more of control/controlclient/hostinfo_* into this package. package hostinfo import ( "io" "os" + "path/filepath" "runtime" "sync/atomic" "go4.org/mem" + "tailscale.com/tailcfg" + "tailscale.com/util/dnsname" "tailscale.com/util/lineread" + "tailscale.com/version" ) +var osVersion func() string // non-nil on some platforms + +// New returns a partially populated Hostinfo for the current host. +func New() *tailcfg.Hostinfo { + hostname, _ := os.Hostname() + hostname = dnsname.FirstLabel(hostname) + var osv string + if osVersion != nil { + osv = osVersion() + } + return &tailcfg.Hostinfo{ + IPNVersion: version.Long, + Hostname: hostname, + OS: version.OS(), + OSVersion: osv, + Package: packageType(), + GoArch: runtime.GOARCH, + DeviceModel: deviceModel(), + } +} + +func packageType() string { + switch runtime.GOOS { + case "windows": + if _, err := os.Stat(`C:\ProgramData\chocolatey\lib\tailscale`); err == nil { + return "choco" + } + case "darwin": + // Using tailscaled or IPNExtension? + exe, _ := os.Executable() + return filepath.Base(exe) + case "linux": + // Report whether this is in a snap. + // See https://snapcraft.io/docs/environment-variables + // We just look at two somewhat arbitrarily. + if os.Getenv("SNAP_NAME") != "" && os.Getenv("SNAP") != "" { + return "snap" + } + } + return "" +} + // EnvType represents a known environment type. // The empty string, the default, means unknown. type EnvType string @@ -42,6 +86,16 @@ func GetEnvType() EnvType { return e } +var deviceModelAtomic atomic.Value // of string + +// SetDeviceModel sets the device model for use in Hostinfo updates. +func SetDeviceModel(model string) { deviceModelAtomic.Store(model) } + +func deviceModel() string { + s, _ := deviceModelAtomic.Load().(string) + return s +} + func getEnvType() EnvType { if inKnative() { return KNative @@ -64,8 +118,8 @@ func getEnvType() EnvType { return "" } -// InContainer reports whether we're running in a container. -func InContainer() bool { +// inContainer reports whether we're running in a container. +func inContainer() bool { if runtime.GOOS != "linux" { return false } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 9d54d9a6f..046744404 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -29,6 +29,7 @@ import ( "tailscale.com/client/tailscale/apitype" "tailscale.com/control/controlclient" "tailscale.com/health" + "tailscale.com/hostinfo" "tailscale.com/ipn" "tailscale.com/ipn/ipnstate" "tailscale.com/ipn/policy" @@ -730,7 +731,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error { return nil } - hostinfo := controlclient.NewHostinfo() + hostinfo := hostinfo.New() hostinfo.BackendLogID = b.backendLogID hostinfo.FrontendLogID = opts.FrontendLogID diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index a02761719..dfc4ba21c 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -407,7 +407,7 @@ type Hostinfo struct { OS string // 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") Package string `json:",omitempty"` // Tailscale package to disambiguate ("choco", "appstore", etc; "" for unknown) - DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone 11 Pro") + DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone12,3") Hostname string // name of the host the client runs on ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections ShareeNode bool `json:",omitempty"` // indicates this node exists in netmap because it's owned by a shared-to user