portlist: add macOS osImpl, finish migration to new style

Previously:

* 036f70b7b4 for linux
* 35bee36549 for windows

This does macOS.

And removes all the compat code for the old style. (e.g. iOS, js are
no longer mentioned; all platforms without implementations just
default to not doing anything)

One possible regression is that platforms without explicit
implementations previously tried to do the "netstat -na" style to get
open ports (but not process names). Maybe that worked on FreeBSD and
OpenBSD previously, but nobody ever really tested it. And it was kinda
useless without associated process names. So better off removing those
for now until they get a good implementation.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/6202/merge
Brad Fitzpatrick 2 years ago committed by Brad Fitzpatrick
parent da8def8e13
commit 21ef7e5c35

@ -2,21 +2,30 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !ios && !js //go:build darwin && !ios
package portlist package portlist
import ( import (
"sort" "bufio"
"strconv" "bytes"
"strings" "io"
"go4.org/mem"
) )
func parsePort(s string) int { // parsePort returns the port number at the end of s following the last "." or
// ":", whichever comes last. It returns -1 on a parse error or invalid number
// and 0 if the port number was "*".
//
// This is basically net.SplitHostPort except that it handles a "." (as macOS
// and others return in netstat output), uses mem.RO, and validates that the
// port must be numeric and in the uint16 range.
func parsePort(s mem.RO) int {
// a.b.c.d:1234 or [a:b:c:d]:1234 // a.b.c.d:1234 or [a:b:c:d]:1234
i1 := strings.LastIndexByte(s, ':') i1 := mem.LastIndexByte(s, ':')
// a.b.c.d.1234 or [a:b:c:d].1234 // a.b.c.d.1234 or [a:b:c:d].1234
i2 := strings.LastIndexByte(s, '.') i2 := mem.LastIndexByte(s, '.')
i := i1 i := i1
if i2 > i { if i2 > i {
@ -27,12 +36,12 @@ func parsePort(s string) int {
return -1 return -1
} }
portstr := s[i+1:] portstr := s.SliceFrom(i + 1)
if portstr == "*" { if portstr.EqualString("*") {
return 0 return 0
} }
port, err := strconv.ParseUint(portstr, 10, 16) port, err := mem.ParseUint(portstr, 10, 16)
if err != nil { if err != nil {
// invalid port; weird // invalid port; weird
return -1 return -1
@ -41,34 +50,45 @@ func parsePort(s string) int {
return int(port) return int(port)
} }
func isLoopbackAddr(s string) bool { func isLoopbackAddr(s mem.RO) bool {
return strings.HasPrefix(s, "127.") || return mem.HasPrefix(s, mem.S("127.")) ||
strings.HasPrefix(s, "[::1]:") || mem.HasPrefix(s, mem.S("[::1]:")) ||
strings.HasPrefix(s, "::1.") mem.HasPrefix(s, mem.S("::1."))
} }
type nothing struct{} type nothing struct{}
// Lowest common denominator parser for "netstat -na" format. // appendParsePortsNetstat appends to base listening ports
// from "netstat" output, read from br. See TestParsePortsNetstat
// for example input lines.
//
// This used to be a lowest common denominator parser for "netstat -na" format.
// All of Linux, Windows, and macOS support -na and give similar-ish output // All of Linux, Windows, and macOS support -na and give similar-ish output
// formats that we can parse without special detection logic. // formats that we can parse without special detection logic.
// Unfortunately, options to filter by proto or state are non-portable, // Unfortunately, options to filter by proto or state are non-portable,
// so we'll filter for ourselves. // so we'll filter for ourselves.
func appendParsePortsNetstat(base []Port, output string) []Port { // Nowadays, though, we only use it for macOS as of 2022-11-04.
m := map[Port]nothing{} func appendParsePortsNetstat(base []Port, br *bufio.Reader) ([]Port, error) {
lines := strings.Split(string(output), "\n") ret := base
var fieldBuf [10]mem.RO
var lastline string for {
var lastport Port line, err := br.ReadBytes('\n')
for _, line := range lines { if err != nil {
trimline := strings.TrimSpace(line) if err == io.EOF {
cols := strings.Fields(trimline) break
}
return nil, err
}
trimline := bytes.TrimSpace(line)
cols := mem.AppendFields(fieldBuf[:0], mem.B(trimline))
if len(cols) < 1 { if len(cols) < 1 {
continue continue
} }
protos := strings.ToLower(cols[0]) protos := cols[0]
var proto, laddr, raddr string
if strings.HasPrefix(protos, "tcp") { var proto string
var laddr, raddr mem.RO
if mem.HasPrefixFold(protos, mem.S("tcp")) {
if len(cols) < 4 { if len(cols) < 4 {
continue continue
} }
@ -76,7 +96,7 @@ func appendParsePortsNetstat(base []Port, output string) []Port {
laddr = cols[len(cols)-3] laddr = cols[len(cols)-3]
raddr = cols[len(cols)-2] raddr = cols[len(cols)-2]
state := cols[len(cols)-1] state := cols[len(cols)-1]
if !strings.HasPrefix(state, "LISTEN") { if !mem.HasPrefix(state, mem.S("LISTEN")) {
// not interested in non-listener sockets // not interested in non-listener sockets
continue continue
} }
@ -84,7 +104,7 @@ func appendParsePortsNetstat(base []Port, output string) []Port {
// not interested in loopback-bound listeners // not interested in loopback-bound listeners
continue continue
} }
} else if strings.HasPrefix(protos, "udp") { } else if mem.HasPrefixFold(protos, mem.S("udp")) {
if len(cols) < 3 { if len(cols) < 3 {
continue continue
} }
@ -95,53 +115,21 @@ func appendParsePortsNetstat(base []Port, output string) []Port {
// not interested in loopback-bound listeners // not interested in loopback-bound listeners
continue continue
} }
} else if protos[0] == '[' && len(trimline) > 2 {
// Windows: with netstat -nab, appends a line like:
// [description]
// after the port line.
p := lastport
delete(m, lastport)
proc := trimline[1 : len(trimline)-1]
if proc == "svchost.exe" && lastline != "" {
p.Process = argvSubject(lastline)
} else {
p.Process = argvSubject(proc)
}
m[p] = nothing{}
} else { } else {
// not interested in other protocols // not interested in other protocols
lastline = trimline
continue continue
} }
lport := parsePort(laddr) lport := parsePort(laddr)
rport := parsePort(raddr) rport := parsePort(raddr)
if rport != 0 || lport <= 0 { if rport > 0 || lport <= 0 {
// not interested in "connected" sockets // not interested in "connected" sockets
continue continue
} }
ret = append(ret, Port{
p := Port{
Proto: proto, Proto: proto,
Port: uint16(lport), Port: uint16(lport),
}
m[p] = nothing{}
lastport = p
lastline = ""
}
ret := base
for p := range m {
ret = append(ret, p)
}
// Only sort the part we appended. It's up to the caller to sort the whole
// thing if they'd like. In practice the caller's base will have len 0,
// though, so the whole thing will be sorted.
toSort := ret[len(base):]
sort.Slice(toSort, func(i, j int) bool {
return (&toSort[i]).lessThan(&toSort[j])
}) })
}
return ret return ret, nil
} }

@ -1,31 +0,0 @@
// 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.
//go:build (windows || freebsd || openbsd || darwin) && !ios && !js
package portlist
import (
"fmt"
"os/exec"
"strings"
)
func appendListeningPortsNetstat(base []Port, arg string) ([]Port, error) {
exe, err := exec.LookPath("netstat")
if err != nil {
return nil, fmt.Errorf("netstat: lookup: %v", err)
}
output, err := exec.Command(exe, arg).Output()
if err != nil {
xe, ok := err.(*exec.ExitError)
stderr := ""
if ok {
stderr = strings.TrimSpace(string(xe.Stderr))
}
return nil, fmt.Errorf("netstat: %v (%q)", err, stderr)
}
return appendParsePortsNetstat(base, string(output)), nil
}

@ -2,13 +2,17 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !ios && !js //go:build darwin && !ios
package portlist package portlist
import ( import (
"bufio"
"encoding/json" "encoding/json"
"strings"
"testing" "testing"
"go4.org/mem"
) )
func TestParsePort(t *testing.T) { func TestParsePort(t *testing.T) {
@ -27,7 +31,7 @@ func TestParsePort(t *testing.T) {
} }
for _, io := range tests { for _, io := range tests {
got := parsePort(io.in) got := parsePort(mem.S(io.in))
if got != io.expect { if got != io.expect {
t.Fatalf("input:%#v expect:%v got:%v\n", io.in, io.expect, got) t.Fatalf("input:%#v expect:%v got:%v\n", io.in, io.expect, got)
} }
@ -35,12 +39,6 @@ func TestParsePort(t *testing.T) {
} }
const netstatOutput = ` const netstatOutput = `
// linux
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
udp 0 0 0.0.0.0:5353 0.0.0.0:*
udp6 0 0 :::5353 :::*
udp6 0 0 :::5354 :::*
// macOS // macOS
tcp4 0 0 *.23 *.* LISTEN tcp4 0 0 *.23 *.* LISTEN
tcp6 0 0 *.24 *.* LISTEN tcp6 0 0 *.24 *.* LISTEN
@ -49,43 +47,26 @@ tcp4 0 0 127.0.0.1.8186 *.* LISTEN
tcp6 0 0 ::1.8187 *.* LISTEN tcp6 0 0 ::1.8187 *.* LISTEN
tcp4 0 0 127.1.2.3.8188 *.* LISTEN tcp4 0 0 127.1.2.3.8188 *.* LISTEN
udp6 0 0 *.5453 *.* udp6 0 0 *.106 *.*
udp4 0 0 *.5553 *.* udp4 0 0 *.104 *.*
udp46 0 0 *.146 *.*
// Windows 10
Proto Local Address Foreign Address State
TCP 0.0.0.0:32 0.0.0.0:0 LISTENING
[sshd.exe]
UDP 0.0.0.0:5050 *:*
CDPSvc
[svchost.exe]
UDP 0.0.0.0:53 *:*
[chrome.exe]
UDP 10.0.1.43:9353 *:*
[iTunes.exe]
UDP [::]:53 *:*
UDP [::]:53 *:*
[funball.exe]
` `
func TestParsePortsNetstat(t *testing.T) { func TestParsePortsNetstat(t *testing.T) {
want := List{ want := List{
Port{"tcp", 22, ""},
Port{"tcp", 23, ""}, Port{"tcp", 23, ""},
Port{"tcp", 24, ""}, Port{"tcp", 24, ""},
Port{"tcp", 32, "sshd"}, Port{"udp", 104, ""},
Port{"udp", 53, "chrome"}, Port{"udp", 106, ""},
Port{"udp", 53, "funball"}, Port{"udp", 146, ""},
Port{"udp", 5050, "CDPSvc"},
Port{"udp", 5353, ""},
Port{"udp", 5354, ""},
Port{"udp", 5453, ""},
Port{"udp", 5553, ""},
Port{"tcp", 8185, ""}, // but not 8186, 8187, 8188 on localhost Port{"tcp", 8185, ""}, // but not 8186, 8187, 8188 on localhost
Port{"udp", 9353, "iTunes"},
} }
pl := appendParsePortsNetstat(nil, netstatOutput) pl, err := appendParsePortsNetstat(nil, bufio.NewReader(strings.NewReader(netstatOutput)))
if err != nil {
t.Fatal(err)
}
pl = sortAndDedup(pl)
jgot, _ := json.MarshalIndent(pl, "", "\t") jgot, _ := json.MarshalIndent(pl, "", "\t")
jwant, _ := json.MarshalIndent(want, "", "\t") jwant, _ := json.MarshalIndent(want, "", "\t")
if len(pl) != len(want) { if len(pl) != len(want) {
@ -93,7 +74,7 @@ func TestParsePortsNetstat(t *testing.T) {
} }
for i := range pl { for i := range pl {
if pl[i] != want[i] { if pl[i] != want[i] {
t.Errorf("row#%d\n got: %#v\n\nwant: %#v\n", t.Errorf("row#%d\n got: %+v\n\nwant: %+v\n",
i, pl[i], want[i]) i, pl[i], want[i])
t.Fatalf("Got:\n%s\n\nWant:\n%s\n", jgot, jwant) t.Fatalf("Got:\n%s\n\nWant:\n%s\n", jgot, jwant)
} }

@ -10,14 +10,15 @@ package portlist
import ( import (
"context" "context"
"errors" "errors"
"fmt" "runtime"
"sync" "sync"
"time" "time"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/version"
) )
var pollInterval = 5 * time.Second // default; changed by some OS-specific init funcs
var debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST") var debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")
// Poller scans the systems for listening ports periodically and sends // Poller scans the systems for listening ports periodically and sends
@ -29,11 +30,8 @@ type Poller struct {
// code. When non-nil, it's responsible for getting the complete list of // code. When non-nil, it's responsible for getting the complete list of
// cached ports complete with the process name. That is, when set, // cached ports complete with the process name. That is, when set,
// addProcesses is not used. // addProcesses is not used.
// // A nil values means we don't have code for getting the list on the current
// This is part of a multi-step migration (starting 2022-10-22) to move to // operating system.
// using osImpl for all of Linux, macOS (unsandboxed), and Windows. But
// during the transition period, we support this being nil.
// TODO(bradfitz): finish that migration.
os osImpl os osImpl
osOnce sync.Once // guards init of os osOnce sync.Once // guards init of os
@ -66,12 +64,11 @@ type osImpl interface {
// newOSImpl, if non-nil, constructs a new osImpl. // newOSImpl, if non-nil, constructs a new osImpl.
var newOSImpl func() osImpl var newOSImpl func() osImpl
var errUnimplemented = errors.New("portlist poller not implemented on " + runtime.GOOS)
// NewPoller returns a new portlist Poller. It returns an error // NewPoller returns a new portlist Poller. It returns an error
// if the portlist couldn't be obtained. // if the portlist couldn't be obtained.
func NewPoller() (*Poller, error) { func NewPoller() (*Poller, error) {
if version.OS() == "iOS" {
return nil, errors.New("not available on iOS")
}
if debugDisablePortlist() { if debugDisablePortlist() {
return nil, errors.New("portlist disabled by envknob") return nil, errors.New("portlist disabled by envknob")
} }
@ -81,6 +78,9 @@ func NewPoller() (*Poller, error) {
} }
p.closeCtx, p.closeCtxCancel = context.WithCancel(context.Background()) p.closeCtx, p.closeCtxCancel = context.WithCancel(context.Background())
p.osOnce.Do(p.initOSField) p.osOnce.Do(p.initOSField)
if p.os == nil {
return nil, errUnimplemented
}
// Do one initial poll synchronously so we can return an error // Do one initial poll synchronously so we can return an error
// early. // early.
@ -172,25 +172,6 @@ func (p *Poller) getList() (List, error) {
} }
p.osOnce.Do(p.initOSField) p.osOnce.Do(p.initOSField)
var err error var err error
if p.os != nil {
p.scratch, err = p.os.AppendListeningPorts(p.scratch[:0]) p.scratch, err = p.os.AppendListeningPorts(p.scratch[:0])
return p.scratch, err return p.scratch, err
} }
// Old path for OSes that don't have osImpl yet.
// TODO(bradfitz): delete these when macOS and Windows are converted.
p.scratch, err = appendListeningPorts(p.scratch[:0])
if err != nil {
return nil, fmt.Errorf("listPorts: %s", err)
}
pl := sortAndDedup(p.scratch)
if pl.equal(p.prev) {
// Nothing changed, skip inode lookup
return p.prev, nil
}
pl, err = addProcesses(pl)
if err != nil {
return nil, fmt.Errorf("addProcesses: %s", err)
}
return pl, nil
}

@ -1,22 +0,0 @@
// 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.
//go:build ios
package portlist
import (
"errors"
"time"
)
const pollInterval = 9999 * time.Hour
func appendListeningPorts(base []Port) ([]Port, error) {
return nil, errors.New("not implemented")
}
func addProcesses(pl []Port) ([]Port, error) {
return nil, errors.New("not implemented")
}

@ -1,17 +0,0 @@
// Copyright (c) 2021 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.
package portlist
import "time"
const pollInterval = 365 * 24 * time.Hour
func appendListeningPorts(base []Port) ([]Port, error) {
return base, nil
}
func addProcesses(pl []Port) ([]Port, error) {
return pl, nil
}

@ -27,6 +27,8 @@ import (
func init() { func init() {
newOSImpl = newLinuxImpl newOSImpl = newLinuxImpl
// Reading the sockfiles on Linux is very fast, so we can do it often.
pollInterval = 1 * time.Second
} }
type linuxImpl struct { type linuxImpl struct {
@ -78,9 +80,6 @@ func (li *linuxImpl) Close() error {
return nil return nil
} }
// Reading the sockfiles on Linux is very fast, so we can do it often.
const pollInterval = 1 * time.Second
const ( const (
v6Localhost = "00000000000000000000000001000000:" v6Localhost = "00000000000000000000000001000000:"
v6Any = "00000000000000000000000000000000:0000" v6Any = "00000000000000000000000000000000:0000"
@ -420,11 +419,3 @@ func readlink(path, buf []byte) (n int, ok bool) {
} }
return n, true return n, true
} }
func appendListeningPorts([]Port) ([]Port, error) {
panic("unused on linux; needed to compile for now")
}
func addProcesses([]Port) ([]Port, error) {
panic("unused on linux; needed to compile for now")
}

@ -15,16 +15,115 @@ import (
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"
"go4.org/mem"
) )
func init() {
newOSImpl = newMacOSImpl
// We have to run netstat, which is a bit expensive, so don't do it too often. // We have to run netstat, which is a bit expensive, so don't do it too often.
const pollInterval = 5 * time.Second pollInterval = 5 * time.Second
}
type macOSImpl struct {
known map[protoPort]*portMeta // inode string => metadata
netstatPath string // lazily populated
br *bufio.Reader // reused
portsBuf []Port
}
type protoPort struct {
proto string
port uint16
}
type portMeta struct {
port Port
keep bool
}
func newMacOSImpl() osImpl {
return &macOSImpl{
known: map[protoPort]*portMeta{},
br: bufio.NewReader(bytes.NewReader(nil)),
}
}
func (*macOSImpl) Close() error { return nil }
func (im *macOSImpl) AppendListeningPorts(base []Port) ([]Port, error) {
var err error
im.portsBuf, err = im.appendListeningPortsNetstat(im.portsBuf[:0])
if err != nil {
return nil, err
}
for _, pm := range im.known {
pm.keep = false
}
var needProcs bool
for _, p := range im.portsBuf {
fp := protoPort{
proto: p.Proto,
port: p.Port,
}
if pm, ok := im.known[fp]; ok {
pm.keep = true
} else {
needProcs = true
im.known[fp] = &portMeta{
port: p,
keep: true,
}
}
}
ret := base
for k, m := range im.known {
if !m.keep {
delete(im.known, k)
}
}
func appendListeningPorts(base []Port) ([]Port, error) { if needProcs {
return appendListeningPortsNetstat(base, "-na") im.addProcesses() // best effort
} }
var lsofFailed int64 // atomic bool for _, m := range im.known {
ret = append(ret, m.port)
}
return sortAndDedup(ret), nil
}
func (im *macOSImpl) appendListeningPortsNetstat(base []Port) ([]Port, error) {
if im.netstatPath == "" {
var err error
im.netstatPath, err = exec.LookPath("netstat")
if err != nil {
return nil, fmt.Errorf("netstat: lookup: %v", err)
}
}
cmd := exec.Command(im.netstatPath, "-na")
outPipe, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
im.br.Reset(outPipe)
if err := cmd.Start(); err != nil {
return nil, err
}
defer cmd.Process.Wait()
defer cmd.Process.Kill()
return appendParsePortsNetstat(base, im.br)
}
var lsofFailed atomic.Bool
// In theory, lsof could replace the function of both listPorts() and // In theory, lsof could replace the function of both listPorts() and
// addProcesses(), since it provides a superset of the netstat output. // addProcesses(), since it provides a superset of the netstat output.
@ -34,75 +133,82 @@ var lsofFailed int64 // atomic bool
// This fails in a macOS sandbox (i.e. in the Mac App Store or System // This fails in a macOS sandbox (i.e. in the Mac App Store or System
// Extension GUI build), but does at least work in the // Extension GUI build), but does at least work in the
// tailscaled-on-macos mode. // tailscaled-on-macos mode.
func addProcesses(pl []Port) ([]Port, error) { func (im *macOSImpl) addProcesses() error {
if atomic.LoadInt64(&lsofFailed) != 0 { if lsofFailed.Load() {
// This previously failed in the macOS sandbox, so don't try again. // This previously failed in the macOS sandbox, so don't try again.
return pl, nil return nil
} }
exe, err := exec.LookPath("lsof") exe, err := exec.LookPath("lsof")
if err != nil { if err != nil {
return nil, fmt.Errorf("lsof: lookup: %v", err) return fmt.Errorf("lsof: lookup: %v", err)
} }
output, err := exec.Command(exe, "-F", "-n", "-P", "-O", "-S2", "-T", "-i4", "-i6").Output() lsofCmd := exec.Command(exe, "-F", "-n", "-P", "-O", "-S2", "-T", "-i4", "-i6")
outPipe, err := lsofCmd.StdoutPipe()
if err != nil {
return err
}
err = lsofCmd.Start()
if err != nil { if err != nil {
var stderr []byte var stderr []byte
if xe, ok := err.(*exec.ExitError); ok { if xe, ok := err.(*exec.ExitError); ok {
stderr = xe.Stderr stderr = xe.Stderr
} }
// fails when run in a macOS sandbox, so make this non-fatal. // fails when run in a macOS sandbox, so make this non-fatal.
if atomic.CompareAndSwapInt64(&lsofFailed, 0, 1) { if lsofFailed.CompareAndSwap(false, true) {
log.Printf("portlist: can't run lsof in Mac sandbox; omitting process names from service list. Error details: %v, %s", err, bytes.TrimSpace(stderr)) log.Printf("portlist: can't run lsof in Mac sandbox; omitting process names from service list. Error details: %v, %s", err, bytes.TrimSpace(stderr))
} }
return pl, nil return nil
} }
im.br.Reset(outPipe)
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 var cmd, proto string
for scanner.Scan() { for {
line := scanner.Text() line, err := im.br.ReadBytes('\n')
if line == "" { if err != nil {
break
}
if len(line) < 1 {
continue continue
} }
field, val := line[0], line[1:] field, val := line[0], bytes.TrimSpace(line[1:])
switch field { switch field {
case 'p': case 'p':
// starting a new process // starting a new process
cmd = "" cmd = ""
proto = "" proto = ""
case 'c': case 'c':
cmd = val cmd = string(val) // TODO(bradfitz): avoid garbage; cache process names between runs?
case 'P': case 'P':
proto = strings.ToLower(val) proto = lsofProtoLower(val)
case 'n': case 'n':
if strings.Contains(val, "->") { if mem.Contains(mem.B(val), mem.S("->")) {
continue continue
} }
// a listening port // a listening port
port := parsePort(val) port := parsePort(mem.B(val))
if port > 0 { if port <= 0 {
pp := ProtoPort{proto, uint16(port)} continue
p := m[pp] }
pp := protoPort{proto, uint16(port)}
m := im.known[pp]
switch { switch {
case p != nil: case m != nil:
p.Process = cmd m.port.Process = cmd
default: default:
// ignore: processes and ports come and go // ignore: processes and ports come and go
} }
} }
} }
return nil
} }
return pl, nil func lsofProtoLower(p []byte) string {
if string(p) == "TCP" {
return "tcp"
}
if string(p) == "UDP" {
return "udp"
}
return strings.ToLower(string(p))
} }

@ -1,22 +0,0 @@
// 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.
//go:build !linux && !windows && !darwin && !js
package portlist
import "time"
// We have to run netstat, which is a bit expensive, so don't do it too often.
const pollInterval = 5 * time.Second
func appendListeningPorts(base []Port) ([]Port, error) {
return appendListeningPortsNetstat(base, "-na")
}
func addProcesses(pl []Port) ([]Port, error) {
// Generic version has no way to get process mappings.
// This has to be OS-specific.
return pl, nil
}

@ -14,11 +14,13 @@ import (
"tailscale.com/net/netstat" "tailscale.com/net/netstat"
) )
// Forking on Windows is insanely expensive, so don't do it too often.
const pollInterval = 5 * time.Second
func init() { func init() {
newOSImpl = newWindowsImpl newOSImpl = newWindowsImpl
// The portlist poller used to fork on Windows, which is insanely expensive,
// so historically we only did this every 5 seconds on Windows. Maybe we
// could reduce it down to 1 seconds like Linux, but nobody's benchmarked as
// of 2022-11-04.
pollInterval = 5 * time.Second
} }
type famPort struct { type famPort struct {
@ -116,11 +118,3 @@ func procNameOfPid(pid int) string {
name = strings.TrimSuffix(name, ".EXE") name = strings.TrimSuffix(name, ".EXE")
return name return name
} }
func appendListeningPorts([]Port) ([]Port, error) {
panic("unused on windows; needed to compile for now")
}
func addProcesses([]Port) ([]Port, error) {
panic("unused on windows; needed to compile for now")
}

Loading…
Cancel
Save