Просмотр исходного кода

portlist: collect IPv6 listening sockets on linux.

This is important because some of those v6 sockets are actually
dual-stacked sockets, so this is our only chance of discovering
some services.

Fixes #1443.

Signed-off-by: David Anderson <[email protected]>
David Anderson 5 лет назад
Родитель
Сommit
63a9adeb6c
2 измененных файлов с 137 добавлено и 42 удалено
  1. 70 42
      portlist/portlist_linux.go
  2. 67 0
      portlist/portlist_linux_test.go

+ 70 - 42
portlist/portlist_linux.go

@@ -10,6 +10,7 @@ import (
 	"io"
 	"io/ioutil"
 	"os"
+	"path/filepath"
 	"runtime"
 	"sort"
 	"strconv"
@@ -26,19 +27,25 @@ const pollInterval = 1 * time.Second
 
 // TODO(apenwarr): Include IPv6 ports eventually.
 // Right now we don't route IPv6 anyway so it's better to exclude them.
-var sockfiles = []string{"/proc/net/tcp", "/proc/net/udp"}
-var protos = []string{"tcp", "udp"}
+var sockfiles = []string{"/proc/net/tcp", "/proc/net/tcp6", "/proc/net/udp", "/proc/net/udp6"}
 
 var sawProcNetPermissionErr syncs.AtomicBool
 
+const (
+	v6Localhost = "00000000000000000000000001000000:"
+	v6Any       = "00000000000000000000000000000000:0000"
+	v4Localhost = "0100007F:"
+	v4Any       = "00000000:0000"
+)
+
 func listPorts() (List, error) {
 	if sawProcNetPermissionErr.Get() {
 		return nil, nil
 	}
 	l := []Port{}
 
-	for pi, fname := range sockfiles {
-		proto := protos[pi]
+	for _, fname := range sockfiles {
+		proto := strings.TrimSuffix(filepath.Base(fname), "6")
 
 		// Android 10+ doesn't allow access to this anymore.
 		// https://developer.android.com/about/versions/10/privacy/changes#proc-net-filesystem
@@ -59,47 +66,12 @@ func listPorts() (List, error) {
 		defer f.Close()
 		r := bufio.NewReader(f)
 
-		// skip header row
-		_, err = r.ReadString('\n')
+		ports, err := parsePorts(r, proto)
 		if err != nil {
-			return nil, err
+			return nil, fmt.Errorf("parsing %q: %w", fname, err)
 		}
 
-		for err == nil {
-			line, err := r.ReadString('\n')
-			if err == io.EOF {
-				break
-			}
-			if err != nil {
-				return nil, err
-			}
-
-			// sl local rem ... inode
-			words := strings.Fields(line)
-			local := words[1]
-			rem := words[2]
-			inode := words[9]
-
-			// If a port is bound to 127.0.0.1, ignore it.
-			if strings.HasPrefix(local, "0100007F:") {
-				continue
-			}
-			if rem != "00000000:0000" {
-				// not a "listener" port
-				continue
-			}
-
-			portv, err := strconv.ParseUint(local[9:], 16, 16)
-			if err != nil {
-				return nil, fmt.Errorf("%#v: %s", local[9:], err)
-			}
-			inodev := fmt.Sprintf("socket:[%s]", inode)
-			l = append(l, Port{
-				Proto: proto,
-				Port:  uint16(portv),
-				inode: inodev,
-			})
-		}
+		l = append(l, ports...)
 	}
 
 	sort.Slice(l, func(i, j int) bool {
@@ -109,6 +81,62 @@ func listPorts() (List, error) {
 	return l, nil
 }
 
+func parsePorts(r *bufio.Reader, proto string) ([]Port, error) {
+	var ret []Port
+
+	// skip header row
+	_, err := r.ReadString('\n')
+	if err != nil {
+		return nil, err
+	}
+
+	for err == nil {
+		line, err := r.ReadString('\n')
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			return nil, err
+		}
+
+		// sl local rem ... inode
+		words := strings.Fields(line)
+		local := words[1]
+		rem := words[2]
+		inode := words[9]
+
+		// If a port is bound to localhost, ignore it.
+		// TODO: localhost is bigger than 1 IP, we need to ignore
+		// more things.
+		if strings.HasPrefix(local, v4Localhost) || strings.HasPrefix(local, v6Localhost) {
+			continue
+		}
+		if rem != v4Any && rem != v6Any {
+			// not a "listener" port
+			continue
+		}
+
+		// Don't use strings.Split here, because it causes
+		// allocations significant enough to show up in profiles.
+		i := strings.IndexByte(local, ':')
+		if i == -1 {
+			return nil, fmt.Errorf("%q unexpectedly didn't have a colon", local)
+		}
+		portv, err := strconv.ParseUint(local[i+1:], 16, 16)
+		if err != nil {
+			return nil, fmt.Errorf("%#v: %s", local[9:], err)
+		}
+		inodev := fmt.Sprintf("socket:[%s]", inode)
+		ret = append(ret, Port{
+			Proto: proto,
+			Port:  uint16(portv),
+			inode: inodev,
+		})
+	}
+
+	return ret, nil
+}
+
 func addProcesses(pl []Port) ([]Port, error) {
 	pm := map[string]*Port{} // by Port.inode
 	for i := range pl {

+ 67 - 0
portlist/portlist_linux_test.go

@@ -0,0 +1,67 @@
+// 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.
+
+package portlist
+
+import (
+	"bufio"
+	"bytes"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestParsePorts(t *testing.T) {
+	tests := []struct {
+		name string
+		in   string
+		want []Port
+	}{
+		{
+			name: "empty",
+			in:   "header line (ignored)\n",
+			want: nil,
+		},
+		{
+			name: "ipv4",
+			in: `header line
+  0: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 22303 1 0000000000000000 100 0 0 10 0
+  1: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 34062 1 0000000000000000 100 0 0 10 0
+  2: 5501A8C0:ADD4 B25E9536:01BB 01 00000000:00000000 02:00000B2B 00000000  1000        0 155276677 2 0000000000000000 22 4 30 10 -1
+`,
+			want: []Port{
+				{Proto: "tcp", Port: 22, inode: "socket:[34062]"},
+			},
+		},
+		{
+			name: "ipv6",
+			in: `  sl  local_address                         remote_address                        st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
+   0: 00000000000000000000000001000000:0277 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 35720 1 0000000000000000 100 0 0 10 0
+   1: 00000000000000000000000000000000:1F91 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 142240557 1 0000000000000000 100 0 0 10 0
+   2: 00000000000000000000000000000000:0016 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 34064 1 0000000000000000 100 0 0 10 0
+   3: 69050120005716BC64906EBE009ECD4D:D506 0047062600000000000000006E171268:01BB 01 00000000:00000000 02:0000009E 00000000  1000        0 151042856 2 0000000000000000 21 4 28 10 -1
+`,
+			want: []Port{
+				{Proto: "tcp", Port: 8081, inode: "socket:[142240557]"},
+				{Proto: "tcp", Port: 22, inode: "socket:[34064]"},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			buf := bytes.NewBufferString(test.in)
+			r := bufio.NewReader(buf)
+
+			got, err := parsePorts(r, "tcp")
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			if diff := cmp.Diff(got, test.want, cmp.AllowUnexported(Port{})); diff != "" {
+				t.Errorf("unexpected parsed ports (-got+want):\n%s", diff)
+			}
+		})
+	}
+}