Update dependencies

This commit is contained in:
bluepython508
2024-11-01 17:33:34 +00:00
parent 033ac0b400
commit 5cdfab398d
3596 changed files with 1033483 additions and 259 deletions

36
vendor/tailscale.com/portlist/clean.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package portlist
import (
"path/filepath"
"strings"
)
// argvSubject takes a command and its flags, and returns the
// short/pretty name for the process. This is usually the basename of
// the binary being executed, but can sometimes vary (e.g. so that we
// don't report all Java programs as "java").
func argvSubject(argv ...string) string {
if len(argv) == 0 {
return ""
}
ret := filepath.Base(argv[0])
// Handle special cases.
switch {
case ret == "mono" && len(argv) >= 2:
// .Net programs execute as `mono actualProgram.exe`.
ret = filepath.Base(argv[1])
}
// Handle space separated argv
ret, _, _ = strings.Cut(ret, " ")
// Remove common noise.
ret = strings.TrimSpace(ret)
ret = strings.TrimSuffix(ret, ".exe")
return ret
}

132
vendor/tailscale.com/portlist/netstat.go generated vendored Normal file
View File

@@ -0,0 +1,132 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin && !ios
package portlist
import (
"bufio"
"bytes"
"io"
"go4.org/mem"
)
// 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
i1 := mem.LastIndexByte(s, ':')
// a.b.c.d.1234 or [a:b:c:d].1234
i2 := mem.LastIndexByte(s, '.')
i := i1
if i2 > i {
i = i2
}
if i < 0 {
// no match; weird
return -1
}
portstr := s.SliceFrom(i + 1)
if portstr.EqualString("*") {
return 0
}
port, err := mem.ParseUint(portstr, 10, 16)
if err != nil {
// invalid port; weird
return -1
}
return int(port)
}
func isLoopbackAddr(s mem.RO) bool {
return mem.HasPrefix(s, mem.S("127.")) ||
mem.HasPrefix(s, mem.S("[::1]:")) ||
mem.HasPrefix(s, mem.S("::1."))
}
// 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
// formats that we can parse without special detection logic.
// Unfortunately, options to filter by proto or state are non-portable,
// so we'll filter for ourselves.
// Nowadays, though, we only use it for macOS as of 2022-11-04.
func appendParsePortsNetstat(base []Port, br *bufio.Reader, includeLocalhost bool) ([]Port, error) {
ret := base
var fieldBuf [10]mem.RO
for {
line, err := br.ReadBytes('\n')
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
trimline := bytes.TrimSpace(line)
cols := mem.AppendFields(fieldBuf[:0], mem.B(trimline))
if len(cols) < 1 {
continue
}
protos := cols[0]
var proto string
var laddr, raddr mem.RO
if mem.HasPrefixFold(protos, mem.S("tcp")) {
if len(cols) < 4 {
continue
}
proto = "tcp"
laddr = cols[len(cols)-3]
raddr = cols[len(cols)-2]
state := cols[len(cols)-1]
if !mem.HasPrefix(state, mem.S("LISTEN")) {
// not interested in non-listener sockets
continue
}
if !includeLocalhost && isLoopbackAddr(laddr) {
// not interested in loopback-bound listeners
continue
}
} else if mem.HasPrefixFold(protos, mem.S("udp")) {
if len(cols) < 3 {
continue
}
proto = "udp"
laddr = cols[len(cols)-2]
raddr = cols[len(cols)-1]
if !includeLocalhost && isLoopbackAddr(laddr) {
// not interested in loopback-bound listeners
continue
}
} else {
// not interested in other protocols
continue
}
lport := parsePort(laddr)
rport := parsePort(raddr)
if rport > 0 || lport <= 0 {
// not interested in "connected" sockets
continue
}
ret = append(ret, Port{
Proto: proto,
Port: uint16(lport),
})
}
return ret, nil
}

122
vendor/tailscale.com/portlist/poller.go generated vendored Normal file
View File

