Skip to content

Commit

Permalink
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Browse files Browse the repository at this point in the history
Change-Id: If98dc381f71fd2e83fb07cf09dbc61af0f87baca
  • Loading branch information
bradfitz committed Nov 5, 2024
1 parent 3a78ec4 commit 3c5d48f
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 101 deletions.
36 changes: 14 additions & 22 deletions net/netmon/interfaces_android.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package netmon

import (
"bytes"
"errors"
"log"
"net/netip"
"os/exec"
Expand Down Expand Up @@ -34,11 +33,6 @@ func init() {

var procNetRouteErr atomic.Bool

// errStopReading is a sentinel error value used internally by
// lineread.File callers to stop reading. It doesn't escape to
// callers/users.
var errStopReading = errors.New("stop reading")

/*
Parse 10.0.0.1 out of:
Expand All @@ -54,44 +48,42 @@ func likelyHomeRouterIPAndroid() (ret netip.Addr, myIP netip.Addr, ok bool) {
}
lineNum := 0
var f []mem.RO
err := lineread.File(procNetRoutePath, func(line []byte) error {
for lr := range lineread.File(procNetRoutePath) {
line, err := lr.Value()
if err != nil {
procNetRouteErr.Store(true)
return likelyHomeRouterIP()
}

lineNum++
if lineNum == 1 {
// Skip header line.
return nil
continue
}
if lineNum > maxProcNetRouteRead {
return errStopReading
break
}
f = mem.AppendFields(f[:0], mem.B(line))
if len(f) < 4 {
return nil
continue
}
gwHex, flagsHex := f[2], f[3]
flags, err := mem.ParseUint(flagsHex, 16, 16)
if err != nil {
return nil // ignore error, skip line and keep going
continue // ignore error, skip line and keep going
}
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
return nil
continue
}
ipu32, err := mem.ParseUint(gwHex, 16, 32)
if err != nil {
return nil // ignore error, skip line and keep going
continue // ignore error, skip line and keep going
}
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
if ip.IsPrivate() {
ret = ip
return errStopReading
break
}
return nil
})
if errors.Is(err, errStopReading) {
err = nil
}
if err != nil {
procNetRouteErr.Store(true)
return likelyHomeRouterIP()
}
if ret.IsValid() {
// Try to get the local IP of the interface associated with
Expand Down
22 changes: 11 additions & 11 deletions net/netmon/interfaces_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package netmon

import (
"errors"
"io"
"net/netip"
"os/exec"
Expand Down Expand Up @@ -73,31 +72,34 @@ func likelyHomeRouterIPDarwinExec() (ret netip.Addr, netif string, ok bool) {
defer io.Copy(io.Discard, stdout) // clear the pipe to prevent hangs

var f []mem.RO
lineread.Reader(stdout, func(lineb []byte) error {
for lr := range lineread.Reader(stdout) {
lineb, err := lr.Value()
if err != nil {
break
}
line := mem.B(lineb)
if !mem.Contains(line, mem.S("default")) {
return nil
continue
}
f = mem.AppendFields(f[:0], line)
if len(f) < 4 || !f[0].EqualString("default") {
return nil
continue
}
ipm, flagsm, netifm := f[1], f[2], f[3]
if !mem.Contains(flagsm, mem.S("G")) {
return nil
continue
}
if mem.Contains(flagsm, mem.S("I")) {
return nil
continue
}
ip, err := netip.ParseAddr(string(mem.Append(nil, ipm)))
if err == nil && ip.IsPrivate() {
ret = ip
netif = netifm.StringCopy()
// We've found what we're looking for.
return errStopReadingNetstatTable
break
}
return nil
})
}
return ret, netif, ret.IsValid()
}

Expand All @@ -110,5 +112,3 @@ func TestFetchRoutingTable(t *testing.T) {
}
}
}

var errStopReadingNetstatTable = errors.New("found private gateway")
35 changes: 14 additions & 21 deletions net/netmon/interfaces_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ func init() {

var procNetRouteErr atomic.Bool

// errStopReading is a sentinel error value used internally by
// lineread.File callers to stop reading. It doesn't escape to
// callers/users.
var errStopReading = errors.New("stop reading")

/*
Parse 10.0.0.1 out of:
Expand All @@ -52,44 +47,42 @@ func likelyHomeRouterIPLinux() (ret netip.Addr, myIP netip.Addr, ok bool) {
}
lineNum := 0
var f []mem.RO
err := lineread.File(procNetRoutePath, func(line []byte) error {
for lr := range lineread.File(procNetRoutePath) {
line, err := lr.Value()
if err != nil {
procNetRouteErr.Store(true)
log.Printf("interfaces: failed to read /proc/net/route: %v", err)
return ret, myIP, false
}
lineNum++
if lineNum == 1 {
// Skip header line.
return nil
continue
}
if lineNum > maxProcNetRouteRead {
return errStopReading
break
}
f = mem.AppendFields(f[:0], mem.B(line))
if len(f) < 4 {
return nil
continue
}
gwHex, flagsHex := f[2], f[3]
flags, err := mem.ParseUint(flagsHex, 16, 16)
if err != nil {
return nil // ignore error, skip line and keep going
continue // ignore error, skip line and keep going
}
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
return nil
continue
}
ipu32, err := mem.ParseUint(gwHex, 16, 32)
if err != nil {
return nil // ignore error, skip line and keep going
continue // ignore error, skip line and keep going
}
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
if ip.IsPrivate() {
ret = ip
return errStopReading
break
}
return nil
})
if errors.Is(err, errStopReading) {
err = nil
}
if err != nil {
procNetRouteErr.Store(true)
log.Printf("interfaces: failed to read /proc/net/route: %v", err)
}
if ret.IsValid() {
// Try to get the local IP of the interface associated with
Expand Down
2 changes: 2 additions & 0 deletions net/netmon/netmon_linux_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

//go:build linux && !android

package netmon

import (
Expand Down
13 changes: 7 additions & 6 deletions net/tshttpproxy/tshttpproxy_synology.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,22 @@ func synologyProxiesFromConfig() (*url.URL, *url.URL, error) {
func parseSynologyConfig(r io.Reader) (*url.URL, *url.URL, error) {
cfg := map[string]string{}

if err := lineread.Reader(r, func(line []byte) error {
for lr := range lineread.Reader(r) {
line, err := lr.Value()
if err != nil {
return nil, nil, err
}
// accept and skip over empty lines
line = bytes.TrimSpace(line)
if len(line) == 0 {
return nil
continue
}

key, value, ok := strings.Cut(string(line), "=")
if !ok {
return fmt.Errorf("missing \"=\" in proxy.conf line: %q", line)
return nil, nil, fmt.Errorf("missing \"=\" in proxy.conf line: %q", line)
}
cfg[string(key)] = string(value)
return nil
}); err != nil {
return nil, nil, err
}

if cfg["proxy_enabled"] != "yes" {
Expand Down
58 changes: 30 additions & 28 deletions util/lineread/lineread.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

// Package lineread reads lines from files. It's not fancy, but it got repetitive.
// Package lineread splits things into lines.
package lineread

import (
Expand All @@ -15,24 +15,12 @@ import (
)

// File returns an iterator that reads lines from the named file.
//
// The returned substrings don't include the trailing newline.
// Lines may be empty.
func File(name string) iter.Seq[result.Of[[]byte]] {
f, err := os.Open(name)
return func(yield func(result.Of[[]byte]) bool) {
if err != nil {
yield(result.Error[[]byte](err))
return
}
defer f.Close()
bs := bufio.NewScanner(f)
for bs.Scan() {
if !yield(result.Value(bs.Bytes())) {
return
}
}
if err := bs.Err(); err != nil {
yield(result.Error[[]byte](err))
}
}
return reader(f, f, err)
}

// Bytes returns an iterator over the lines in bs.
Expand All @@ -54,17 +42,31 @@ func Bytes(bs []byte) iter.Seq[[]byte] {
}
}

// Reader calls fn for each line.
// If fn returns an error, Reader stops reading and returns that error.
// Reader may also return errors encountered reading and parsing from r.
// To stop reading early, use a sentinel "stop" error value and ignore
// it when returned from Reader.
func Reader(r io.Reader, fn func(line []byte) error) error {
bs := bufio.NewScanner(r)
for bs.Scan() {
if err := fn(bs.Bytes()); err != nil {
return err
// Reader returns an iterator over the lines in r.
//
// The returned substrings don't include the trailing newline.
// Lines may be empty.
func Reader(r io.Reader) iter.Seq[result.Of[[]byte]] {
return reader(r, nil, nil)
}

func reader(r io.Reader, c io.Closer, err error) iter.Seq[result.Of[[]byte]] {
return func(yield func(result.Of[[]byte]) bool) {
if err != nil {
yield(result.Error[[]byte](err))
return
}
if c != nil {
defer c.Close()
}
bs := bufio.NewScanner(r)
for bs.Scan() {
if !yield(result.Value(bs.Bytes())) {
return
}
}
if err := bs.Err(); err != nil {
yield(result.Error[[]byte](err))
}
}
return bs.Err()
}
10 changes: 6 additions & 4 deletions util/lineread/lineread_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

package lineread

import (
Expand All @@ -19,10 +22,9 @@ func TestBytesLines(t *testing.T) {

func TestReader(t *testing.T) {
var got []string
Reader(strings.NewReader("foo\n\nbar\nbaz"), func(line []byte) error {
got = append(got, string(line))
return nil
})
for line := range Reader(strings.NewReader("foo\n\nbar\nbaz")) {
got = append(got, string(line.MustValue()))
}
want := []string{"foo", "", "bar", "baz"}
if !slices.Equal(got, want) {
t.Errorf("got %q; want %q", got, want)
Expand Down
18 changes: 9 additions & 9 deletions util/pidowner/pidowner_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ import (

func ownerOfPID(pid int) (userID string, err error) {
file := fmt.Sprintf("/proc/%d/status", pid)
err = lineread.File(file, func(line []byte) error {
for lr := range lineread.File(file) {
line, err := lr.Value()
if err != nil {
if os.IsNotExist(err) {
return "", ErrProcessNotFound
}
return "", err
}
if len(line) < 4 || string(line[:4]) != "Uid:" {
return nil
continue
}
f := strings.Fields(string(line))
if len(f) >= 2 {
userID = f[1] // real userid
}
return nil
})
if os.IsNotExist(err) {
return "", ErrProcessNotFound
}
if err != nil {
return
}
if userID == "" {
return "", fmt.Errorf("missing Uid line in %s", file)
Expand Down

0 comments on commit 3c5d48f

Please sign in to comment.