netstat.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build darwin && !ios
  4. package portlist
  5. import (
  6. "bufio"
  7. "bytes"
  8. "io"
  9. "go4.org/mem"
  10. )
  11. // parsePort returns the port number at the end of s following the last "." or
  12. // ":", whichever comes last. It returns -1 on a parse error or invalid number
  13. // and 0 if the port number was "*".
  14. //
  15. // This is basically net.SplitHostPort except that it handles a "." (as macOS
  16. // and others return in netstat output), uses mem.RO, and validates that the
  17. // port must be numeric and in the uint16 range.
  18. func parsePort(s mem.RO) int {
  19. // a.b.c.d:1234 or [a:b:c:d]:1234
  20. i1 := mem.LastIndexByte(s, ':')
  21. // a.b.c.d.1234 or [a:b:c:d].1234
  22. i2 := mem.LastIndexByte(s, '.')
  23. i := i1
  24. if i2 > i {
  25. i = i2
  26. }
  27. if i < 0 {
  28. // no match; weird
  29. return -1
  30. }
  31. portstr := s.SliceFrom(i + 1)
  32. if portstr.EqualString("*") {
  33. return 0
  34. }
  35. port, err := mem.ParseUint(portstr, 10, 16)
  36. if err != nil {
  37. // invalid port; weird
  38. return -1
  39. }
  40. return int(port)
  41. }
  42. func isLoopbackAddr(s mem.RO) bool {
  43. return mem.HasPrefix(s, mem.S("127.")) ||
  44. mem.HasPrefix(s, mem.S("[::1]:")) ||
  45. mem.HasPrefix(s, mem.S("::1."))
  46. }
  47. // appendParsePortsNetstat appends to base listening ports
  48. // from "netstat" output, read from br. See TestParsePortsNetstat
  49. // for example input lines.
  50. //
  51. // This used to be a lowest common denominator parser for "netstat -na" format.
  52. // All of Linux, Windows, and macOS support -na and give similar-ish output
  53. // formats that we can parse without special detection logic.
  54. // Unfortunately, options to filter by proto or state are non-portable,
  55. // so we'll filter for ourselves.
  56. // Nowadays, though, we only use it for macOS as of 2022-11-04.
  57. func appendParsePortsNetstat(base []Port, br *bufio.Reader, includeLocalhost bool) ([]Port, error) {
  58. ret := base
  59. var fieldBuf [10]mem.RO
  60. for {
  61. line, err := br.ReadBytes('\n')
  62. if err != nil {
  63. if err == io.EOF {
  64. break
  65. }
  66. return nil, err
  67. }
  68. trimline := bytes.TrimSpace(line)
  69. cols := mem.AppendFields(fieldBuf[:0], mem.B(trimline))
  70. if len(cols) < 1 {
  71. continue
  72. }
  73. protos := cols[0]
  74. var proto string
  75. var laddr, raddr mem.RO
  76. if mem.HasPrefixFold(protos, mem.S("tcp")) {
  77. if len(cols) < 4 {
  78. continue
  79. }
  80. proto = "tcp"
  81. laddr = cols[len(cols)-3]
  82. raddr = cols[len(cols)-2]
  83. state := cols[len(cols)-1]
  84. if !mem.HasPrefix(state, mem.S("LISTEN")) {
  85. // not interested in non-listener sockets
  86. continue
  87. }
  88. if !includeLocalhost && isLoopbackAddr(laddr) {
  89. // not interested in loopback-bound listeners
  90. continue
  91. }
  92. } else if mem.HasPrefixFold(protos, mem.S("udp")) {
  93. if len(cols) < 3 {
  94. continue
  95. }
  96. proto = "udp"
  97. laddr = cols[len(cols)-2]
  98. raddr = cols[len(cols)-1]
  99. if !includeLocalhost && isLoopbackAddr(laddr) {
  100. // not interested in loopback-bound listeners
  101. continue
  102. }
  103. } else {
  104. // not interested in other protocols
  105. continue
  106. }
  107. lport := parsePort(laddr)
  108. rport := parsePort(raddr)
  109. if rport > 0 || lport <= 0 {
  110. // not interested in "connected" sockets
  111. continue
  112. }
  113. ret = append(ret, Port{
  114. Proto: proto,
  115. Port: uint16(lport),
  116. })
  117. }
  118. return ret, nil
  119. }