@@ -0,0 +1,122 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// This file contains the code related to the Poller type and its methods.
// The hot loop to keep efficient is Poller.Run.
package portlist
import (
"errors"
"fmt"
"runtime"
"slices"
"sync"
"time"
"tailscale.com/envknob"
)
var (
newOSImpl func(includeLocalhost bool) osImpl // if non-nil, constructs a new osImpl.
pollInterval = 5 * time.Second // default; changed by some OS-specific init funcs
debugDisablePortlist = envknob.RegisterBool("TS_DEBUG_DISABLE_PORTLIST")
)
// PollInterval is the recommended OS-specific interval
// to wait between *Poller.Poll method calls.
func PollInterval() time.Duration {
return pollInterval
}
// Poller scans the systems for listening ports periodically and sends
// the results to C.
type Poller struct {
// IncludeLocalhost controls whether services bound to localhost are included.
//
// This field should only be changed before calling Run.
IncludeLocalhost bool
// os, if non-nil, is an OS-specific implementation of the portlist getting
// code. When non-nil, it's responsible for getting the complete list of
// cached ports complete with the process name. That is, when set,
// addProcesses is not used.
// A nil values means we don't have code for getting the list on the current
// operating system.
os osImpl
initOnce sync.Once // guards init of os
initErr error
// scatch is memory for Poller.getList to reuse between calls.
scratch []Port
prev List // most recent data, not aliasing scratch
}
// osImpl is the OS-specific implementation of getting the open listening ports.
type osImpl interface {
Close() error
// AppendListeningPorts appends to base (which must have length 0 but
// optional capacity) the list of listening ports. The Port struct should be
// populated as completely as possible. Another pass will not add anything
// to it.
//
// The appended ports should be in a sorted (or at least stable) order so
// the caller can cheaply detect when there are no changes.
AppendListeningPorts(base []Port) ([]Port, error)
}
func (p *Poller) setPrev(pl List) {
// Make a copy, as the pass in pl slice aliases pl.scratch and we don't want
// that to except to the caller.
p.prev = slices.Clone(pl)
}
// init initializes the Poller by ensuring it has an underlying
// OS implementation and is not turned off by envknob.
func (p *Poller) init() {
switch {
case debugDisablePortlist():
p.initErr = errors.New("portlist disabled by envknob")
case newOSImpl == nil:
p.initErr = errors.New("portlist poller not implemented on " + runtime.GOOS)
default:
p.os = newOSImpl(p.IncludeLocalhost)
}
}
// Close closes the Poller.
func (p *Poller) Close() error {
if p.initErr != nil {
return p.initErr
}
if p.os == nil {
return nil
}
return p.os.Close()
}
// Poll returns the list of listening ports, if changed from
// a previous call as indicated by the changed result.
func (p *Poller) Poll() (ports []Port, changed bool, err error) {
p.initOnce.Do(p.init)
if p.initErr != nil {
return nil, false, fmt.Errorf("error initializing poller: %w", p.initErr)
}
pl, err := p.getList()
if err != nil {
return nil, false, err
}
if pl.equal(p.prev) {
return nil, false, nil
}
p.setPrev(pl)
return p.prev, true, nil
}
func (p *Poller) getList() (List, error) {
var err error
p.scratch, err = p.os.AppendListeningPorts(p.scratch[:0])
return p.scratch, err
}

80
vendor/tailscale.com/portlist/portlist.go generated vendored Normal file
View File

@@ -0,0 +1,80 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// This file is just the types. The bulk of the code is in poller.go.
// The portlist package contains code that checks what ports are open and
// listening on the current machine.
package portlist
import (
"fmt"
"sort"
"strings"
)
// Port is a listening port on the machine.
type Port struct {
Proto string // "tcp" or "udp"
Port uint16 // port number
Process string // optional process name, if found (requires suitable permissions)
Pid int // process ID, if known (requires suitable permissions)
}
// List is a list of Ports.
type List []Port
func (a *Port) lessThan(b *Port) bool {
if a.Port != b.Port {
return a.Port < b.Port
}
if a.Proto != b.Proto {
return a.Proto < b.Proto
}
return a.Process < b.Process
}
func (a *Port) equal(b *Port) bool {
return a.Port == b.Port &&
a.Proto == b.Proto &&
a.Process == b.Process
}
func (a List) equal(b List) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !a[i].equal(&b[i]) {
return false
}
}
return true
}
func (pl List) String() string {
var sb strings.Builder
for _, v := range pl {
fmt.Fprintf(&sb, "%-3s %5d %#v\n",
v.Proto, v.Port, v.Process)
}
return strings.TrimRight(sb.String(), "\n")
}
// sortAndDedup sorts ps in place (by Port.lessThan) and then returns
// a subset of it with duplicate (Proto, Port) removed.
func sortAndDedup(ps List) List {
sort.Slice(ps, func(i, j int) bool {
return (&ps[i]).lessThan(&ps[j])
})
out := ps[:0]
var last Port
for _, p := range ps {
if last.Proto == p.Proto && last.Port == p.Port {
continue
}
out = append(out, p)
last = p
}
return out
}

