tailscale/portlist/portlist_macos.go
Brad Fitzpatrick 5bb14c07dc portlist: don't depend on tempfork/osexec on iOS, saves 90KB
This gives us 90KB more of memory on iOS, as it shrinks the
NetworkExtension binary by 90KB.

The netstat binary isn't available in the network extension anyway, so
no point pulling in the osexec package which'll just fail to find
netstat anyway.
2020-04-07 07:58:09 -07:00

101 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 darwin,amd64
package portlist
import (
"bufio"
"bytes"
"fmt"
"log"
"os"
"strings"
"time"
exec "tailscale.com/tempfork/osexec"
)
// We have to run netstat, which is a bit expensive, so don't do it too often.
const pollInterval = 5 * time.Second
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)
}
output, err := exec.Command(exe, "-F", "-n", "-P", "-O", "-S2", "-T", "-i4", "-i6").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 == "" {
continue
}
field, val := line[0], line[1:]
switch field {
case 'p':
// starting a new process
cmd = ""
proto = ""
case 'c':
cmd = val
case 'P':
proto = strings.ToLower(val)
case 'n':
if strings.Contains(val, "->") {
continue
}
// a listening port
port := parsePort(val)
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
}