Update dependencies
This commit is contained in:
36
vendor/tailscale.com/portlist/clean.go
generated
vendored
Normal file
36
vendor/tailscale.com/portlist/clean.go
generated
vendored
Normal 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
132
vendor/tailscale.com/portlist/netstat.go
generated
vendored
Normal 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
122
vendor/tailscale.com/portlist/poller.go
generated
vendored
Normal 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
80
vendor/tailscale.com/portlist/portlist.go
generated
vendored
Normal 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
408
vendor/tailscale.com/portlist/portlist_linux.go
generated
vendored
Normal 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
230
vendor/tailscale.com/portlist/portlist_macos.go
generated
vendored
Normal 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
103
vendor/tailscale.com/portlist/portlist_windows.go
generated
vendored
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user