408
vendor/tailscale.com/portlist/portlist_linux.go generated vendored Normal file
View File

@@ -0,0 +1,408 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package portlist
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"io/fs"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"unsafe"
"go4.org/mem"
"golang.org/x/sys/unix"
"tailscale.com/util/dirwalk"
"tailscale.com/util/mak"
)
func init() {
newOSImpl = newLinuxImpl
// Reading the sockfiles on Linux is very fast, so we can do it often.
pollInterval = 1 * time.Second
}
type linuxImpl struct {
procNetFiles []*os.File // seeked to start & reused between calls
readlinkPathBuf []byte
known map[string]*portMeta // inode string => metadata
br *bufio.Reader
includeLocalhost bool
}
type portMeta struct {
port Port
pid int
keep bool
needsProcName bool
}
func newLinuxImplBase(includeLocalhost bool) *linuxImpl {
return &linuxImpl{
br: bufio.NewReader(eofReader),
known: map[string]*portMeta{},
includeLocalhost: includeLocalhost,
}
}
func newLinuxImpl(includeLocalhost bool) osImpl {
li := newLinuxImplBase(includeLocalhost)
for _, name := range []string{
"/proc/net/tcp",
"/proc/net/tcp6",
"/proc/net/udp",
"/proc/net/udp6",
} {
f, err := os.Open(name)
if err != nil {
if os.IsNotExist(err) {
continue
}
log.Printf("portlist warning; ignoring: %v", err)
continue
}
li.procNetFiles = append(li.procNetFiles, f)
}
return li
}
func (li *linuxImpl) Close() error {
for _, f := range li.procNetFiles {
f.Close()
}
li.procNetFiles = nil
return nil
}
const (
v6Localhost = "00000000000000000000000001000000:"
v6Any = "00000000000000000000000000000000:0000"
v4Localhost = "0100007F:"
v4Any = "00000000:0000"
)
var eofReader = bytes.NewReader(nil)
func (li *linuxImpl) AppendListeningPorts(base []Port) ([]Port, error) {
if runtime.GOOS == "android" {
// Android 10+ doesn't allow access to this anymore.
// https://developer.android.com/about/versions/10/privacy/changes#proc-net-filesystem
// Ignore it rather than have the system log about our violation.
return nil, nil
}
br := li.br
defer br.Reset(eofReader)
// Start by marking all previous known ports as gone. If this mark
// bit is still false later, we'll remove them.
for _, pm := range li.known {
pm.keep = false
}
for _, f := range li.procNetFiles {
name := f.Name()
_, err := f.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
br.Reset(f)
err = li.parseProcNetFile(br, filepath.Base(name))
if err != nil {
return nil, fmt.Errorf("parsing %q: %w", name, err)
}
}
// Delete ports that aren't open any longer.
// And see if there are any process names we need to look for.
var needProc map[string]*portMeta
for inode, pm := range li.known {
if !pm.keep {
delete(li.known, inode)
continue
}
if pm.needsProcName {
mak.Set(&needProc, inode, pm)
}
}
err := li.findProcessNames(needProc)
if err != nil {
return nil, err
}
ret := base
for _, pm := range li.known {
ret = append(ret, pm.port)
}
return sortAndDedup(ret), nil
}
// fileBase is one of "tcp", "tcp6", "udp", "udp6".
func (li *linuxImpl) parseProcNetFile(r *bufio.Reader, fileBase string) error {
proto := strings.TrimSuffix(fileBase, "6")
// skip header row
_, err := r.ReadSlice('\n')
if err != nil {
return err
}
fields := make([]mem.RO, 0, 20) // 17 current fields + some future slop
wantRemote := mem.S(v4Any)
if strings.HasSuffix(fileBase, "6") {
wantRemote = mem.S(v6Any)
}
// remoteIndex is the index within a line to the remote address field.
// -1 means not yet found.
remoteIndex := -1
// Add an upper bound on how many rows we'll attempt to read just
// to make sure this doesn't consume too much of their CPU.
// TODO(bradfitz,crawshaw): adaptively adjust polling interval as function
// of open sockets.
const maxRows = 1e6
rows := 0
// Scratch buffer for making inode strings.
inoBuf := make([]byte, 0, 50)
for {
line, err := r.ReadSlice('\n')
if err == io.EOF {
break
}
if err != nil {
return err
}
rows++
if rows >= maxRows {
break
}
if len(line) == 0 {
continue
}
// On the first row of output, find the index of the 3rd field (index 2),
// the remote address. All the rows are aligned, at least until 4 billion open
// TCP connections, per the Linux get_tcp4_sock's "%4d: " on an int i.
if remoteIndex == -1 {
remoteIndex = fieldIndex(line, 2)
if remoteIndex == -1 {
break
}
}
if len(line) < remoteIndex || !mem.HasPrefix(mem.B(line).SliceFrom(remoteIndex), wantRemote) {
// Fast path for not being a listener port.
continue
}
// sl local rem ... inode
fields = mem.AppendFields(fields[:0], mem.B(line))
local := fields[1]
rem := fields[2]
inode := fields[9]
if !rem.Equal(wantRemote) {
// not a "listener" port
continue
}
// If a port is bound to localhost, ignore it.
// TODO: localhost is bigger than 1 IP, we need to ignore
// more things.
if !li.includeLocalhost && (mem.HasPrefix(local, mem.S(v4Localhost)) || mem.HasPrefix(local, mem.S(v6Localhost))) {
continue
}
// Don't use strings.Split here, because it causes
// allocations significant enough to show up in profiles.
i := mem.IndexByte(local, ':')
if i == -1 {
return fmt.Errorf("%q unexpectedly didn't have a colon", local.StringCopy())
}
portv, err := mem.ParseUint(local.SliceFrom(i+1), 16, 16)
if err != nil {
return fmt.Errorf("%#v: %s", local.SliceFrom(9).StringCopy(), err)
}
inoBuf = append(inoBuf[:0], "socket:["...)
inoBuf = mem.Append(inoBuf, inode)
inoBuf = append(inoBuf, ']')
if pm, ok := li.known[string(inoBuf)]; ok {
pm.keep = true
// Rest should be unchanged.
} else {
li.known[string(inoBuf)] = &portMeta{
needsProcName: true,
keep: true,
port: Port{
Proto: proto,
Port: uint16(portv),
},
}
}
}
return nil
}
// errDone is an internal sentinel error that we found everything we were looking for.
var errDone = errors.New("done")
// need is keyed by inode string.
func (li *linuxImpl) findProcessNames(need map[string]*portMeta) error {
if len(need) == 0 {
return nil
}
defer func() {
// Anything we didn't find, give up on and don't try to look for it later.
for _, pm := range need {
pm.needsProcName = false
}
}()
err := foreachPID(func(pid mem.RO) error {
var procBuf [128]byte
fdPath := mem.Append(procBuf[:0], mem.S("/proc/"))
fdPath = mem.Append(fdPath, pid)
fdPath = mem.Append(fdPath, mem.S("/fd"))
// Android logs a bunch of audit violations in logcat
// if we try to open things we don't have access
// to. So on Android only, ask if we have permission
// rather than just trying it to determine whether we
// have permission.
if runtime.GOOS == "android" && syscall.Access(string(fdPath), unix.R_OK) != nil {
return nil
}
dirwalk.WalkShallow(mem.B(fdPath), func(fd mem.RO, de fs.DirEntry) error {
targetBuf := make([]byte, 64) // plenty big for "socket:[165614651]"
linkPath := li.readlinkPathBuf[:0]
linkPath = fmt.Appendf(linkPath, "/proc/")
linkPath = mem.Append(linkPath, pid)
linkPath = append(linkPath, "/fd/"...)
linkPath = mem.Append(linkPath, fd)
linkPath = append(linkPath, 0) // terminating NUL
li.readlinkPathBuf = linkPath // to reuse its buffer next time
n, ok := readlink(linkPath, targetBuf)
if !ok {
// Not a symlink or no permission.
// Skip it.
return nil
}
pe := need[string(targetBuf[:n])] // m[string([]byte)] avoids alloc
if pe == nil {
return nil
}
bs, err := os.ReadFile(fmt.Sprintf("/proc/%s/cmdline", pid.StringCopy()))
if err != nil {
// Usually shouldn't happen. One possibility is
// the process has gone away, so let's skip it.
return nil
}
argv := strings.Split(strings.TrimSuffix(string(bs), "\x00"), "\x00")
if p, err := mem.ParseInt(pid, 10, 0); err == nil {
pe.pid = int(p)
}
pe.port.Process = argvSubject(argv...)
pid64, _ := mem.ParseInt(pid, 10, 0)
pe.port.Pid = int(pid64)
pe.needsProcName = false
delete(need, string(targetBuf[:n]))
if len(need) == 0 {
return errDone
}
return nil
})
return nil
})
if err == errDone {
return nil
}
return err
}
func foreachPID(fn func(pidStr mem.RO) error) error {
err := dirwalk.WalkShallow(mem.S("/proc"), func(name mem.RO, de fs.DirEntry) error {
if !isNumeric(name) {
return nil
}
return fn(name)
})
if os.IsNotExist(err) {
// This can happen if the directory we're
// reading disappears during the run. No big
// deal.
return nil
}
return err
}
func isNumeric(s mem.RO) bool {
for i, n := 0, s.Len(); i < n; i++ {
b := s.At(i)
if b < '0' || b > '9' {
return false
}
}
return s.Len() > 0
}
// fieldIndex returns the offset in line where the Nth field (0-based) begins, or -1
// if there aren't that many fields. Fields are separated by 1 or more spaces.
func fieldIndex(line []byte, n int) int {
skip := 0
for i := 0; i <= n; i++ {
// Skip spaces.
for skip < len(line) && line[skip] == ' ' {
skip++
}
if skip == len(line) {
return -1
}
if i == n {
break
}
// Skip non-space.
for skip < len(line) && line[skip] != ' ' {
skip++
}
}
return skip
}
// path must be null terminated.
func readlink(path, buf []byte) (n int, ok bool) {
if len(buf) == 0 || len(path) < 2 || path[len(path)-1] != 0 {
return 0, false
}
var dirfd int = unix.AT_FDCWD
r0, _, e1 := unix.Syscall6(unix.SYS_READLINKAT,
uintptr(dirfd),
uintptr(unsafe.Pointer(&path[0])),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(len(buf)),
0, 0)
n = int(r0)
if e1 != 0 {
return 0, false
}
return n, true
}

