|
|
@@ -0,0 +1,122 @@
|
|
|
+// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
+// SPDX-License-Identifier: BSD-3-Clause
|
|
|
+
|
|
|
+package portlist
|
|
|
+
|
|
|
+import (
|
|
|
+ "bufio"
|
|
|
+ "bytes"
|
|
|
+ "os"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+func init() {
|
|
|
+ newOSImpl = newPlan9Impl
|
|
|
+
|
|
|
+ pollInterval = 5 * time.Second
|
|
|
+}
|
|
|
+
|
|
|
+type plan9Impl struct {
|
|
|
+ known map[protoPort]*portMeta // inode string => metadata
|
|
|
+
|
|
|
+ br *bufio.Reader // reused
|
|
|
+ portsBuf []Port
|
|
|
+ includeLocalhost bool
|
|
|
+}
|
|
|
+
|
|
|
+type protoPort struct {
|
|
|
+ proto string
|
|
|
+ port uint16
|
|
|
+}
|
|
|
+
|
|
|
+type portMeta struct {
|
|
|
+ port Port
|
|
|
+ keep bool
|
|
|
+}
|
|
|
+
|
|
|
+func newPlan9Impl(includeLocalhost bool) osImpl {
|
|
|
+ return &plan9Impl{
|
|
|
+ known: map[protoPort]*portMeta{},
|
|
|
+ br: bufio.NewReader(bytes.NewReader(nil)),
|
|
|
+ includeLocalhost: includeLocalhost,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (*plan9Impl) Close() error { return nil }
|
|
|
+
|
|
|
+func (im *plan9Impl) AppendListeningPorts(base []Port) ([]Port, error) {
|
|
|
+ ret := base
|
|
|
+
|
|
|
+ des, err := os.ReadDir("/proc")
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ for _, de := range des {
|
|
|
+ if !de.IsDir() {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ pidStr := de.Name()
|
|
|
+ pid, err := strconv.Atoi(pidStr)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ st, _ := os.ReadFile("/proc/" + pidStr + "/fd")
|
|
|
+ if !bytes.Contains(st, []byte("/net/tcp/clone")) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ args, _ := os.ReadFile("/proc/" + pidStr + "/args")
|
|
|
+ procName := string(bytes.TrimSpace(args))
|
|
|
+ // term% cat /proc/417/fd
|
|
|
+ // /usr/glenda
|
|
|
+ // 0 r M 35 (0000000000000001 0 00) 16384 260 /dev/cons
|
|
|
+ // 1 w c 0 (000000000000000a 0 00) 0 471 /dev/null
|
|
|
+ // 2 w M 35 (0000000000000001 0 00) 16384 108 /dev/cons
|
|
|
+ // 3 rw I 0 (000000000000002c 0 00) 0 14 /net/tcp/clone
|
|
|
+ for line := range bytes.Lines(st) {
|
|
|
+ if !bytes.Contains(line, []byte("/net/tcp/clone")) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ f := strings.Fields(string(line))
|
|
|
+ if len(f) < 10 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if f[9] != "/net/tcp/clone" {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ qid, err := strconv.ParseUint(strings.TrimPrefix(f[4], "("), 16, 64)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ tcpN := (qid >> 5) & (1<<12 - 1)
|
|
|
+ tcpNStr := strconv.FormatUint(tcpN, 10)
|
|
|
+ st, _ := os.ReadFile("/net/tcp/" + tcpNStr + "/status")
|
|
|
+ if !bytes.Contains(st, []byte("Listen ")) {
|
|
|
+ // Unexpected. Or a race.
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ bl, _ := os.ReadFile("/net/tcp/" + tcpNStr + "/local")
|
|
|
+ i := bytes.LastIndexByte(bl, '!')
|
|
|
+ if i == -1 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if bytes.HasPrefix(bl, []byte("127.0.0.1!")) && !im.includeLocalhost {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ portStr := strings.TrimSpace(string(bl[i+1:]))
|
|
|
+ port, _ := strconv.Atoi(portStr)
|
|
|
+ if port == 0 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ ret = append(ret, Port{
|
|
|
+ Proto: "tcp",
|
|
|
+ Port: uint16(port),
|
|
|
+ Process: procName,
|
|
|
+ Pid: pid,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return sortAndDedup(ret), nil
|
|
|
+}
|