100 lines
2.3 KiB
Go
100 lines
2.3 KiB
Go
// 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.
|
|
|
|
// +build !linux,!windows
|
|
|
|
package portlist
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
|
|
exec "tailscale.com/tempfork/osexec"
|
|
)
|
|
|
|
// We have to run netstat, which is a bit expensive, so don't do it too often.
|
|
const POLL_SECONDS = 5
|
|
|
|
func listPorts() (List, error) {
|
|
return listPortsNetstat("-na")
|
|
}
|
|
|
|
// In theory, lsof could replace the function of both listPorts() and
|
|
// addProcesses(), since it provides a superset of the netstat output.
|
|
// However, "netstat -na" runs ~100x faster than lsof on my machine, so
|
|
// we should do it only if the list of open ports has actually changed.
|
|
//
|
|
// TODO(apenwarr): this fails in a macOS sandbox (ie. our usual case).
|
|
// We might as well just delete this code if we can't find a solution.
|
|
func addProcesses(pl []Port) ([]Port, error) {
|
|
exe, err := exec.LookPath("lsof")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("lsof: lookup: %v", err)
|
|
}
|
|
c := exec.Cmd{
|
|
Path: exe,
|
|
Args: []string{exe, "-F", "-n", "-P", "-O", "-S2", "-T", "-i4", "-i6"},
|
|
}
|
|
output, err := c.Output()
|
|
if err != nil {
|
|
xe, ok := err.(*exec.ExitError)
|
|
stderr := ""
|
|
if ok {
|
|
stderr = strings.TrimSpace(string(xe.Stderr))
|
|
}
|
|
// fails when run in a macOS sandbox, so make this non-fatal.
|
|
log.Printf("portlist: lsof: %v (%q)\n", err, stderr)
|
|
return pl, nil
|
|
}
|
|
|
|
type ProtoPort struct {
|
|
proto string
|
|
port uint16
|
|
}
|
|
m := map[ProtoPort]*Port{}
|
|
for i := range pl {
|
|
pp := ProtoPort{pl[i].Proto, pl[i].Port}
|
|
m[pp] = &pl[i]
|
|
}
|
|
|
|
r := bytes.NewReader(output)
|
|
scanner := bufio.NewScanner(r)
|
|
|
|
var cmd, proto string
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
if line[0] == 'p' {
|
|
// starting a new process
|
|
cmd = ""
|
|
proto = ""
|
|
} else if line[0] == 'c' {
|
|
cmd = line[1:len(line)]
|
|
} else if line[0] == 'P' {
|
|
proto = strings.ToLower(line[1:len(line)])
|
|
} else if line[0] == 'n' {
|
|
rest := line[1:len(line)]
|
|
i := strings.Index(rest, "->")
|
|
if i < 0 {
|
|
// a listening port
|
|
port := parsePort(rest)
|
|
if port > 0 {
|
|
pp := ProtoPort{proto, uint16(port)}
|
|
p := m[pp]
|
|
if p != nil {
|
|
p.Process = cmd
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "weird: missing %v\n", pp)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return pl, nil
|
|
}
|