// 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 }