230
vendor/tailscale.com/portlist/portlist_macos.go generated vendored Normal file
View File

@@ -0,0 +1,230 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build darwin && !ios
package portlist
import (
"bufio"
"bytes"
"fmt"
"log"
"os/exec"
"strings"
"sync/atomic"
"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.
pollInterval = 5 * time.Second
}
type macOSImpl struct {
known map[protoPort]*portMeta // inode string => metadata
netstatPath string // lazily populated
br *bufio.Reader // reused
portsBuf []Port
includeLocalhost bool
}
type protoPort struct {
proto string
port uint16
}
type portMeta struct {
port Port
keep bool
}
func newMacOSImpl(includeLocalhost bool) osImpl {
return &macOSImpl{
known: map[protoPort]*portMeta{},
br: bufio.NewReader(bytes.NewReader(nil)),
includeLocalhost: includeLocalhost,
}
}
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)
}
}
if needProcs {
im.addProcesses() // best effort
}
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, im.includeLocalhost)
}
var lsofFailed atomic.Bool
// 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.
//
// 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
// tailscaled-on-macos mode.
func (im *macOSImpl) addProcesses() error {
if lsofFailed.Load() {
// This previously failed in the macOS sandbox, so don't try again.
return nil
}
exe, err := exec.LookPath("lsof")
if err != nil {
return fmt.Errorf("lsof: lookup: %v", err)
}
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 {
var stderr []byte
if xe, ok := err.(*exec.ExitError); ok {
stderr = xe.Stderr
}
// fails when run in a macOS sandbox, so make this non-fatal.
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))
}
return nil
}
defer func() {
ps, err := lsofCmd.Process.Wait()
if err != nil || ps.ExitCode() != 0 {
log.Printf("portlist: can't run lsof in Mac sandbox; omitting process names from service list. Error: %v, exit code %d", err, ps.ExitCode())
lsofFailed.Store(true)
}
}()
defer lsofCmd.Process.Kill()
im.br.Reset(outPipe)
var cmd, proto string
var pid int
for {
line, err := im.br.ReadBytes('\n')
if err != nil {
break
}
if len(line) < 1 {
continue
}
field, val := line[0], bytes.TrimSpace(line[1:])
switch field {
case 'p':
// starting a new process
cmd = ""
proto = ""
pid = 0
if p, err := mem.ParseInt(mem.B(val), 10, 0); err == nil {
pid = int(p)
}
case 'c':
cmd = string(val) // TODO(bradfitz): avoid garbage; cache process names between runs?
case 'P':
proto = lsofProtoLower(val)
case 'n':
if mem.Contains(mem.B(val), mem.S("->")) {
continue
}
// a listening port
port := parsePort(mem.B(val))
if port <= 0 {
continue
}
pp := protoPort{proto, uint16(port)}
m := im.known[pp]
switch {
case m != nil:
m.port.Process = cmd
m.port.Pid = pid
default:
// ignore: processes and ports come and go
}
}
}
return nil
}
func lsofProtoLower(p []byte) string {
if string(p) == "TCP" {
return "tcp"
}
if string(p) == "UDP" {
return "udp"
}
return strings.ToLower(string(p))
}

