mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
46ce80758d
In prep for reducing garbage, being able to reuse memory. So far this doesn't actually reuse much. This is just changing signatures around. But some improvement in any case: bradfitz@tsdev:~/src/tailscale.com$ ~/go/bin/benchstat before after name old time/op new time/op delta GetList-8 11.8ms ± 9% 9.9ms ± 3% -15.98% (p=0.000 n=10+10) name old alloc/op new alloc/op delta GetList-8 99.5kB ± 2% 91.9kB ± 0% -7.62% (p=0.000 n=9+9) name old allocs/op new allocs/op delta GetList-8 3.05k ± 1% 2.93k ± 0% -3.83% (p=0.000 n=8+9) More later, once parsers can reuse strings from previous parses. Updates #5958 Change-Id: I76cd5048246dd24d11c4e263d8bb8041747fb2b0 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
149 lines
3.3 KiB
Go
149 lines
3.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.
|
|
|
|
//go:build !ios && !js
|
|
// +build !ios,!js
|
|
|
|
package portlist
|
|
|
|
import (
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func parsePort(s string) int {
|
|
// a.b.c.d:1234 or [a:b:c:d]:1234
|
|
i1 := strings.LastIndexByte(s, ':')
|
|
// a.b.c.d.1234 or [a:b:c:d].1234
|
|
i2 := strings.LastIndexByte(s, '.')
|
|
|
|
i := i1
|
|
if i2 > i {
|
|
i = i2
|
|
}
|
|
if i < 0 {
|
|
// no match; weird
|
|
return -1
|
|
}
|
|
|
|
portstr := s[i+1:]
|
|
if portstr == "*" {
|
|
return 0
|
|
}
|
|
|
|
port, err := strconv.ParseUint(portstr, 10, 16)
|
|
if err != nil {
|
|
// invalid port; weird
|
|
return -1
|
|
}
|
|
|
|
return int(port)
|
|
}
|
|
|
|
func isLoopbackAddr(s string) bool {
|
|
return strings.HasPrefix(s, "127.") ||
|
|
strings.HasPrefix(s, "[::1]:") ||
|
|
strings.HasPrefix(s, "::1.")
|
|
}
|
|
|
|
type nothing struct{}
|
|
|
|
// 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.
|
|
func appendParsePortsNetstat(base []Port, output string) []Port {
|
|
m := map[Port]nothing{}
|
|
lines := strings.Split(string(output), "\n")
|
|
|
|
var lastline string
|
|
var lastport Port
|
|
for _, line := range lines {
|
|
trimline := strings.TrimSpace(line)
|
|
cols := strings.Fields(trimline)
|
|
if len(cols) < 1 {
|
|
continue
|
|
}
|
|
protos := strings.ToLower(cols[0])
|
|
var proto, laddr, raddr string
|
|
if strings.HasPrefix(protos, "tcp") {
|
|
if len(cols) < 4 {
|
|
continue
|
|
}
|
|
proto = "tcp"
|
|
laddr = cols[len(cols)-3]
|
|
raddr = cols[len(cols)-2]
|
|
state := cols[len(cols)-1]
|
|
if !strings.HasPrefix(state, "LISTEN") {
|
|
// not interested in non-listener sockets
|
|
continue
|
|
}
|
|
if isLoopbackAddr(laddr) {
|
|
// not interested in loopback-bound listeners
|
|
continue
|
|
}
|
|
} else if strings.HasPrefix(protos, "udp") {
|
|
if len(cols) < 3 {
|
|
continue
|
|
}
|
|
proto = "udp"
|
|
laddr = cols[len(cols)-2]
|
|
raddr = cols[len(cols)-1]
|
|
if isLoopbackAddr(laddr) {
|
|
// not interested in loopback-bound listeners
|
|
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 {
|
|
// not interested in other protocols
|
|
lastline = trimline
|
|
continue
|
|
}
|
|
|
|
lport := parsePort(laddr)
|
|
rport := parsePort(raddr)
|
|
if rport != 0 || lport <= 0 {
|
|
// not interested in "connected" sockets
|
|
continue
|
|
}
|
|
|
|
p := Port{
|
|
Proto: proto,
|
|
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
|
|
}
|