portlist_linux.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package portlist
  4. import (
  5. "bufio"
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/fs"
  11. "log"
  12. "os"
  13. "path/filepath"
  14. "runtime"
  15. "strings"
  16. "syscall"
  17. "time"
  18. "unsafe"
  19. "go4.org/mem"
  20. "golang.org/x/sys/unix"
  21. "tailscale.com/util/dirwalk"
  22. "tailscale.com/util/mak"
  23. )
  24. func init() {
  25. newOSImpl = newLinuxImpl
  26. // Reading the sockfiles on Linux is very fast, so we can do it often.
  27. pollInterval = 1 * time.Second
  28. }
  29. type linuxImpl struct {
  30. procNetFiles []*os.File // seeked to start & reused between calls
  31. readlinkPathBuf []byte
  32. known map[string]*portMeta // inode string => metadata
  33. br *bufio.Reader
  34. includeLocalhost bool
  35. }
  36. type portMeta struct {
  37. port Port
  38. pid int
  39. keep bool
  40. needsProcName bool
  41. }
  42. func newLinuxImplBase(includeLocalhost bool) *linuxImpl {
  43. return &linuxImpl{
  44. br: bufio.NewReader(eofReader),
  45. known: map[string]*portMeta{},
  46. includeLocalhost: includeLocalhost,
  47. }
  48. }
  49. func newLinuxImpl(includeLocalhost bool) osImpl {
  50. li := newLinuxImplBase(includeLocalhost)
  51. for _, name := range []string{
  52. "/proc/net/tcp",
  53. "/proc/net/tcp6",
  54. "/proc/net/udp",
  55. "/proc/net/udp6",
  56. } {
  57. f, err := os.Open(name)
  58. if err != nil {
  59. if os.IsNotExist(err) {
  60. continue
  61. }
  62. log.Printf("portlist warning; ignoring: %v", err)
  63. continue
  64. }
  65. li.procNetFiles = append(li.procNetFiles, f)
  66. }
  67. return li
  68. }
  69. func (li *linuxImpl) Close() error {
  70. for _, f := range li.procNetFiles {
  71. f.Close()
  72. }
  73. li.procNetFiles = nil
  74. return nil
  75. }
  76. const (
  77. v6Localhost = "00000000000000000000000001000000:"
  78. v6Any = "00000000000000000000000000000000:0000"
  79. v4Localhost = "0100007F:"
  80. v4Any = "00000000:0000"
  81. )
  82. var eofReader = bytes.NewReader(nil)
  83. func (li *linuxImpl) AppendListeningPorts(base []Port) ([]Port, error) {
  84. if runtime.GOOS == "android" {
  85. // Android 10+ doesn't allow access to this anymore.
  86. // https://developer.android.com/about/versions/10/privacy/changes#proc-net-filesystem
  87. // Ignore it rather than have the system log about our violation.
  88. return nil, nil
  89. }
  90. br := li.br
  91. defer br.Reset(eofReader)
  92. // Start by marking all previous known ports as gone. If this mark
  93. // bit is still false later, we'll remove them.
  94. for _, pm := range li.known {
  95. pm.keep = false
  96. }
  97. for _, f := range li.procNetFiles {
  98. name := f.Name()
  99. _, err := f.Seek(0, io.SeekStart)
  100. if err != nil {
  101. return nil, err
  102. }
  103. br.Reset(f)
  104. err = li.parseProcNetFile(br, filepath.Base(name))
  105. if err != nil {
  106. return nil, fmt.Errorf("parsing %q: %w", name, err)
  107. }
  108. }
  109. // Delete ports that aren't open any longer.
  110. // And see if there are any process names we need to look for.
  111. var needProc map[string]*portMeta
  112. for inode, pm := range li.known {
  113. if !pm.keep {
  114. delete(li.known, inode)
  115. continue
  116. }
  117. if pm.needsProcName {
  118. mak.Set(&needProc, inode, pm)
  119. }
  120. }
  121. err := li.findProcessNames(needProc)
  122. if err != nil {
  123. return nil, err
  124. }
  125. ret := base
  126. for _, pm := range li.known {
  127. ret = append(ret, pm.port)
  128. }
  129. return sortAndDedup(ret), nil
  130. }
  131. // fileBase is one of "tcp", "tcp6", "udp", "udp6".
  132. func (li *linuxImpl) parseProcNetFile(r *bufio.Reader, fileBase string) error {
  133. proto := strings.TrimSuffix(fileBase, "6")
  134. // skip header row
  135. _, err := r.ReadSlice('\n')
  136. if err != nil {
  137. return err
  138. }
  139. fields := make([]mem.RO, 0, 20) // 17 current fields + some future slop
  140. wantRemote := mem.S(v4Any)
  141. if strings.HasSuffix(fileBase, "6") {
  142. wantRemote = mem.S(v6Any)
  143. }
  144. // remoteIndex is the index within a line to the remote address field.
  145. // -1 means not yet found.
  146. remoteIndex := -1
  147. // Add an upper bound on how many rows we'll attempt to read just
  148. // to make sure this doesn't consume too much of their CPU.
  149. // TODO(bradfitz,crawshaw): adaptively adjust polling interval as function
  150. // of open sockets.
  151. const maxRows = 1e6
  152. rows := 0
  153. // Scratch buffer for making inode strings.
  154. inoBuf := make([]byte, 0, 50)
  155. for {
  156. line, err := r.ReadSlice('\n')
  157. if err == io.EOF {
  158. break
  159. }
  160. if err != nil {
  161. return err
  162. }
  163. rows++
  164. if rows >= maxRows {
  165. break
  166. }
  167. if len(line) == 0 {
  168. continue
  169. }
  170. // On the first row of output, find the index of the 3rd field (index 2),
  171. // the remote address. All the rows are aligned, at least until 4 billion open
  172. // TCP connections, per the Linux get_tcp4_sock's "%4d: " on an int i.
  173. if remoteIndex == -1 {
  174. remoteIndex = fieldIndex(line, 2)
  175. if remoteIndex == -1 {
  176. break
  177. }
  178. }
  179. if len(line) < remoteIndex || !mem.HasPrefix(mem.B(line).SliceFrom(remoteIndex), wantRemote) {
  180. // Fast path for not being a listener port.
  181. continue
  182. }
  183. // sl local rem ... inode
  184. fields = mem.AppendFields(fields[:0], mem.B(line))
  185. local := fields[1]
  186. rem := fields[2]
  187. inode := fields[9]
  188. if !rem.Equal(wantRemote) {
  189. // not a "listener" port
  190. continue
  191. }
  192. // If a port is bound to localhost, ignore it.
  193. // TODO: localhost is bigger than 1 IP, we need to ignore
  194. // more things.
  195. if !li.includeLocalhost && (mem.HasPrefix(local, mem.S(v4Localhost)) || mem.HasPrefix(local, mem.S(v6Localhost))) {
  196. continue
  197. }
  198. // Don't use strings.Split here, because it causes
  199. // allocations significant enough to show up in profiles.
  200. i := mem.IndexByte(local, ':')
  201. if i == -1 {
  202. return fmt.Errorf("%q unexpectedly didn't have a colon", local.StringCopy())
  203. }
  204. portv, err := mem.ParseUint(local.SliceFrom(i+1), 16, 16)
  205. if err != nil {
  206. return fmt.Errorf("%#v: %s", local.SliceFrom(9).StringCopy(), err)
  207. }
  208. inoBuf = append(inoBuf[:0], "socket:["...)
  209. inoBuf = mem.Append(inoBuf, inode)
  210. inoBuf = append(inoBuf, ']')
  211. if pm, ok := li.known[string(inoBuf)]; ok {
  212. pm.keep = true
  213. // Rest should be unchanged.
  214. } else {
  215. li.known[string(inoBuf)] = &portMeta{
  216. needsProcName: true,
  217. keep: true,
  218. port: Port{
  219. Proto: proto,
  220. Port: uint16(portv),
  221. },
  222. }
  223. }
  224. }
  225. return nil
  226. }
  227. // errDone is an internal sentinel error that we found everything we were looking for.
  228. var errDone = errors.New("done")
  229. // need is keyed by inode string.
  230. func (li *linuxImpl) findProcessNames(need map[string]*portMeta) error {
  231. if len(need) == 0 {
  232. return nil
  233. }
  234. defer func() {
  235. // Anything we didn't find, give up on and don't try to look for it later.
  236. for _, pm := range need {
  237. pm.needsProcName = false
  238. }
  239. }()
  240. err := foreachPID(func(pid mem.RO) error {
  241. var procBuf [128]byte
  242. fdPath := mem.Append(procBuf[:0], mem.S("/proc/"))
  243. fdPath = mem.Append(fdPath, pid)
  244. fdPath = mem.Append(fdPath, mem.S("/fd"))
  245. // Android logs a bunch of audit violations in logcat
  246. // if we try to open things we don't have access
  247. // to. So on Android only, ask if we have permission
  248. // rather than just trying it to determine whether we
  249. // have permission.
  250. if runtime.GOOS == "android" && syscall.Access(string(fdPath), unix.R_OK) != nil {
  251. return nil
  252. }
  253. dirwalk.WalkShallow(mem.B(fdPath), func(fd mem.RO, de fs.DirEntry) error {
  254. targetBuf := make([]byte, 64) // plenty big for "socket:[165614651]"
  255. linkPath := li.readlinkPathBuf[:0]
  256. linkPath = fmt.Appendf(linkPath, "/proc/")
  257. linkPath = mem.Append(linkPath, pid)
  258. linkPath = append(linkPath, "/fd/"...)
  259. linkPath = mem.Append(linkPath, fd)
  260. linkPath = append(linkPath, 0) // terminating NUL
  261. li.readlinkPathBuf = linkPath // to reuse its buffer next time
  262. n, ok := readlink(linkPath, targetBuf)
  263. if !ok {
  264. // Not a symlink or no permission.
  265. // Skip it.
  266. return nil
  267. }
  268. pe := need[string(targetBuf[:n])] // m[string([]byte)] avoids alloc
  269. if pe == nil {
  270. return nil
  271. }
  272. bs, err := os.ReadFile(fmt.Sprintf("/proc/%s/cmdline", pid.StringCopy()))
  273. if err != nil {
  274. // Usually shouldn't happen. One possibility is
  275. // the process has gone away, so let's skip it.
  276. return nil
  277. }
  278. argv := strings.Split(strings.TrimSuffix(string(bs), "\x00"), "\x00")
  279. if p, err := mem.ParseInt(pid, 10, 0); err == nil {
  280. pe.pid = int(p)
  281. }
  282. pe.port.Process = argvSubject(argv...)
  283. pid64, _ := mem.ParseInt(pid, 10, 0)
  284. pe.port.Pid = int(pid64)
  285. pe.needsProcName = false
  286. delete(need, string(targetBuf[:n]))
  287. if len(need) == 0 {
  288. return errDone
  289. }
  290. return nil
  291. })
  292. return nil
  293. })
  294. if err == errDone {
  295. return nil
  296. }
  297. return err
  298. }
  299. func foreachPID(fn func(pidStr mem.RO) error) error {
  300. err := dirwalk.WalkShallow(mem.S("/proc"), func(name mem.RO, de fs.DirEntry) error {
  301. if !isNumeric(name) {
  302. return nil
  303. }
  304. return fn(name)
  305. })
  306. if os.IsNotExist(err) {
  307. // This can happen if the directory we're
  308. // reading disappears during the run. No big
  309. // deal.
  310. return nil
  311. }
  312. return err
  313. }
  314. func isNumeric(s mem.RO) bool {
  315. for i, n := 0, s.Len(); i < n; i++ {
  316. b := s.At(i)
  317. if b < '0' || b > '9' {
  318. return false
  319. }
  320. }
  321. return s.Len() > 0
  322. }
  323. // fieldIndex returns the offset in line where the Nth field (0-based) begins, or -1
  324. // if there aren't that many fields. Fields are separated by 1 or more spaces.
  325. func fieldIndex(line []byte, n int) int {
  326. skip := 0
  327. for i := 0; i <= n; i++ {
  328. // Skip spaces.
  329. for skip < len(line) && line[skip] == ' ' {
  330. skip++
  331. }
  332. if skip == len(line) {
  333. return -1
  334. }
  335. if i == n {
  336. break
  337. }
  338. // Skip non-space.
  339. for skip < len(line) && line[skip] != ' ' {
  340. skip++
  341. }
  342. }
  343. return skip
  344. }
  345. // path must be null terminated.
  346. func readlink(path, buf []byte) (n int, ok bool) {
  347. if len(buf) == 0 || len(path) < 2 || path[len(path)-1] != 0 {
  348. return 0, false
  349. }
  350. var dirfd int = unix.AT_FDCWD
  351. r0, _, e1 := unix.Syscall6(unix.SYS_READLINKAT,
  352. uintptr(dirfd),
  353. uintptr(unsafe.Pointer(&path[0])),
  354. uintptr(unsafe.Pointer(&buf[0])),
  355. uintptr(len(buf)),
  356. 0, 0)
  357. n = int(r0)
  358. if e1 != 0 {
  359. return 0, false
  360. }
  361. return n, true
  362. }