| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- // 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
- }
|