wgengine: don't allocate so much in userspaceEngine.getStatus

It was one of the top garbage producers on my phone.

It's slated to be deleted and replaced anyway, but this helps in the
meantime.

The go.sum changes look scary, but the new dep only adds 240 bytes to
the binary. The go.sum noise is just cmd/go being aggressive in
including a lot of stuff (which is being fixed in Go 1.15, for what I
understand). And I ran a go mod tidy, which added some too. (I had to
write a custom wrapper around go mod tidy because this mod tidy
normally breaks on tailscale.io/control being missing but referenced
in tests)

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2020-04-08 08:42:38 -07:00
committed by Brad Fitzpatrick
parent 6b2e29867e
commit 922d9546bf
4 changed files with 260 additions and 108 deletions

View File

@ -6,11 +6,12 @@ package wgengine
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"log"
"net"
"strconv"
"strings"
"sync"
"time"
@ -18,6 +19,7 @@ import (
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"github.com/tailscale/wireguard-go/wgcfg"
"go4.org/mem"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/interfaces"
"tailscale.com/tailcfg"
@ -451,6 +453,8 @@ func (e *userspaceEngine) getStatusCallback() StatusCallback {
return e.statusCallback
}
// TODO: this function returns an error but it's always nil, and when
// there's actually a problem it just calls log.Fatal. Why?
func (e *userspaceEngine) getStatus() (*Status, error) {
e.wgLock.Lock()
defer e.wgLock.Unlock()
@ -463,58 +467,70 @@ func (e *userspaceEngine) getStatus() (*Status, error) {
return nil, nil
}
// TODO(apenwarr): get rid of silly uapi stuff for in-process comms
// FIXME: get notified of status changes instead of polling.
var bb strings.Builder
bio := bufio.NewWriter(&bb)
ipcErr := e.wgdev.IpcGetOperation(bio)
if ipcErr != nil {
log.Fatalf("IpcGetOperation: %v\n", ipcErr)
}
bio.Flush()
// lineLen is the max UAPI line we expect. The longest I see is
// len("preshared_key=")+64 hex+"\n" == 79. Add some slop.
const lineLen = 100
pr, pw := io.Pipe()
errc := make(chan error, 1)
go func() {
defer pw.Close()
bw := bufio.NewWriterSize(pw, lineLen)
// TODO(apenwarr): get rid of silly uapi stuff for in-process comms
// FIXME: get notified of status changes instead of polling.
if err := e.wgdev.IpcGetOperation(bw); err != nil {
errc <- fmt.Errorf("IpcGetOperation: %w", err)
return
}
errc <- bw.Flush()
}()
s := Status{}
pp := make(map[wgcfg.Key]*PeerStatus)
var p *PeerStatus = &PeerStatus{}
bbs := bb.String()
lines := strings.Split(bbs, "\n")
p := &PeerStatus{}
var hst1, hst2, n int64
var err error
for _, line := range lines {
k, v := line, ""
if i := strings.IndexByte(line, '='); i != -1 {
k, v = line[:i], line[i+1:]
bs := bufio.NewScanner(pr)
bs.Buffer(make([]byte, lineLen), lineLen)
for bs.Scan() {
line := bs.Bytes()
k := line
var v mem.RO
if i := bytes.IndexByte(line, '='); i != -1 {
k = line[:i]
v = mem.B(line[i+1:])
}
switch k {
switch string(k) {
case "public_key":
pk, err := wgcfg.ParseHexKey(v)
pk, err := key.NewPublicFromHexMem(v)
if err != nil {
log.Fatalf("IpcGetOperation: invalid key %#v\n", v)
}
p = &PeerStatus{}
pp[pk] = p
pp[wgcfg.Key(pk)] = p
key := tailcfg.NodeKey(pk)
p.NodeKey = key
case "rx_bytes":
n, err = strconv.ParseInt(v, 10, 64)
n, err = v.ParseInt(10, 64)
p.RxBytes = ByteCount(n)
if err != nil {
log.Fatalf("IpcGetOperation: rx_bytes invalid: %#v\n", line)
}
case "tx_bytes":
n, err = strconv.ParseInt(v, 10, 64)
n, err = v.ParseInt(10, 64)
p.TxBytes = ByteCount(n)
if err != nil {
log.Fatalf("IpcGetOperation: tx_bytes invalid: %#v\n", line)
}
case "last_handshake_time_sec":
hst1, err = strconv.ParseInt(v, 10, 64)
hst1, err = v.ParseInt(10, 64)
if err != nil {
log.Fatalf("IpcGetOperation: hst1 invalid: %#v\n", line)
}
case "last_handshake_time_nsec":
hst2, err = strconv.ParseInt(v, 10, 64)
hst2, err = v.ParseInt(10, 64)
if err != nil {
log.Fatalf("IpcGetOperation: hst2 invalid: %#v\n", line)
}
@ -523,6 +539,12 @@ func (e *userspaceEngine) getStatus() (*Status, error) {
} // else leave at time.IsZero()
}
}
if err := bs.Err(); err != nil {
log.Fatalf("reading IpcGetOperation output: %v", err)
}
if err := <-errc; err != nil {
log.Fatalf("IpcGetOperation: %v", err)
}
e.mu.Lock()
defer e.mu.Unlock()
@ -537,7 +559,7 @@ func (e *userspaceEngine) getStatus() (*Status, error) {
}
if len(pp) != len(e.peerSequence) {
e.logf("wg status returned %v peers, expected %v\n", len(s.Peers), len(e.peerSequence))
e.logf("wg status returned %v peers, expected %v\n", len(pp), len(e.peerSequence))
}
return &Status{