103
vendor/tailscale.com/portlist/portlist_windows.go generated vendored Normal file
View File

@@ -0,0 +1,103 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package portlist
import (
"time"
"tailscale.com/net/netstat"
)
func init() {
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 {
proto string
port uint16
pid uint32
}
type windowsImpl struct {
known map[famPort]*portMeta // inode string => metadata
includeLocalhost bool
}
type portMeta struct {
port Port
keep bool
}
func newWindowsImpl(includeLocalhost bool) osImpl {
return &windowsImpl{
known: map[famPort]*portMeta{},
includeLocalhost: includeLocalhost,
}
}
func (*windowsImpl) Close() error { return nil }
func (im *windowsImpl) AppendListeningPorts(base []Port) ([]Port, error) {
// TODO(bradfitz): netstat.Get makes a bunch of garbage. Add an Append-style
// API to that package instead/additionally.
tab, err := netstat.Get()
if err != nil {
return nil, err
}
for _, pm := range im.known {
pm.keep = false
}
ret := base
for _, e := range tab.Entries {
if e.State != "LISTEN" {
continue
}
if !im.includeLocalhost && !e.Local.Addr().IsUnspecified() {
continue
}
fp := famPort{
proto: "tcp", // TODO(bradfitz): UDP too; add to netstat
port: e.Local.Port(),
pid: uint32(e.Pid),
}
pm, ok := im.known[fp]
if ok {
pm.keep = true
continue
}
var process string
if e.OSMetadata != nil {
if module, err := e.OSMetadata.GetModule(); err == nil {
process = module
}
}
pm = &portMeta{
keep: true,
port: Port{
Proto: "tcp",
Port: e.Local.Port(),
Process: process,
Pid: e.Pid,
},
}
im.known[fp] = pm
}
for k, m := range im.known {
if !m.keep {
delete(im.known, k)
continue
}
ret = append(ret, m.port)
}
return sortAndDedup(ret), nil
}