hostinfo_linux.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // +build linux,!android
  5. package controlclient
  6. import (
  7. "bytes"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "os"
  12. "strings"
  13. "syscall"
  14. "go4.org/mem"
  15. "tailscale.com/util/lineread"
  16. "tailscale.com/version/distro"
  17. )
  18. func init() {
  19. osVersion = osVersionLinux
  20. }
  21. func osVersionLinux() string {
  22. dist := distro.Get()
  23. propFile := "/etc/os-release"
  24. switch dist {
  25. case distro.Synology:
  26. propFile = "/etc.defaults/VERSION"
  27. case distro.OpenWrt:
  28. propFile = "/etc/openwrt_release"
  29. }
  30. m := map[string]string{}
  31. lineread.File(propFile, func(line []byte) error {
  32. eq := bytes.IndexByte(line, '=')
  33. if eq == -1 {
  34. return nil
  35. }
  36. k, v := string(line[:eq]), strings.Trim(string(line[eq+1:]), `"'`)
  37. m[k] = v
  38. return nil
  39. })
  40. var un syscall.Utsname
  41. syscall.Uname(&un)
  42. var attrBuf strings.Builder
  43. attrBuf.WriteString("; kernel=")
  44. for _, b := range un.Release {
  45. if b == 0 {
  46. break
  47. }
  48. attrBuf.WriteByte(byte(b))
  49. }
  50. if inContainer() {
  51. attrBuf.WriteString("; container")
  52. }
  53. if inKnative() {
  54. attrBuf.WriteString("; env=kn")
  55. }
  56. attr := attrBuf.String()
  57. id := m["ID"]
  58. switch id {
  59. case "debian":
  60. slurp, _ := ioutil.ReadFile("/etc/debian_version")
  61. return fmt.Sprintf("Debian %s (%s)%s", bytes.TrimSpace(slurp), m["VERSION_CODENAME"], attr)
  62. case "ubuntu":
  63. return fmt.Sprintf("Ubuntu %s%s", m["VERSION"], attr)
  64. case "", "centos": // CentOS 6 has no /etc/os-release, so its id is ""
  65. if cr, _ := ioutil.ReadFile("/etc/centos-release"); len(cr) > 0 { // "CentOS release 6.10 (Final)
  66. return fmt.Sprintf("%s%s", bytes.TrimSpace(cr), attr)
  67. }
  68. fallthrough
  69. case "fedora", "rhel", "alpine", "nixos":
  70. // Their PRETTY_NAME is fine as-is for all versions I tested.
  71. fallthrough
  72. default:
  73. if v := m["PRETTY_NAME"]; v != "" {
  74. return fmt.Sprintf("%s%s", v, attr)
  75. }
  76. }
  77. switch dist {
  78. case distro.Synology:
  79. return fmt.Sprintf("Synology %s%s", m["productversion"], attr)
  80. case distro.OpenWrt:
  81. return fmt.Sprintf("OpenWrt %s%s", m["DISTRIB_RELEASE"], attr)
  82. }
  83. return fmt.Sprintf("Other%s", attr)
  84. }
  85. func inContainer() (ret bool) {
  86. lineread.File("/proc/1/cgroup", func(line []byte) error {
  87. if mem.Contains(mem.B(line), mem.S("/docker/")) ||
  88. mem.Contains(mem.B(line), mem.S("/lxc/")) {
  89. ret = true
  90. return io.EOF // arbitrary non-nil error to stop loop
  91. }
  92. return nil
  93. })
  94. lineread.File("/proc/mounts", func(line []byte) error {
  95. if mem.Contains(mem.B(line), mem.S("fuse.lxcfs")) {
  96. ret = true
  97. return io.EOF
  98. }
  99. return nil
  100. })
  101. return
  102. }
  103. func inKnative() bool {
  104. // https://cloud.google.com/run/docs/reference/container-contract#env-vars
  105. if os.Getenv("K_REVISION") != "" && os.Getenv("K_CONFIGURATION") != "" &&
  106. os.Getenv("K_SERVICE") != "" && os.Getenv("PORT") != "" {
  107. return true
  108. }
  109. return false
  110. }