From 4d15e954bdef74dc37fe7d435eae781d99525972 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 11 Jan 2021 12:46:45 -0800 Subject: [PATCH 01/34] net/flowtrack: add new package to specialize groupcache/lru key type Reduces allocs. Signed-off-by: Brad Fitzpatrick --- net/flowtrack/flowtrack.go | 99 +++++++++++++++++++++++++++++++++ net/flowtrack/flowtrack_test.go | 82 +++++++++++++++++++++++++++ wgengine/filter/filter.go | 23 +++----- 3 files changed, 189 insertions(+), 15 deletions(-) create mode 100644 net/flowtrack/flowtrack.go create mode 100644 net/flowtrack/flowtrack_test.go diff --git a/net/flowtrack/flowtrack.go b/net/flowtrack/flowtrack.go new file mode 100644 index 000000000..8d490d854 --- /dev/null +++ b/net/flowtrack/flowtrack.go @@ -0,0 +1,99 @@ +// Copyright (c) 2021 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. +// +// Original implementation (from same author) from which this was derived was: +// https://github.com/golang/groupcache/blob/5b532d6fd5efaf7fa130d4e859a2fde0fc3a9e1b/lru/lru.go +// ... which was Apache licensed: +// https://github.com/golang/groupcache/blob/master/LICENSE + +// Package flowtrack contains types for tracking TCP/UDP flows by 4-tuples. +package flowtrack + +import ( + "container/list" + + "inet.af/netaddr" +) + +// Tuple is a 4-tuple of source and destination IP and port. +type Tuple struct { + Src netaddr.IPPort + Dst netaddr.IPPort +} + +// Cache is an LRU cache keyed by Tuple. +// +// The zero value is valid to use. +// +// It is not safe for concurrent access. +type Cache struct { + // MaxEntries is the maximum number of cache entries before + // an item is evicted. Zero means no limit. + MaxEntries int + + ll *list.List + m map[Tuple]*list.Element // of *entry +} + +// entry is the container/list element type. +type entry struct { + key Tuple + value interface{} +} + +// Add adds a value to the cache, set or updating its assoicated +// value. +// +// If MaxEntries is non-zero and the length of the cache is greater +// after any addition, the least recently used value is evicted. +func (c *Cache) Add(key Tuple, value interface{}) { + if c.m == nil { + c.m = make(map[Tuple]*list.Element) + c.ll = list.New() + } + if ee, ok := c.m[key]; ok { + c.ll.MoveToFront(ee) + ee.Value.(*entry).value = value + return + } + ele := c.ll.PushFront(&entry{key, value}) + c.m[key] = ele + if c.MaxEntries != 0 && c.Len() > c.MaxEntries { + c.RemoveOldest() + } +} + +// Get looks up a key's value from the cache, also reporting +// whether it was present. +func (c *Cache) Get(key Tuple) (value interface{}, ok bool) { + if ele, hit := c.m[key]; hit { + c.ll.MoveToFront(ele) + return ele.Value.(*entry).value, true + } + return nil, false +} + +// Remove removes the provided key from the cache if it was present. +func (c *Cache) Remove(key Tuple) { + if ele, hit := c.m[key]; hit { + c.removeElement(ele) + } +} + +// RemoveOldest removes the oldest item from the cache, if any. +func (c *Cache) RemoveOldest() { + if c.ll != nil { + if ele := c.ll.Back(); ele != nil { + c.removeElement(ele) + } + } +} + +func (c *Cache) removeElement(e *list.Element) { + c.ll.Remove(e) + delete(c.m, e.Value.(*entry).key) +} + +// Len returns the number of items in the cache. +func (c *Cache) Len() int { return len(c.m) } diff --git a/net/flowtrack/flowtrack_test.go b/net/flowtrack/flowtrack_test.go new file mode 100644 index 000000000..4c473c717 --- /dev/null +++ b/net/flowtrack/flowtrack_test.go @@ -0,0 +1,82 @@ +// Copyright (c) 2021 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 flowtrack + +import ( + "testing" + + "inet.af/netaddr" +) + +func TestCache(t *testing.T) { + c := &Cache{MaxEntries: 2} + + k1 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("1.1.1.1:1")} + k2 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("2.2.2.2:2")} + k3 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("3.3.3.3:3")} + k4 := Tuple{Src: netaddr.MustParseIPPort("1.1.1.1:1"), Dst: netaddr.MustParseIPPort("4.4.4.4:4")} + + wantLen := func(want int) { + t.Helper() + if got := c.Len(); got != want { + t.Fatalf("Len = %d; want %d", got, want) + } + } + wantVal := func(key Tuple, want interface{}) { + t.Helper() + got, ok := c.Get(key) + if !ok { + t.Fatalf("Get(%q) failed; want value %v", key, want) + } + if got != want { + t.Fatalf("Get(%q) = %v; want %v", key, got, want) + } + } + wantMissing := func(key Tuple) { + t.Helper() + if got, ok := c.Get(key); ok { + t.Fatalf("Get(%q) = %v; want absent from cache", key, got) + } + } + + wantLen(0) + c.RemoveOldest() // shouldn't panic + c.Remove(k4) // shouldn't panic + + c.Add(k1, 1) + wantLen(1) + c.Add(k2, 2) + wantLen(2) + c.Add(k3, 3) + wantLen(2) // hit the max + + wantMissing(k1) + c.Remove(k1) + wantLen(2) // no change; k1 should've been the deleted one per LRU + + wantVal(k3, 3) + + wantVal(k2, 2) + c.Remove(k2) + wantLen(1) + wantMissing(k2) + + c.Add(k3, 30) + wantVal(k3, 30) + wantLen(1) + + allocs := int(testing.AllocsPerRun(1000, func() { + got, ok := c.Get(k3) + if !ok { + t.Fatal("missing k3") + } + if got != 30 { + t.Fatalf("got = %d; want 30", got) + } + })) + if allocs != 0 { + t.Errorf("allocs = %v; want 0", allocs) + } +} diff --git a/wgengine/filter/filter.go b/wgengine/filter/filter.go index f35578e15..a0bdbf3af 100644 --- a/wgengine/filter/filter.go +++ b/wgengine/filter/filter.go @@ -10,9 +10,9 @@ import ( "sync" "time" - "github.com/golang/groupcache/lru" "golang.org/x/time/rate" "inet.af/netaddr" + "tailscale.com/net/flowtrack" "tailscale.com/net/packet" "tailscale.com/types/logger" ) @@ -41,17 +41,10 @@ type Filter struct { state *filterState } -// tuple is a 4-tuple of source and destination IP and port. It's used -// as a lookup key in filterState. -type tuple struct { - Src netaddr.IPPort - Dst netaddr.IPPort -} - // filterState is a state cache of past seen packets. type filterState struct { mu sync.Mutex - lru *lru.Cache // of tuple + lru *flowtrack.Cache // from flowtrack.Tuple -> nil } // lruMax is the size of the LRU cache in filterState. @@ -141,7 +134,7 @@ func New(matches []Match, localNets []netaddr.IPPrefix, shareStateWith *Filter, state = shareStateWith.state } else { state = &filterState{ - lru: lru.New(lruMax), + lru: &flowtrack.Cache{MaxEntries: lruMax}, } } f := &Filter{ @@ -334,7 +327,7 @@ func (f *Filter) runIn4(q *packet.Parsed) (r Response, why string) { return Accept, "tcp ok" } case packet.UDP: - t := tuple{q.Src, q.Dst} + t := flowtrack.Tuple{Src: q.Src, Dst: q.Dst} f.state.mu.Lock() _, ok := f.state.lru.Get(t) @@ -389,7 +382,7 @@ func (f *Filter) runIn6(q *packet.Parsed) (r Response, why string) { return Accept, "tcp ok" } case packet.UDP: - t := tuple{q.Src, q.Dst} + t := flowtrack.Tuple{Src: q.Src, Dst: q.Dst} f.state.mu.Lock() _, ok := f.state.lru.Get(t) @@ -413,10 +406,10 @@ func (f *Filter) runOut(q *packet.Parsed) (r Response, why string) { return Accept, "ok out" } - t := tuple{q.Dst, q.Src} - var ti interface{} = t // allocate once, rather than twice inside mutex + tuple := flowtrack.Tuple{Src: q.Dst, Dst: q.Src} // src/dst reversed + f.state.mu.Lock() - f.state.lru.Add(ti, ti) + f.state.lru.Add(tuple, nil) f.state.mu.Unlock() return Accept, "ok out" } From a80446c026e07fb85a599393565fa71a1e1b7fa1 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 11 Jan 2021 13:17:18 -0800 Subject: [PATCH 02/34] Update depaware (removes lru from wgengine/filter) --- cmd/tailscale/depaware.txt | 3 ++- cmd/tailscaled/depaware.txt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 4edd14a02..857f8672d 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -9,7 +9,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+ W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet L 💣 github.com/godbus/dbus/v5 from tailscale.com/wgengine/router/dns - github.com/golang/groupcache/lru from tailscale.com/wgengine/filter+ + github.com/golang/groupcache/lru from tailscale.com/wgengine/magicsock L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+ @@ -52,6 +52,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/logtail/backoff from tailscale.com/control/controlclient+ tailscale.com/metrics from tailscale.com/derp tailscale.com/net/dnscache from tailscale.com/control/controlclient+ + tailscale.com/net/flowtrack from tailscale.com/wgengine/filter 💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscale/cli+ tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli+ tailscale.com/net/netns from tailscale.com/control/controlclient+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 620c98c6a..6190b7388 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -9,7 +9,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+ W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet L 💣 github.com/godbus/dbus/v5 from tailscale.com/wgengine/router/dns - github.com/golang/groupcache/lru from tailscale.com/wgengine/filter+ + github.com/golang/groupcache/lru from tailscale.com/wgengine/magicsock github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+ L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink @@ -87,6 +87,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/logtail/filch from tailscale.com/logpolicy tailscale.com/metrics from tailscale.com/derp tailscale.com/net/dnscache from tailscale.com/control/controlclient+ + tailscale.com/net/flowtrack from tailscale.com/wgengine/filter 💣 tailscale.com/net/interfaces from tailscale.com/ipn+ tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock tailscale.com/net/netns from tailscale.com/control/controlclient+ From f85769b1ed1bbd1e16bf3b611df716b9679d2abc Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 11 Jan 2021 13:23:04 -0800 Subject: [PATCH 03/34] wgengine/magicsock: drop netaddr.IPPort cache netaddr.IP no longer allocates, so don't need a cache or all its associated code/complexity. This totally removes groupcache/lru from the deps. Also go mod tidy. --- cmd/tailscale/depaware.txt | 1 - cmd/tailscaled/depaware.txt | 1 - go.mod | 9 +---- go.sum | 72 ++++----------------------------- wgengine/magicsock/magicsock.go | 49 +--------------------- 5 files changed, 11 insertions(+), 121 deletions(-) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 857f8672d..62b8ad4aa 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -9,7 +9,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+ W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet L 💣 github.com/godbus/dbus/v5 from tailscale.com/wgengine/router/dns - github.com/golang/groupcache/lru from tailscale.com/wgengine/magicsock L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 6190b7388..9798ad6eb 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -9,7 +9,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+ W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet L 💣 github.com/godbus/dbus/v5 from tailscale.com/wgengine/router/dns - github.com/golang/groupcache/lru from tailscale.com/wgengine/magicsock github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+ L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink diff --git a/go.mod b/go.mod index 6ddb56703..868a98a44 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/go-multierror/multierror v1.0.2 github.com/go-ole/go-ole v1.2.4 github.com/godbus/dbus/v5 v5.0.3 - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e github.com/golang/protobuf v1.4.2 // indirect github.com/google/go-cmp v0.5.4 github.com/goreleaser/nfpm v1.1.10 @@ -22,17 +21,13 @@ require ( github.com/mdlayher/netlink v1.2.0 github.com/mdlayher/sdnotify v0.0.0-20200625151349-e4a4f32afc4a github.com/miekg/dns v1.1.30 - github.com/onsi/ginkgo v1.10.1 // indirect - github.com/onsi/gomega v1.7.0 // indirect github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 github.com/peterbourgon/ff/v2 v2.0.0 github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 github.com/tailscale/wireguard-go v0.0.0-20210109012254-dc30a1b9415e github.com/tcnksm/go-httpstat v0.2.0 github.com/toqueteos/webbrowser v1.2.0 - go4.org/intern v0.0.0-20201223061701-969c7e87e7cb // indirect go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 - go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 golang.org/x/net v0.0.0-20201216054612-986b41b23924 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d @@ -42,10 +37,8 @@ require ( golang.org/x/time v0.0.0-20191024005414-555d28b269f0 golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8 - gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect - gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect gvisor.dev/gvisor v0.0.0-20210111185822-3ff3110fcdd6 honnef.co/go/tools v0.1.0 - inet.af/netaddr v0.0.0-20201231012616-c5dc91d2a016 + inet.af/netaddr v0.0.0-20210105212526-648fbc18a69d rsc.io/goversion v1.2.0 ) diff --git a/go.sum b/go.sum index fcddc3fd0..28b4a6e4c 100644 --- a/go.sum +++ b/go.sum @@ -24,7 +24,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14= github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -54,12 +53,9 @@ github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmE github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/cgroups v0.0.0-20181219155423-39b18af02c41/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a/go.mod h1:W0qIOTD7mp2He++YVq+kgfXezRYqzP1uDuMVH1bITDY= github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= @@ -83,9 +79,7 @@ github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/dpjacques/clockwork v0.1.1-0.20190114191937-d864eecc357b/go.mod h1:D8mP2A8vVT2GkXqPorSBmhnshhkFBYgzhA90KmJt25Y= github.com/dpjacques/clockwork v0.1.1-0.20200827220843-c1f524b839be/go.mod h1:D8mP2A8vVT2GkXqPorSBmhnshhkFBYgzhA90KmJt25Y= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dvyukov/go-fuzz v0.0.0-20201127111758-49e582c6c23d/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= @@ -123,8 +117,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -140,6 +132,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= @@ -211,9 +204,7 @@ github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1 h1:zc0R6cOw98cMengLA0fvU5 github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/lxn/walk v0.0.0-20201110160827-18ea5e372cdb/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= -github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= @@ -241,13 +232,10 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -263,7 +251,6 @@ github.com/peterbourgon/ff/v2 v2.0.0 h1:lx0oYI5qr/FU1xnpNhQ+EZM04gKgn46jyYvGEEqB github.com/peterbourgon/ff/v2 v2.0.0/go.mod h1:xjwr+t+SjWm4L46fcj/D+Ap+6ME7+HqFzaP22pP5Ggk= github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 h1:+/+DxvQaYifJ+grD4klzrS5y+KJXldn/2YTl5JG+vZ8= github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -280,7 +267,6 @@ github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1: github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -298,54 +284,40 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tailscale/depaware v0.0.0-20201003033024-5d95aab075be/go.mod h1:jissDaJNHiyV2tFdr3QyNEfsZrax/i2yQiSO+CljThI= github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 h1:lK99QQdH3yBWY6aGilF+IRlQIdmhzLrsEmF6JgN+Ryw= github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= -github.com/tailscale/wireguard-go v0.0.0-20201220011020-db78fad0bebf h1:HuBwLWbDNIh/G72KSImSEx+dnd7FPGFI1e60LMJtLjU= -github.com/tailscale/wireguard-go v0.0.0-20201220011020-db78fad0bebf/go.mod h1:9PbAnF5CAklkURoO0uQhm+YUjDmm9T9oCyTGlCHuTPQ= -github.com/tailscale/wireguard-go v0.0.0-20201228234719-da0d2727455d h1:ha3qx0YBsEYM1VpLoAxVyLsz74H2a/Kv/id+2Bo/WLU= -github.com/tailscale/wireguard-go v0.0.0-20201228234719-da0d2727455d/go.mod h1:FEGDKc5yHNWtTS5ugWnHMNF0d9LlaHv/zQwOrVogo2U= -github.com/tailscale/wireguard-go v0.0.0-20210108235412-4a0869cbdb90 h1:a4vBvXdw8zlO01Om9AFjMd93JGhDv5hVp65+YGxiA1o= -github.com/tailscale/wireguard-go v0.0.0-20210108235412-4a0869cbdb90/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8= github.com/tailscale/wireguard-go v0.0.0-20210109012254-dc30a1b9415e h1:ZXbXfVJOhSq4/Gt7TnqwXBPCctzYXkWXo3oQS7LZ40I= github.com/tailscale/wireguard-go v0.0.0-20210109012254-dc30a1b9415e/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8= -github.com/tailscale/wireguard-go v0.0.20201119-0.20201228205120-066446d1733a h1:RUJeuZlAm1DT6Mhk9UTsaHrDeDZhPrbKfNsaEtKF6+0= -github.com/tailscale/wireguard-go v0.0.20201119-0.20201228205120-066446d1733a/go.mod h1:UIAx57STfAZOrNVj8QGP2zG3ovWPMTD4DDubFHqMlYI= github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netns v0.0.0-20200520041808-52d707b772fe/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go4.org/intern v0.0.0-20201223054237-ef8cbcb8edd7 h1:yeDrXaQ3VRXbTN7lHj70DxW4LdPow83MVwPPRjpP70U= go4.org/intern v0.0.0-20201223054237-ef8cbcb8edd7/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= go4.org/intern v0.0.0-20201223061701-969c7e87e7cb h1:yuqO0E4bHRsTPUocDpRKXfLE40lwWplVxENQ2WOV7Gc= go4.org/intern v0.0.0-20201223061701-969c7e87e7cb/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= -go4.org/mem v0.0.0-20200706164138-185c595c3ecc/go.mod h1:NEYvpHWemiG/E5UWfaN5QAIGZeT1sa0Z2UNk6oeMb/k= +go4.org/intern v0.0.0-20210101010959-7cab76ca296a h1:28p852HIWWaOS019DYK/A3yTmpm1HJaUce63pvll4C8= +go4.org/intern v0.0.0-20210101010959-7cab76ca296a/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 h1:vSug/WNOi2+4jrKdivxayTN/zd8EA1UrStjpWvvo1jk= go4.org/mem v0.0.0-20201119185036-c04c5a6ff174/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e h1:ExUmGi0ZsQmiVo9giDQqXkr7vreeXPMkOGIusfsfbzI= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -353,9 +325,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc= @@ -373,7 +343,6 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -403,9 +372,6 @@ golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -445,7 +411,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -460,9 +425,6 @@ golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -505,16 +467,11 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200707200213-416e8f4faf8a/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201001230009-b5b87423c93b/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201021000207-d49c4edd7d96/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 h1:1Bs6RVeBFtLZ8Yi1Hk07DiOqzvwLD/4hln4iahvFlag= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -523,10 +480,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.zx2c4.com/wireguard v0.0.20200321-0.20200715051853-507f148e1c42/go.mod h1:GJvYs5O24/ASlwPiRklVnjMx2xQzrOic0DuU6GvYJL4= golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9 h1:qowcZ56hhpeoESmWzI4Exhx4Y78TpCyXUJur4/c0CoE= golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9/go.mod h1:LMeNfjlcPZTrBC1juwgbQyA4Zy2XVcsrdO/fIJxwyuA= -golang.zx2c4.com/wireguard/windows v0.1.2-0.20201004085714-dd60d0447f81/go.mod h1:GaK5zcgr5XE98WaRzIDilumDBp5/yP8j2kG/LCDnvAM= golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8 h1:nlXPqGA98n+qcq1pwZ28KjM5EsFQvamKS00A+VUeVjs= golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8/go.mod h1:psva4yDnAHLuh7lUzOK7J7bLYxNFfo0iKWz+mi9gzkA= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -538,6 +493,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -563,14 +519,14 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b h1:jEdfCm+8YTWSYgU4L7Nq0jjU+q9RxIhi0cXLTY+Ih3A= google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -581,8 +537,6 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gvisor.dev/gvisor v0.0.0-20200903175658-a842a338ecd9 h1:w2M4YLwjhea3cX8qp7pOvMg97svEgPzpAcGZHb7BVwc= -gvisor.dev/gvisor v0.0.0-20200903175658-a842a338ecd9/go.mod h1:n17AP1iZxpRCzqyHLJdpa2e1SzY1rNZ9tt3/MePxlGs= gvisor.dev/gvisor v0.0.0-20210111185822-3ff3110fcdd6 h1:H5EvGkFG+pgAAbZMV8Me3Gy+HUYdaDcGXKWWixZ0EE8= gvisor.dev/gvisor v0.0.0-20210111185822-3ff3110fcdd6/go.mod h1:5DEMKRjYDiM24fvDUWPjBpABm9ROMcv/kEcox3fHtm0= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -590,21 +544,12 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.0 h1:AWNL1W1i7f0wNZ8VwOKNJ0sliKvOF/adn0EHenfUh+c= honnef.co/go/tools v0.1.0/go.mod h1:XtegFAyX/PfluP4921rXU5IkjkqBCDnUq4W8VCIoKvM= -inet.af/netaddr v0.0.0-20200810144936-56928fe48a98/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww= -inet.af/netaddr v0.0.0-20201218162718-658fec415e52/go.mod h1:qqYzz/2whtrbWJvt+DNWQyvekNN4ePQZcg2xc2/Yjww= -inet.af/netaddr v0.0.0-20201223185330-97d366981fac h1:aqMW8vft7VmOIhtQhsTWhAuZzOBGYBv+Otyvwj+VGSU= -inet.af/netaddr v0.0.0-20201223185330-97d366981fac/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o= -inet.af/netaddr v0.0.0-20201224214825-a55841caa437 h1:Li2QBwaT/hU3wE7GdyoqaX+TzIlI+V1zs/CuWrjX8e4= -inet.af/netaddr v0.0.0-20201224214825-a55841caa437/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o= -inet.af/netaddr v0.0.0-20201226233944-2d1876c01610 h1:9Nnw3NS9SL4SlFtBWSdv7onMbdY+B8nflRNZvhgxuMY= -inet.af/netaddr v0.0.0-20201226233944-2d1876c01610/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o= inet.af/netaddr v0.0.0-20201228234250-33d0a924ebbf h1:0eHZ8v6j5wIiOVyoYPd70ueZ/RPEQtRlzi60uneDbRU= inet.af/netaddr v0.0.0-20201228234250-33d0a924ebbf/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o= -inet.af/netaddr v0.0.0-20201231012616-c5dc91d2a016 h1:CEeeAJW60aRKE6gGJC5krs2xC/uM2l8SasvgeDXFN5Q= -inet.af/netaddr v0.0.0-20201231012616-c5dc91d2a016/go.mod h1:lbePDLSB5c45kkUmF7ETNE5X9z/yuQvWJIv1hhb5rFI= +inet.af/netaddr v0.0.0-20210105212526-648fbc18a69d h1:6f0242aW/6x2enQBOSKgDS8KQNw6Tp7IVR8eG3x0Jc8= +inet.af/netaddr v0.0.0-20210105212526-648fbc18a69d/go.mod h1:jPZo7Jy4nke2cCgISa4fKJKa5T7+EO8k5fWwWghzneg= k8s.io/api v0.16.13/go.mod h1:QWu8UWSTiuQZMMeYjwLs6ILu5O74qKSJ0c+4vrchDxs= k8s.io/apimachinery v0.16.13/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ= k8s.io/apimachinery v0.16.14-rc.0/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ= @@ -620,4 +565,3 @@ rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w= rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -tailscale.com v1.2.10/go.mod h1:JEJiCce3MHtPCTdX2ahLc4tcnxZ7b5etish1Yt0B6+w= diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 5523c2bfb..c31914a85 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -27,7 +27,6 @@ import ( "syscall" "time" - "github.com/golang/groupcache/lru" "github.com/tailscale/wireguard-go/conn" "github.com/tailscale/wireguard-go/wgcfg" "go4.org/mem" @@ -1427,7 +1426,7 @@ func (c *Conn) awaitUDP4(b []byte) { return } addr := pAddr.(*net.UDPAddr) - ipp, ok := c.pconn4.ippCache.IPPort(addr) + ipp, ok := netaddr.FromStdAddr(addr.IP, addr.Port, addr.Zone) if !ok { continue } @@ -1615,7 +1614,7 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, *net.UDPAddr, error) { return 0, nil, nil, err } addr := pAddr.(*net.UDPAddr) - ipp, ok := c.pconn6.ippCache.IPPort(addr) + ipp, ok := netaddr.FromStdAddr(addr.IP, addr.Port, addr.Zone) if !ok { continue } @@ -2556,10 +2555,6 @@ func (c *Conn) CreateEndpoint(pubKey [32]byte, addrs string) (conn.Endpoint, err // RebindingUDPConn is a UDP socket that can be re-bound. // Unix has no notion of re-binding a socket, so we swap it out for a new one. type RebindingUDPConn struct { - // ippCache is a cache from UDPAddr => netaddr.IPPort. It's not safe for concurrent use. - // This is used by ReceiveIPv6 and awaitUDP4 (called from ReceiveIPv4). - ippCache ippCache - mu sync.Mutex pconn net.PacketConn } @@ -3455,46 +3450,6 @@ func (de *discoEndpoint) stopAndReset() { de.pendingCLIPings = nil } -// ippCache is a cache of *net.UDPAddr => netaddr.IPPort mappings. -// -// It's not safe for concurrent use. -type ippCache struct { - c *lru.Cache -} - -// IPPort is a caching wrapper around netaddr.FromStdAddr. -// -// It is not safe for concurrent use. -func (ic *ippCache) IPPort(u *net.UDPAddr) (netaddr.IPPort, bool) { - if u == nil || len(u.IP) > 16 { - return netaddr.IPPort{}, false - } - if ic.c == nil { - ic.c = lru.New(64) // arbitrary - } - - key := ippCacheKey{ipLen: uint8(len(u.IP)), port: uint16(u.Port), zone: u.Zone} - copy(key.ip[:], u.IP[:]) - - if v, ok := ic.c.Get(key); ok { - return v.(netaddr.IPPort), true - } - ipp, ok := netaddr.FromStdAddr(u.IP, u.Port, u.Zone) - if ok { - ic.c.Add(key, ipp) - } - return ipp, ok -} - -// ippCacheKey is the cache key type used by ippCache.IPPort. -// It must be comparable, being used as a map key in the lru package. -type ippCacheKey struct { - ip [16]byte - port uint16 - ipLen uint8 // bytes in ip that are valid; rest are zero - zone string -} - // derpStr replaces DERP IPs in s with "derp-". func derpStr(s string) string { return strings.ReplaceAll(s, "127.3.3.40:", "derp-") } From 024671406b9d860d29dce93836c73ec13592761d Mon Sep 17 00:00:00 2001 From: Sonia Appasamy Date: Mon, 11 Jan 2021 17:24:32 -0500 Subject: [PATCH 04/34] ipn: only send services in Hostinfo if Tailnet has opted-in to services collection (#1107) Signed-off-by: Sonia Appasamy --- control/controlclient/direct.go | 33 +++++++++++++++++---------------- control/controlclient/netmap.go | 6 ++++++ ipn/local.go | 18 +++++++++--------- tailcfg/tailcfg.go | 4 ++++ 4 files changed, 36 insertions(+), 25 deletions(-) diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 612ccbfc6..d0d65080a 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -751,22 +751,23 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw c.mu.Unlock() nm := &NetworkMap{ - NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()), - PrivateKey: persist.PrivateNodeKey, - MachineKey: machinePubKey, - Expiry: resp.Node.KeyExpiry, - Name: resp.Node.Name, - Addresses: resp.Node.Addresses, - Peers: resp.Peers, - LocalPort: localPort, - User: resp.Node.User, - UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile), - Domain: resp.Domain, - DNS: resp.DNSConfig, - Hostinfo: resp.Node.Hostinfo, - PacketFilter: lastParsedPacketFilter, - DERPMap: lastDERPMap, - Debug: resp.Debug, + NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()), + PrivateKey: persist.PrivateNodeKey, + MachineKey: machinePubKey, + Expiry: resp.Node.KeyExpiry, + Name: resp.Node.Name, + Addresses: resp.Node.Addresses, + Peers: resp.Peers, + LocalPort: localPort, + User: resp.Node.User, + UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile), + Domain: resp.Domain, + DNS: resp.DNSConfig, + Hostinfo: resp.Node.Hostinfo, + PacketFilter: lastParsedPacketFilter, + CollectServices: resp.CollectServices, + DERPMap: lastDERPMap, + Debug: resp.Debug, } addUserProfile := func(userID tailcfg.UserID) { if _, dup := nm.UserProfiles[userID]; dup { diff --git a/control/controlclient/netmap.go b/control/controlclient/netmap.go index ab4caa83c..a79a5c780 100644 --- a/control/controlclient/netmap.go +++ b/control/controlclient/netmap.go @@ -39,6 +39,12 @@ type NetworkMap struct { Hostinfo tailcfg.Hostinfo PacketFilter []filter.Match + // CollectServices reports whether this node's Tailnet has + // requested that info about services be included in HostInfo. + // If set, Hostinfo.ShieldsUp blocks services collection; that + // takes precedence over this field. + CollectServices bool + // DERPMap is the last DERP server map received. It's reused // between updates and should not be modified. DERPMap *tailcfg.DERPMap diff --git a/ipn/local.go b/ipn/local.go index b3abdf9d1..ba2733b16 100644 --- a/ipn/local.go +++ b/ipn/local.go @@ -1015,16 +1015,18 @@ func (b *LocalBackend) parseWgStatusLocked(s *wgengine.Status) (ret EngineStatus return ret } -// shieldsAreUp returns whether user preferences currently request -// "shields up" mode, which disallows all inbound connections. -func (b *LocalBackend) shieldsAreUp() bool { +// shouldUploadServices reports whether this node should include services +// in Hostinfo. When the user preferences currently request "shields up" +// mode, all inbound connections are refused, so services are not reported. +// Otherwise, shouldUploadServices respects NetMap.CollectServices. +func (b *LocalBackend) shouldUploadServices() bool { b.mu.Lock() defer b.mu.Unlock() - if b.prefs == nil { - return true // default to safest setting + if b.prefs == nil || b.netMap == nil { + return false // default to safest setting } - return b.prefs.ShieldsUp + return !b.prefs.ShieldsUp && b.netMap.CollectServices } func (b *LocalBackend) SetCurrentUserID(uid string) { @@ -1124,9 +1126,7 @@ func (b *LocalBackend) SetPrefs(newp *Prefs) { // painstakingly constructing it in twelvety other places. func (b *LocalBackend) doSetHostinfoFilterServices(hi *tailcfg.Hostinfo) { hi2 := *hi - if b.shieldsAreUp() { - // No local services are available, since ShieldsUp will block - // them all. + if !b.shouldUploadServices() { hi2.Services = []tailcfg.Service{} } diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 86ab028ef..df1cfcd60 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -665,6 +665,10 @@ type MapResponse struct { // forms are coming later. Domain string + // CollectServices reports whether this node's Tailnet has + // requested that info about services be included in HostInfo. + CollectServices bool `json:",omitempty"` + // PacketFilter are the firewall rules. // // For MapRequest.Version >= 6, a nil value means the most From 676b5b79469bf247be2deb632d3de7ed87ebf012 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 11 Jan 2021 11:38:49 -0800 Subject: [PATCH 05/34] net/netcheck: improve the preferred DERP hysteresis Users in Amsterdam (as one example) were flipping back and forth between equidistant London & Frankfurt relays too much. Signed-off-by: Brad Fitzpatrick --- net/netcheck/netcheck.go | 30 ++++++++++++++++++++++++------ net/netcheck/netcheck_test.go | 18 ++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/net/netcheck/netcheck.go b/net/netcheck/netcheck.go index 27d2cedc1..1b0194830 100644 --- a/net/netcheck/netcheck.go +++ b/net/netcheck/netcheck.go @@ -1102,6 +1102,10 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(r *Report) { c.mu.Lock() defer c.mu.Unlock() + var prevDERP int + if c.last != nil { + prevDERP = c.last.PreferredDERP + } if c.prev == nil { c.prev = map[time.Time]*Report{} } @@ -1119,9 +1123,9 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(r *Report) { delete(c.prev, t) continue } - for hp, d := range pr.RegionLatency { - if bd, ok := bestRecent[hp]; !ok || d < bd { - bestRecent[hp] = d + for regionID, d := range pr.RegionLatency { + if bd, ok := bestRecent[regionID]; !ok || d < bd { + bestRecent[regionID] = d } } } @@ -1129,13 +1133,27 @@ func (c *Client) addReportHistoryAndSetPreferredDERP(r *Report) { // Then, pick which currently-alive DERP server from the // current report has the best latency over the past maxAge. var bestAny time.Duration - for hp := range r.RegionLatency { - best := bestRecent[hp] + var oldRegionCurLatency time.Duration + for regionID, d := range r.RegionLatency { + if regionID == prevDERP { + oldRegionCurLatency = d + } + best := bestRecent[regionID] if r.PreferredDERP == 0 || best < bestAny { bestAny = best - r.PreferredDERP = hp + r.PreferredDERP = regionID } } + + // If we're changing our preferred DERP but the old one's still + // accessible and the new one's not much better, just stick with + // where we are. + if prevDERP != 0 && + r.PreferredDERP != prevDERP && + oldRegionCurLatency != 0 && + bestAny > oldRegionCurLatency/3*2 { + r.PreferredDERP = prevDERP + } } func updateLatency(m map[int]time.Duration, regionID int, d time.Duration) { diff --git a/net/netcheck/netcheck_test.go b/net/netcheck/netcheck_test.go index da229ee4c..94a5b369b 100644 --- a/net/netcheck/netcheck_test.go +++ b/net/netcheck/netcheck_test.go @@ -195,6 +195,24 @@ func TestAddReportHistoryAndSetPreferredDERP(t *testing.T) { wantPrevLen: 1, // t=[0123]s all gone. (too old, older than 10 min) wantDERP: 3, // only option }, + { + name: "preferred_derp_hysteresis_no_switch", + steps: []step{ + {0 * time.Second, report("d1", 4, "d2", 5)}, + {1 * time.Second, report("d1", 4, "d2", 3)}, + }, + wantPrevLen: 2, + wantDERP: 1, // 2 didn't get fast enough + }, + { + name: "preferred_derp_hysteresis_do_switch", + steps: []step{ + {0 * time.Second, report("d1", 4, "d2", 5)}, + {1 * time.Second, report("d1", 4, "d2", 1)}, + }, + wantPrevLen: 2, + wantDERP: 2, // 2 got fast enough + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 6b08303b0f29a7263892800c0ab6038463e64d7e Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 11 Jan 2021 19:23:43 -0800 Subject: [PATCH 06/34] Dockerfile: add big warning banner Updates #504 --- Dockerfile | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Dockerfile b/Dockerfile index 18925075c..140889f52 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,23 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. +############################################################################ +# +# WARNING: Tailscale is not yet officially supported in Docker, +# Kubernetes, etc. +# +# It might work, but we don't regularly test it, and it's not as polished as +# our currently supported platforms. This is provided for people who know +# how Tailscale works and what they're doing. +# +# Our tracking bug for officially support container use cases is: +# https://github.com/tailscale/tailscale/issues/504 +# +# Also, see the various bugs tagged "containers": +# https://github.com/tailscale/tailscale/labels/containers +# +############################################################################ + # This Dockerfile includes all the tailscale binaries. # # To build the Dockerfile: From d6e9fb1df0fd67d08065c2277e7c4f4a82b7930f Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 11 Jan 2021 19:16:14 -0800 Subject: [PATCH 07/34] all: adjust Unix permissions for those without umasks Fixes tailscale/corp#1165 Signed-off-by: Brad Fitzpatrick --- cmd/cloner/cloner.go | 2 +- cmd/derper/derper.go | 2 +- ipn/prefs.go | 2 +- logtail/filch/filch.go | 4 ++-- safesocket/unixsocket.go | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/cloner/cloner.go b/cmd/cloner/cloner.go index 73c9d2412..c5be5279e 100644 --- a/cmd/cloner/cloner.go +++ b/cmd/cloner/cloner.go @@ -140,7 +140,7 @@ func main() { flag.Usage() os.Exit(2) } - if err := ioutil.WriteFile(output, out, 0666); err != nil { + if err := ioutil.WriteFile(output, out, 0644); err != nil { log.Fatal(err) } } diff --git a/cmd/derper/derper.go b/cmd/derper/derper.go index 43c0e84da..fbef25d01 100644 --- a/cmd/derper/derper.go +++ b/cmd/derper/derper.go @@ -97,7 +97,7 @@ func writeNewConfig() config { if err != nil { log.Fatal(err) } - if err := atomicfile.WriteFile(*configPath, b, 0666); err != nil { + if err := atomicfile.WriteFile(*configPath, b, 0600); err != nil { log.Fatal(err) } return cfg diff --git a/ipn/prefs.go b/ipn/prefs.go index 970d08a9a..f8256454b 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -296,7 +296,7 @@ func SavePrefs(filename string, p *Prefs) { log.Printf("Saving prefs %v %v\n", filename, p.Pretty()) data := p.ToBytes() os.MkdirAll(filepath.Dir(filename), 0700) - if err := atomicfile.WriteFile(filename, data, 0666); err != nil { + if err := atomicfile.WriteFile(filename, data, 0600); err != nil { log.Printf("SavePrefs: %v\n", err) } } diff --git a/logtail/filch/filch.go b/logtail/filch/filch.go index 86bc45f00..07d9b6203 100644 --- a/logtail/filch/filch.go +++ b/logtail/filch/filch.go @@ -131,11 +131,11 @@ func New(filePrefix string, opts Options) (f *Filch, err error) { path1 := filePrefix + ".log1.txt" path2 := filePrefix + ".log2.txt" - f1, err = os.OpenFile(path1, os.O_CREATE|os.O_RDWR, 0666) + f1, err = os.OpenFile(path1, os.O_CREATE|os.O_RDWR, 0600) if err != nil { return nil, err } - f2, err = os.OpenFile(path2, os.O_CREATE|os.O_RDWR, 0666) + f2, err = os.OpenFile(path2, os.O_CREATE|os.O_RDWR, 0600) if err != nil { return nil, err } diff --git a/safesocket/unixsocket.go b/safesocket/unixsocket.go index b2e2c3399..ad96ac7be 100644 --- a/safesocket/unixsocket.go +++ b/safesocket/unixsocket.go @@ -64,7 +64,7 @@ func listen(path string, port uint16) (ln net.Listener, _ uint16, err error) { if err != nil { return nil, 0, err } - os.Chmod(path, 0666) + os.Chmod(path, 0600) return pipe, 0, err } From ad3fb6125d57aeb59a18c0eaf1f25d18f5010cce Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 11 Jan 2021 20:17:43 -0800 Subject: [PATCH 08/34] net/flowtrack: add Tuple.String method Signed-off-by: Brad Fitzpatrick --- net/flowtrack/flowtrack.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/net/flowtrack/flowtrack.go b/net/flowtrack/flowtrack.go index 8d490d854..5fcd4ab20 100644 --- a/net/flowtrack/flowtrack.go +++ b/net/flowtrack/flowtrack.go @@ -12,6 +12,7 @@ package flowtrack import ( "container/list" + "fmt" "inet.af/netaddr" ) @@ -22,6 +23,10 @@ type Tuple struct { Dst netaddr.IPPort } +func (t Tuple) String() string { + return fmt.Sprintf("(%v => %v)", t.Src, t.Dst) +} + // Cache is an LRU cache keyed by Tuple. // // The zero value is valid to use. From 5eeaea9ef99c5749bbe66c07741b45c7599cbcb5 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 11 Jan 2021 20:17:13 -0800 Subject: [PATCH 09/34] net/packet: add TCPFlag type and some more constants Signed-off-by: Brad Fitzpatrick --- net/packet/packet.go | 17 +++++++++++------ wgengine/filter/filter_test.go | 4 ++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/net/packet/packet.go b/net/packet/packet.go index 5502d1959..29dfed514 100644 --- a/net/packet/packet.go +++ b/net/packet/packet.go @@ -17,10 +17,15 @@ import ( // RFC1858: prevent overlapping fragment attacks. const minFrag = 60 + 20 // max IPv4 header + basic TCP header +type TCPFlag uint8 + const ( - TCPSyn = 0x02 - TCPAck = 0x10 - TCPSynAck = TCPSyn | TCPAck + TCPFin TCPFlag = 0x01 + TCPSyn TCPFlag = 0x02 + TCPRst TCPFlag = 0x04 + TCPPsh TCPFlag = 0x08 + TCPAck TCPFlag = 0x10 + TCPSynAck TCPFlag = TCPSyn | TCPAck ) // Parsed is a minimal decoding of a packet suitable for use in filters. @@ -46,7 +51,7 @@ type Parsed struct { // DstIP4 is the destination address. Family matches IPVersion. Dst netaddr.IPPort // TCPFlags is the packet's TCP flag bigs. Valid iff IPProto == TCP. - TCPFlags uint8 + TCPFlags TCPFlag } func (p *Parsed) String() string { @@ -186,7 +191,7 @@ func (q *Parsed) decode4(b []byte) { } q.Src.Port = binary.BigEndian.Uint16(sub[0:2]) q.Dst.Port = binary.BigEndian.Uint16(sub[2:4]) - q.TCPFlags = sub[13] & 0x3F + q.TCPFlags = TCPFlag(sub[13]) & 0x3F headerLength := (sub[12] & 0xF0) >> 2 q.dataofs = q.subofs + int(headerLength) return @@ -274,7 +279,7 @@ func (q *Parsed) decode6(b []byte) { } q.Src.Port = binary.BigEndian.Uint16(sub[0:2]) q.Dst.Port = binary.BigEndian.Uint16(sub[2:4]) - q.TCPFlags = sub[13] & 0x3F + q.TCPFlags = TCPFlag(sub[13]) & 0x3F headerLength := (sub[12] & 0xF0) >> 2 q.dataofs = q.subofs + int(headerLength) return diff --git a/wgengine/filter/filter_test.go b/wgengine/filter/filter_test.go index 5d0e909a5..fd9a3facf 100644 --- a/wgengine/filter/filter_test.go +++ b/wgengine/filter/filter_test.go @@ -414,7 +414,7 @@ func raw6(proto packet.IPProto, src, dst string, sport, dport uint16, trimLen in payload := make([]byte, 12) // Set the right bit to look like a TCP SYN, if the packet ends up interpreted as TCP - payload[5] = packet.TCPSyn + payload[5] = byte(packet.TCPSyn) b := packet.Generate(&u, payload) // payload large enough to possibly be TCP @@ -443,7 +443,7 @@ func raw4(proto packet.IPProto, src, dst string, sport, dport uint16, trimLength payload := make([]byte, 12) // Set the right bit to look like a TCP SYN, if the packet ends up interpreted as TCP - payload[5] = packet.TCPSyn + payload[5] = byte(packet.TCPSyn) b := packet.Generate(&u, payload) // payload large enough to possibly be TCP From 85e54af0d704f27eb6ccb178f9d90f320fc61fba Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 11 Jan 2021 19:07:08 -0800 Subject: [PATCH 10/34] wgengine: on TCP connect fail/timeout, log some clues about why it failed So users can see why things aren't working. A start. More diagnostics coming. Updates #1094 --- cmd/tailscale/depaware.txt | 2 +- cmd/tailscaled/depaware.txt | 2 +- wgengine/magicsock/magicsock.go | 36 +++++++- wgengine/pendopen.go | 157 ++++++++++++++++++++++++++++++++ wgengine/userspace.go | 13 +++ 5 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 wgengine/pendopen.go diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 62b8ad4aa..e8e189677 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -51,7 +51,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/logtail/backoff from tailscale.com/control/controlclient+ tailscale.com/metrics from tailscale.com/derp tailscale.com/net/dnscache from tailscale.com/control/controlclient+ - tailscale.com/net/flowtrack from tailscale.com/wgengine/filter + tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+ 💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscale/cli+ tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli+ tailscale.com/net/netns from tailscale.com/control/controlclient+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 9798ad6eb..811052f7d 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -86,7 +86,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/logtail/filch from tailscale.com/logpolicy tailscale.com/metrics from tailscale.com/derp tailscale.com/net/dnscache from tailscale.com/control/controlclient+ - tailscale.com/net/flowtrack from tailscale.com/wgengine/filter + tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+ 💣 tailscale.com/net/interfaces from tailscale.com/ipn+ tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock tailscale.com/net/netns from tailscale.com/control/controlclient+ diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index c31914a85..d80307e5f 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -710,14 +710,46 @@ func peerForIP(nm *controlclient.NetworkMap, ip netaddr.IP) (n *tailcfg.Node, ok } } } + + // TODO(bradfitz): this is O(n peers). Add ART to netaddr? + var best netaddr.IPPrefix for _, p := range nm.Peers { for _, cidr := range p.AllowedIPs { if cidr.Contains(ip) { - return p, true + if best.IsZero() || cidr.Bits > best.Bits { + n = p + best = cidr + } } } } - return nil, false + return n, n != nil +} + +// PeerForIP returns the node that ip should route to. +func (c *Conn) PeerForIP(ip netaddr.IP) (n *tailcfg.Node, ok bool) { + c.mu.Lock() + defer c.mu.Unlock() + if c.netMap == nil { + return + } + return peerForIP(c.netMap, ip) +} + +// LastRecvActivityOfDisco returns the time we last got traffic from +// this endpoint (updated every ~10 seconds). +func (c *Conn) LastRecvActivityOfDisco(dk tailcfg.DiscoKey) time.Time { + c.mu.Lock() + defer c.mu.Unlock() + de, ok := c.endpointOfDisco[dk] + if !ok { + return time.Time{} + } + unix := atomic.LoadInt64(&de.lastRecvUnixAtomic) + if unix == 0 { + return time.Time{} + } + return time.Unix(unix, 0) } // Ping handles a "tailscale ping" CLI query. diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go new file mode 100644 index 000000000..dc1a21fec --- /dev/null +++ b/wgengine/pendopen.go @@ -0,0 +1,157 @@ +// Copyright (c) 2021 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 wgengine + +import ( + "os" + "strconv" + "time" + + "tailscale.com/net/flowtrack" + "tailscale.com/net/packet" + "tailscale.com/wgengine/filter" + "tailscale.com/wgengine/tstun" +) + +const tcpTimeoutBeforeDebug = 5 * time.Second + +// debugConnectFailures reports whether the local node should track +// outgoing TCP connections and log which ones fail and why. +func debugConnectFailures() bool { + s := os.Getenv("TS_DEBUG_CONNECT_FAILURES") + if s == "" { + return true + } + v, _ := strconv.ParseBool(s) + return v +} + +type pendingOpenFlow struct { + timer *time.Timer // until giving up on the flow +} + +func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN) (res filter.Response) { + res = filter.Accept // always + + if pp.IPVersion == 0 || + pp.IPProto != packet.TCP || + pp.TCPFlags&(packet.TCPSyn|packet.TCPRst) == 0 { + return + } + + flow := flowtrack.Tuple{Dst: pp.Src, Src: pp.Dst} // src/dst reversed + + e.mu.Lock() + defer e.mu.Unlock() + of, ok := e.pendOpen[flow] + if !ok { + // Not a tracked flow. + return + } + of.timer.Stop() + delete(e.pendOpen, flow) + + if pp.TCPFlags&packet.TCPRst != 0 { + // TODO(bradfitz): have peer send a IP proto 99 "why" + // packet first with details and log that instead + // (e.g. ACL prohibited, shields up, etc). + e.logf("open-conn-track: flow %v got RST by peer", flow) + return + } + + return +} + +func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.TUN) (res filter.Response) { + res = filter.Accept // always + + if pp.IPVersion == 0 || + pp.IPProto != packet.TCP || + pp.TCPFlags&packet.TCPSyn == 0 { + return + } + + flow := flowtrack.Tuple{Src: pp.Src, Dst: pp.Dst} + timer := time.AfterFunc(tcpTimeoutBeforeDebug, func() { + e.onOpenTimeout(flow) + }) + + e.mu.Lock() + defer e.mu.Unlock() + if e.pendOpen == nil { + e.pendOpen = make(map[flowtrack.Tuple]*pendingOpenFlow) + } + if _, dup := e.pendOpen[flow]; dup { + // Duplicates are expected when the OS retransmits. Ignore. + return + } + e.pendOpen[flow] = &pendingOpenFlow{timer: timer} + + return filter.Accept +} + +func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) { + e.mu.Lock() + if _, ok := e.pendOpen[flow]; !ok { + // Not a tracked flow, or already handled & deleted. + e.mu.Unlock() + return + } + delete(e.pendOpen, flow) + e.mu.Unlock() + + // Diagnose why it might've timed out. + n, ok := e.magicConn.PeerForIP(flow.Dst.IP) + if !ok { + e.logf("open-conn-track: timeout opening %v; no associated peer node", flow) + return + } + if n.DiscoKey.IsZero() { + e.logf("open-conn-track: timeout opening %v; peer node %v running pre-0.100", flow, n.Key.ShortString()) + return + } + if n.DERP == "" { + e.logf("open-conn-track: timeout opening %v; peer node %v not connected to any DERP relay", flow, n.Key.ShortString()) + return + } + var lastSeen time.Time + if n.LastSeen != nil { + lastSeen = *n.LastSeen + } + + var ps *PeerStatus + if st, err := e.getStatus(); err == nil { + for _, v := range st.Peers { + if v.NodeKey == n.Key { + v := v // copy + ps = &v + } + } + } else { + e.logf("open-conn-track: timeout opening %v to node %v; failed to get engine status: %v", flow, n.Key.ShortString(), err) + return + } + if ps == nil { + e.logf("open-conn-track: timeout opening %v; target node %v in netmap but unknown to wireguard", flow, n.Key.ShortString()) + return + } + + // TODO(bradfitz): figure out what PeerStatus.LastHandshake + // is. It appears to be the last time we sent a handshake, + // which isn't what I expected. I thought it was when a + // handshake completed, which is what I want. + _ = ps.LastHandshake + + e.logf("open-conn-track: timeout opening %v to node %v; lastSeen=%v, lastRecv=%v", + flow, n.Key.ShortString(), + agoOrNever(lastSeen), agoOrNever(e.magicConn.LastRecvActivityOfDisco(n.DiscoKey))) +} + +func agoOrNever(t time.Time) string { + if t.IsZero() { + return "never" + } + return time.Since(t).Round(time.Second).String() +} diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 6b7d23526..32ef52d47 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -30,6 +30,7 @@ import ( "tailscale.com/control/controlclient" "tailscale.com/internal/deepprint" "tailscale.com/ipn/ipnstate" + "tailscale.com/net/flowtrack" "tailscale.com/net/interfaces" "tailscale.com/net/packet" "tailscale.com/net/tsaddr" @@ -118,6 +119,7 @@ type userspaceEngine struct { endpoints []string pingers map[wgkey.Key]*pinger // legacy pingers for pre-discovery peers linkState *interfaces.State + pendOpen map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go // Lock ordering: magicsock.Conn.mu, wgLock, then mu. } @@ -265,6 +267,17 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) { } e.tundev.PreFilterOut = e.handleLocalPackets + if debugConnectFailures() { + if e.tundev.PreFilterIn != nil { + return nil, errors.New("unexpected PreFilterIn already set") + } + e.tundev.PreFilterIn = e.trackOpenPreFilterIn + if e.tundev.PostFilterOut != nil { + return nil, errors.New("unexpected PostFilterOut already set") + } + e.tundev.PostFilterOut = e.trackOpenPostFilterOut + } + // wireguard-go logs as it starts and stops routines. // Silence those; there are a lot of them, and they're just noise. allowLogf := func(s string) bool { From 0aa55bffce3ae1dd4eb695bf0aada34438a7e143 Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Sun, 10 Jan 2021 06:50:35 -0800 Subject: [PATCH 11/34] magicsock: test error case in derpWriteChanOfAddr In derpWriteChanOfAddr when we call derphttp.NewRegionClient(), there is a check of whether the connection is already errored and if so it returns before grabbing the lock. The lock might already be held and would be a deadlock. This corner case is not being reliably exercised by other tests. This shows up in code coverage reports, the lines of code in derpWriteChanOfAddr are alternately added and subtracted from code coverage. Add a test to specifically exercise this code path, and verify that it doesn't deadlock. This is the best tradeoff I could come up with: + the moment code calls Err() to check if there is an error, we grab the lock to make sure it would deadlock if it tries to grab the lock itself. + if a new call to Err() is added in this code path, only the first one will be covered and the rest will not be tested. + this test doesn't verify whether code is checking for Err() in the right place, which ideally I guess it would. Signed-off-by: Denton Gentry --- wgengine/magicsock/magicsock.go | 2 +- wgengine/magicsock/magicsock_test.go | 83 ++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index d80307e5f..7b451a0b7 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -1170,7 +1170,7 @@ func (c *Conn) derpWriteChanOfAddr(addr netaddr.IPPort, peer key.Public) chan<- return nil } - // Note that derphttp.NewClient does not dial the server + // Note that derphttp.NewRegionClient does not dial the server // so it is safe to do under the mu lock. dc := derphttp.NewRegionClient(c.privateKey, c.logf, func() *tailcfg.DERPRegion { if c.connCtx.Err() != nil { diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 7aac0572a..1609de008 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -11,6 +11,7 @@ import ( "crypto/tls" "encoding/binary" "encoding/json" + "errors" "fmt" "io/ioutil" "net" @@ -542,6 +543,88 @@ func TestDeviceStartStop(t *testing.T) { dev.Close() } +type testConnClosingContext struct { + parent context.Context + mu *sync.Mutex +} + +func (c *testConnClosingContext) Deadline() (deadline time.Time, ok bool) { + d, o := c.parent.Deadline() + return d, o +} +func (c *testConnClosingContext) Done() <-chan struct{} { + return c.parent.Done() +} +func (c *testConnClosingContext) Err() error { + // Deliberately deadlock if anything grabs the lock after checking Err() + c.mu.Lock() + return errors.New("testConnClosingContext error") +} +func (c *testConnClosingContext) Value(key interface{}) interface{} { + return c.parent.Value(key) +} +func (*testConnClosingContext) String() string { + return "testConnClosingContext" +} + +func TestConnClosing(t *testing.T) { + privateKey, err := wgkey.NewPrivate() + if err != nil { + t.Fatalf("generating private key: %v", err) + } + + epCh := make(chan []string, 100) + conn, err := NewConn(Options{ + Logf: t.Logf, + PacketListener: nettype.Std{}, + EndpointsFunc: func(eps []string) { + epCh <- eps + }, + SimulatedNetwork: false, + }) + if err != nil { + t.Fatalf("constructing magicsock: %v", err) + } + + derpMap, cleanup := runDERPAndStun(t, t.Logf, nettype.Std{}, netaddr.IPv4(127, 0, 3, 1)) + defer cleanup() + + // The point of this test case is to exercise handling in derpWriteChanOfAddr() which + // returns early if connCtx.Err() returns non-nil, to avoid a deadlock on conn.mu. + // We swap in a context which always returns an error, and deliberately grabs the lock + // to cause a deadlock if magicsock.go tries to acquire the lock after calling Err(). + closingCtx := testConnClosingContext{parent: conn.connCtx, mu: &conn.mu} + conn.connCtx = &closingCtx + conn.Start() + + conn.SetDERPMap(derpMap) + if err := conn.SetPrivateKey(privateKey); err != nil { + t.Fatalf("setting private key in magicsock: %v", err) + } + + tun := tuntest.NewChannelTUN() + tsTun := tstun.WrapTUN(t.Logf, tun.TUN()) + tsTun.SetFilter(filter.NewAllowAllForTest(t.Logf)) + + dev := device.NewDevice(tsTun, &device.DeviceOptions{ + Logger: &device.Logger{ + Debug: logger.StdLogger(t.Logf), + Info: logger.StdLogger(t.Logf), + Error: logger.StdLogger(t.Logf), + }, + CreateEndpoint: conn.CreateEndpoint, + CreateBind: conn.CreateBind, + SkipBindUpdate: true, + }) + + dev.Up() + conn.WaitReady(t) + + // We don't assert any failures within the test itself. If derpWriteChanOfAddr tries to + // grab the lock it will deadlock, and conn.WaitReady(t) will call t.Fatal() after timeout. + // (verified by deliberately breaking derpWriteChanOfAddr) +} + func makeNestable(t *testing.T) (logf logger.Logf, setT func(t *testing.T)) { var mu sync.RWMutex cur := t From 07e4009e15948583d6fd3f5ecad0b68a8c838e0b Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Sun, 10 Jan 2021 11:52:11 -0800 Subject: [PATCH 12/34] portlist: fully exercise lessThan in tests All cases in lessThan are not reliably exercised by other tests. This shows up in code coverage metrics as lines in lessThan are alternately added and removed from coverage. Add a test case to systematically test all conditions. Signed-off-by: Denton Gentry --- portlist/portlist_test.go | 94 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/portlist/portlist_test.go b/portlist/portlist_test.go index 64613f68c..deda695c7 100644 --- a/portlist/portlist_test.go +++ b/portlist/portlist_test.go @@ -47,6 +47,100 @@ func TestIgnoreLocallyBoundPorts(t *testing.T) { } } +func TestLessThan(t *testing.T) { + tests := []struct { + name string + a, b Port + want bool + }{ + { + "Port a < b", + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"}, + Port{Proto: "tcp", Port: 101, Process: "proc1", inode: "inode1"}, + true, + }, + { + "Port a > b", + Port{Proto: "tcp", Port: 101, Process: "proc1", inode: "inode1"}, + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"}, + false, + }, + { + "Proto a < b", + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"}, + Port{Proto: "udp", Port: 100, Process: "proc1", inode: "inode1"}, + true, + }, + { + "Proto a < b", + Port{Proto: "udp", Port: 100, Process: "proc1", inode: "inode1"}, + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"}, + false, + }, + { + "inode a < b", + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"}, + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode2"}, + true, + }, + { + "inode a > b", + Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode2"}, + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"}, + false, + }, + { + "Process a < b", + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"}, + Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode1"}, + true, + }, + { + "Process a > b", + Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode1"}, + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"}, + false, + }, + { + "Port evaluated first", + Port{Proto: "udp", Port: 100, Process: "proc2", inode: "inode2"}, + Port{Proto: "tcp", Port: 101, Process: "proc1", inode: "inode1"}, + true, + }, + { + "Proto evaluated second", + Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode2"}, + Port{Proto: "udp", Port: 100, Process: "proc1", inode: "inode1"}, + true, + }, + { + "inode evaluated third", + Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode1"}, + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode2"}, + true, + }, + { + "Process evaluated fourth", + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"}, + Port{Proto: "tcp", Port: 100, Process: "proc2", inode: "inode1"}, + true, + }, + { + "equal", + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"}, + Port{Proto: "tcp", Port: 100, Process: "proc1", inode: "inode1"}, + false, + }, + } + + for _, tt := range tests { + got := tt.a.lessThan(&tt.b) + if got != tt.want { + t.Errorf("%s: Equal = %v; want %v", tt.name, got, tt.want) + } + } +} + func BenchmarkGetList(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { From 0aed59b691507ad6a4927f588c61c3b630589646 Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Sun, 10 Jan 2021 12:14:39 -0800 Subject: [PATCH 13/34] portlist: add a test for SameInodes Exercise all cases. Signed-off-by: Denton Gentry --- portlist/portlist_test.go | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/portlist/portlist_test.go b/portlist/portlist_test.go index deda695c7..7fde87e1b 100644 --- a/portlist/portlist_test.go +++ b/portlist/portlist_test.go @@ -141,6 +141,59 @@ func TestLessThan(t *testing.T) { } } +func TestSameInodes(t *testing.T) { + port1 := Port{Proto: "tcp", Port: 100, Process: "proc", inode: "inode1"} + port2 := Port{Proto: "tcp", Port: 100, Process: "proc", inode: "inode1"} + portProto := Port{Proto: "udp", Port: 100, Process: "proc", inode: "inode1"} + portPort := Port{Proto: "tcp", Port: 101, Process: "proc", inode: "inode1"} + portInode := Port{Proto: "tcp", Port: 100, Process: "proc", inode: "inode2"} + portProcess := Port{Proto: "tcp", Port: 100, Process: "other", inode: "inode1"} + + tests := []struct { + name string + a, b List + want bool + }{ + { + "identical", + List{port1, port1}, + List{port2, port2}, + true, + }, + { + "proto differs", + List{port1, port1}, + List{port2, portProto}, + false, + }, + { + "port differs", + List{port1, port1}, + List{port2, portPort}, + false, + }, + { + "inode differs", + List{port1, port1}, + List{port2, portInode}, + false, + }, + { + // SameInodes does not check the Process field + "Process differs", + List{port1, port1}, + List{port2, portProcess}, + true, + }, + } + for _, tt := range tests { + got := tt.a.SameInodes(tt.b) + if got != tt.want { + t.Errorf("%s: Equal = %v; want %v", tt.name, got, tt.want) + } + } +} + func BenchmarkGetList(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { From 2c328da094f06eaf1c946ca0156be40800037ca7 Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Sun, 10 Jan 2021 15:03:57 -0800 Subject: [PATCH 14/34] logtail: add a test to upload logs to local server Start an HTTP server to accept POST requests, and upload some logs to it. Check that uploaded logs were received. Code in logtail:drainPending was not being reliably exercised by other tests. This shows up in code coverage reports, as lines of code in drainPending are alternately added and subtracted from code coverage. This test will reliably exercise and verify this code. Signed-off-by: Denton Gentry --- logtail/logtail_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/logtail/logtail_test.go b/logtail/logtail_test.go index 5f435a8cd..5e3083cc3 100644 --- a/logtail/logtail_test.go +++ b/logtail/logtail_test.go @@ -6,6 +6,8 @@ package logtail import ( "context" + "net/http" + "net/http/httptest" "testing" "time" ) @@ -20,6 +22,27 @@ func TestFastShutdown(t *testing.T) { l.Shutdown(ctx) } +func TestUploadMessages(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + uploads := 0 + testServ := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + uploads += 1 + })) + + l := NewLogger(Config{BaseURL: testServ.URL}, t.Logf) + for i := 1; i < 10; i++ { + l.Write([]byte("log line")) + } + + l.Shutdown(ctx) + cancel() + if uploads == 0 { + t.Error("no log uploads") + } +} + var sink []byte func TestLoggerEncodeTextAllocs(t *testing.T) { From 2e9728023b49a10d6528b6cceff007339e775049 Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Sun, 10 Jan 2021 17:22:11 -0800 Subject: [PATCH 15/34] magicsock: test error case in sendDiscoMessage In sendDiscoMessage there is a check of whether the connection is closed, which is not being reliably exercised by other tests. This shows up in code coverage reports, the lines of code in sendDiscoMessage are alternately added and subtracted from code coverage. Add a test to specifically exercise and verify this code path. Signed-off-by: Denton Gentry --- wgengine/magicsock/magicsock_test.go | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 1609de008..794d01e7a 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -625,6 +625,58 @@ func TestConnClosing(t *testing.T) { // (verified by deliberately breaking derpWriteChanOfAddr) } +// Exercise a code path in sendDiscoMessage if the connection has been closed. +func TestConnClosed(t *testing.T) { + mstun := &natlab.Machine{Name: "stun"} + m1 := &natlab.Machine{Name: "m1"} + m2 := &natlab.Machine{Name: "m2"} + inet := natlab.NewInternet() + sif := mstun.Attach("eth0", inet) + m1if := m1.Attach("eth0", inet) + m2if := m2.Attach("eth0", inet) + + d := &devices{ + m1: m1, + m1IP: m1if.V4(), + m2: m2, + m2IP: m2if.V4(), + stun: mstun, + stunIP: sif.V4(), + } + + derpMap, cleanup := runDERPAndStun(t, t.Logf, d.stun, d.stunIP) + defer cleanup() + + ms1 := newMagicStack(t, logger.WithPrefix(t.Logf, "conn1: "), d.m1, derpMap) + defer ms1.Close() + ms2 := newMagicStack(t, logger.WithPrefix(t.Logf, "conn2: "), d.m2, derpMap) + defer ms2.Close() + + cleanup = meshStacks(t.Logf, []*magicStack{ms1, ms2}) + defer cleanup() + + pkt := tuntest.Ping(ms2.IP(t).IPAddr().IP, ms1.IP(t).IPAddr().IP) + + if len(ms1.conn.activeDerp) == 0 { + t.Errorf("unexpected DERP empty got: %v want: >0", len(ms1.conn.activeDerp)) + } + + ms1.conn.Close() + ms2.conn.Close() + + // This should hit a c.closed conditional in sendDiscoMessage() and return immediately. + ms1.tun.Outbound <- pkt + select { + case <-ms2.tun.Inbound: + t.Error("unexpected response with connection closed") + case <-time.After(100 * time.Millisecond): + } + + if len(ms1.conn.activeDerp) > 0 { + t.Errorf("unexpected DERP active got: %v want:0", len(ms1.conn.activeDerp)) + } +} + func makeNestable(t *testing.T) (logf logger.Logf, setT func(t *testing.T)) { var mu sync.RWMutex cur := t From b771a1363b90930619dcb85e6cf154566d286722 Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Mon, 11 Jan 2021 06:22:35 -0800 Subject: [PATCH 16/34] logtail: start a local server for TestFastShutdown Right now TestFastShutdown tries to upload logs to localhost:1234, which will most likely respond with an error. However if one has an actual service running on port 1234, it would receive a connection attempting to POST every time the unit test runs. Start a local server and direct the upload there instead. Signed-off-by: Denton Gentry --- logtail/logtail_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/logtail/logtail_test.go b/logtail/logtail_test.go index 5e3083cc3..576cc1888 100644 --- a/logtail/logtail_test.go +++ b/logtail/logtail_test.go @@ -16,8 +16,12 @@ func TestFastShutdown(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() + testServ := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) {})) + defer testServ.Close() + l := NewLogger(Config{ - BaseURL: "http://localhost:1234", + BaseURL: testServ.URL, }, t.Logf) l.Shutdown(ctx) } @@ -30,6 +34,7 @@ func TestUploadMessages(t *testing.T) { w.Header().Set("Content-Type", "application/json; charset=utf-8") uploads += 1 })) + defer testServ.Close() l := NewLogger(Config{BaseURL: testServ.URL}, t.Logf) for i := 1; i < 10; i++ { From 8349e10907dbee2cf708b2d8398f6c0ee79f3c06 Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Mon, 11 Jan 2021 13:34:51 -0800 Subject: [PATCH 17/34] magicsock: add description of testClosingContext Signed-off-by: Denton Gentry --- wgengine/magicsock/magicsock_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 794d01e7a..4aac0cdad 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -543,6 +543,11 @@ func TestDeviceStartStop(t *testing.T) { dev.Close() } +// A context used in TestConnClosing() which seeks to test that code which calls +// Err() to see if a connection is already being closed does not then proceed to +// try to acquire the mutex, as this would lead to deadlock. When Err() is called +// this context acquires the lock itself, in order to force a deadlock (and test +// failure on timeout). type testConnClosingContext struct { parent context.Context mu *sync.Mutex From 43e060b0e57b1aa4ab45b6efee394c4586cf062c Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Mon, 11 Jan 2021 20:50:09 -0800 Subject: [PATCH 18/34] netcheck: test sortRegions Signed-off-by: Denton Gentry --- net/netcheck/netcheck_test.go | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/net/netcheck/netcheck_test.go b/net/netcheck/netcheck_test.go index 94a5b369b..7041ba0e9 100644 --- a/net/netcheck/netcheck_test.go +++ b/net/netcheck/netcheck_test.go @@ -578,3 +578,39 @@ func TestLogConciseReport(t *testing.T) { }) } } + +func TestSortRegions(t *testing.T) { + unsortedMap := &tailcfg.DERPMap{ + Regions: map[int]*tailcfg.DERPRegion{}, + } + for rid := 1; rid <= 5; rid++ { + var nodes []*tailcfg.DERPNode + nodes = append(nodes, &tailcfg.DERPNode{ + Name: fmt.Sprintf("%da", rid), + RegionID: rid, + HostName: fmt.Sprintf("derp%d-1", rid), + IPv4: fmt.Sprintf("%d.0.0.1", rid), + IPv6: fmt.Sprintf("%d::1", rid), + }) + unsortedMap.Regions[rid] = &tailcfg.DERPRegion{ + RegionID: rid, + Nodes: nodes, + } + } + report := newReport() + report.RegionLatency[1] = time.Second * time.Duration(5) + report.RegionLatency[2] = time.Second * time.Duration(3) + report.RegionLatency[3] = time.Second * time.Duration(6) + report.RegionLatency[4] = time.Second * time.Duration(0) + report.RegionLatency[5] = time.Second * time.Duration(2) + got := sortRegions(unsortedMap, report) + + // Sorting by latency this should result in rid: 5, 2, 1, 3 + // rid 4 with latency 0 should be at the end + expected := []int{5, 2, 1, 3, 4} + for idx, want := range expected { + if got[idx].RegionID != want { + t.Errorf("idx:%v got:%v want:%v\n", idx, got[idx].RegionID, want) + } + } +} From ac42757cd755cf057e328a67b1eab15985e7b001 Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Tue, 12 Jan 2021 04:15:43 -0800 Subject: [PATCH 19/34] netcheck: use reflect in sortRegions test. Signed-off-by: Denton Gentry --- net/netcheck/netcheck_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/net/netcheck/netcheck_test.go b/net/netcheck/netcheck_test.go index 7041ba0e9..d3c77b71e 100644 --- a/net/netcheck/netcheck_test.go +++ b/net/netcheck/netcheck_test.go @@ -603,14 +603,16 @@ func TestSortRegions(t *testing.T) { report.RegionLatency[3] = time.Second * time.Duration(6) report.RegionLatency[4] = time.Second * time.Duration(0) report.RegionLatency[5] = time.Second * time.Duration(2) - got := sortRegions(unsortedMap, report) + sortedMap := sortRegions(unsortedMap, report) // Sorting by latency this should result in rid: 5, 2, 1, 3 // rid 4 with latency 0 should be at the end - expected := []int{5, 2, 1, 3, 4} - for idx, want := range expected { - if got[idx].RegionID != want { - t.Errorf("idx:%v got:%v want:%v\n", idx, got[idx].RegionID, want) - } + want := []int{5, 2, 1, 3, 4} + got := make([]int, len(sortedMap)) + for i, r := range sortedMap { + got[i] = r.RegionID + } + if !reflect.DeepEqual(got, want) { + t.Errorf("got %v; want %v", got, want) } } From 8d7ddf5e949f7a20f59ff29414f298a874f47a8f Mon Sep 17 00:00:00 2001 From: Christina Wen Date: Thu, 7 Jan 2021 10:09:38 -0500 Subject: [PATCH 20/34] API.md: rename "domain" to "tailnet" Signed-off-by: Christina Wen --- api.md | 139 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 83 insertions(+), 56 deletions(-) diff --git a/api.md b/api.md index c0e00dc44..b1d6dac39 100644 --- a/api.md +++ b/api.md @@ -9,6 +9,12 @@ Currently based on {some authentication method}. Visit the [admin panel](https:/ ## Device +Each Tailscale-connected device has a globally-unique identifier number which we refer as the "deviceID" or sometimes, just "id". +You can use the deviceID to specify operations on a specific device, like retrieving its subnet routes. + +To find the deviceID of a particular device, you can use the ["GET /devices"](#getdevices) API call and generate a list of devices on your network. +Find the device you're looking for and get the "id" field. +This is your deviceID. #### `GET /api/v2/device/:deviceid` - lists the details for a device Returns the details for the specified device. @@ -163,17 +169,37 @@ Response } ``` -## Domain - +## Tailnet +A tailnet is the name of your Tailscale network. +You can find it in the top left corner of the [Admin Panel](https://login.tailscale.com/admin) beside the Tailscale logo. + + +"alice@example.com" belongs to the "example.com" tailnet and would use the following format for API calls: +``` +GET /api/v2/tailnet/example.com/... +curl https://api.tailscale.com/api/v2/tailnet/example.com/... +``` + + +For solo plans, the tailnet is the email you signed up with. +So "alice@gmail.com" has the tailnet "alice@gmail.com" since @gmail.com is a shared email host. +Her API calls would have the following format: +``` +GET /api/v2/tailnet/alice@gmail.com/... +curl https://api.tailscale.com/api/v2/tailnet/alice@gmail.com/... +``` + +Tailnets are a top level resource. ACL is an example of a resource that is tied to a top level tailnet. + + +For more information on Tailscale networks/tailnets, click [here](https://tailscale.com/kb/1064/invite-team-members). + ### ACL -#### `GET /api/v2/domain/:domain/acl` - fetch ACL for a domain +#### `GET /api/v2/tailnet/:tailnet/acl` - fetch ACL for a tailnet -Retrieves the ACL that is currently set for the given domain. Supply the domain of interest in the path. This endpoint can send back either the HuJSON of the ACL or a parsed JSON, depending on the `Accept` header. +Retrieves the ACL that is currently set for the given tailnet. Supply the tailnet of interest in the path. This endpoint can send back either the HuJSON of the ACL or a parsed JSON, depending on the `Accept` header. ##### Parameters @@ -188,8 +214,8 @@ Returns the ACL HuJSON by default. Returns a parsed JSON of the ACL (sans commen ###### Requesting a HuJSON response: ``` -GET /api/v2/domain/example.com/acl -curl https://api.tailscale.com/api/v2/domain/example.com/acl \ +GET /api/v2/tailnet/example.com/acl +curl https://api.tailscale.com/api/v2/tailnet/example.com/acl \ -u "tskey-yourapikey123:" \ -H "Accept: application/hujson" \ -v @@ -235,8 +261,8 @@ Etag: "e0b2816b418b3f266309d94426ac7668ab3c1fa87798785bf82f1085cc2f6d9c" ###### Requesting a JSON response: ``` -GET /api/v2/domain/example.com/acl -curl https://api.tailscale.com/api/v2/domain/example.com/acl \ +GET /api/v2/tailnet/example.com/acl +curl https://api.tailscale.com/api/v2/tailnet/example.com/acl \ -u "tskey-yourapikey123:" \ -H "Accept: application/json" \ -v @@ -272,9 +298,9 @@ Etag: "e0b2816b418b3f266309d94426ac7668ab3c1fa87798785bf82f1085cc2f6d9c" } ``` -#### `POST /api/v2/domain/:domain/acl` - set ACL for a domain +#### `POST /api/v2/tailnet/:tailnet/acl` - set ACL for a tailnet -Sets the ACL for the given domain. HuJSON and JSON are both accepted inputs. An `If-Match` header can be set to avoid missed updates. +Sets the ACL for the given tailnet. HuJSON and JSON are both accepted inputs. An `If-Match` header can be set to avoid missed updates. Returns error for invalid ACLs. Returns error if using an `If-Match` header and the ETag does not match. @@ -291,8 +317,8 @@ ACL JSON or HuJSON (see https://tailscale.com/kb/1018/acls) ##### Example ``` -POST /api/v2/domain/example.com/acl -curl https://api.tailscale.com/api/v2/domain/example.com/acl \ +POST /api/v2/tailnet/example.com/acl +curl https://api.tailscale.com/api/v2/tailnet/example.com/acl \ -u "tskey-yourapikey123:" \ -H "If-Match: \"e0b2816b418b3f266309d94426ac7668ab3c1fa87798785bf82f1085cc2f6d9c\"" --data-binary '// Example/default ACLs for unrestricted connections. @@ -343,7 +369,7 @@ Response } ``` -#### `POST /api/v2/domain/:domain/acl/preview` - preview rule matches on an ACL for a resource +#### `POST /api/v2/tailnet/:tailnet/acl/preview` - preview rule matches on an ACL for a resource Determines what rules match for a user on an ACL without saving the ACL to the server. ##### Parameters @@ -356,8 +382,8 @@ ACL JSON or HuJSON (see https://tailscale.com/kb/1018/acls) ##### Example ``` -POST /api/v2/domain/example.com/acl/preiew -curl https://api.tailscale.com/api/v2/domain/example.com/acl?user=user1@example.com \ +POST /api/v2/tailnet/example.com/acl/preiew +curl https://api.tailscale.com/api/v2/tailnet/example.com/acl?user=user1@example.com \ -u "tskey-yourapikey123:" \ --data-binary '// Example/default ACLs for unrestricted connections. { @@ -388,9 +414,10 @@ Response ``` ### Devices -#### `GET /api/v2/domain/:domain/devices` - list the devices for a domain -Lists the devices for a domain. -Supply the domain of interest in the path. + +#### `GET /api/v2/tailnet/:tailnet/devices` - list the devices for a tailnet +Lists the devices in a tailnet. +Supply the tailnet of interest in the path. Use the `fields` query parameter to explicitly indicate which fields are returned. @@ -413,8 +440,8 @@ If the `fields` parameter is not provided, then the default option is used. ##### Example ``` -GET /api/v2/domain/example.com/devices -curl https://api.tailscale.com/api/v2/domain/example.com/devices \ +GET /api/v2/tailnet/example.com/devices +curl https://api.tailscale.com/api/v2/tailnet/example.com/devices \ -u "tskey-yourapikey123:" ``` @@ -471,9 +498,9 @@ Response ### DNS -#### `GET /api/v2/domain/:domain/dns/nameservers` - list the DNS nameservers for a domain -Lists the DNS nameservers for a domain. -Supply the domain of interest in the path. +#### `GET /api/v2/tailnet/:tailnet/dns/nameservers` - list the DNS nameservers for a tailnet +Lists the DNS nameservers for a tailnet. +Supply the tailnet of interest in the path. ##### Parameters No parameters. @@ -481,8 +508,8 @@ No parameters. ##### Example ``` -GET /api/v2/domain/example.com/dns/nameservers -curl https://api.tailscale.com/api/v2/domain/example.com/dns/nameservers \ +GET /api/v2/tailnet/example.com/dns/nameservers +curl https://api.tailscale.com/api/v2/tailnet/example.com/dns/nameservers \ -u "tskey-yourapikey123:" ``` @@ -493,9 +520,9 @@ Response } ``` -#### `POST /api/v2/domain/:domain/dns/nameservers` - replaces the list of DNS nameservers for a domain -Replaces the list of DNS nameservers for the given domain with the list supplied by the user. -Supply the domain of interest in the path. +#### `POST /api/v2/tailnet/:tailnet/dns/nameservers` - replaces the list of DNS nameservers for a tailnet +Replaces the list of DNS nameservers for the given tailnet with the list supplied by the user. +Supply the tailnet of interest in the path. Note that changing the list of DNS nameservers may also affect the status of MagicDNS (if MagicDNS is on). ##### Parameters @@ -515,8 +542,8 @@ If all nameservers have been removed, MagicDNS will be automatically disabled (u ##### Example ###### Adding DNS nameservers with the MagicDNS on: ``` -POST /api/v2/domain/example.com/dns/nameservers -curl -X POST 'https://api.tailscale.com/api/v2/domain/example.com/dns/nameservers' \ +POST /api/v2/tailnet/example.com/dns/nameservers +curl -X POST 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/nameservers' \ -u "tskey-yourapikey123:" \ --data-binary '{"dns": ["8.8.8.8"]}' ``` @@ -531,8 +558,8 @@ Response: ###### Removing all DNS nameservers with the MagicDNS on: ``` -POST /api/v2/domain/example.com/dns/nameservers -curl -X POST 'https://api.tailscale.com/api/v2/domain/example.com/dns/nameservers' \ +POST /api/v2/tailnet/example.com/dns/nameservers +curl -X POST 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/nameservers' \ -u "tskey-yourapikey123:" \ --data-binary '{"dns": []}' ``` @@ -545,17 +572,17 @@ Response: } ``` -#### `GET /api/v2/domain/:domain/dns/preferences` - retrieves the DNS preferences for a domain -Retrieves the DNS preferences that are currently set for the given domain. -Supply the domain of interest in the path. +#### `GET /api/v2/tailnet/:tailnet/dns/preferences` - retrieves the DNS preferences for a tailnet +Retrieves the DNS preferences that are currently set for the given tailnet. +Supply the tailnet of interest in the path. ##### Parameters No parameters. ##### Example ``` -GET /api/v2/domain/example.com/dns/preferences -curl 'https://api.tailscale.com/api/v2/domain/example.com/dns/preferences' \ +GET /api/v2/tailnet/example.com/dns/preferences +curl 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/preferences' \ -u "tskey-yourapikey123:" ``` @@ -566,19 +593,19 @@ Response: } ``` -#### `POST /api/v2/domain/:domain/dns/preferences` - replaces the DNS preferences for a domain -Replaces the DNS preferences for a domain, specifically, the MagicDNS setting. +#### `POST /api/v2/tailnet/:tailnet/dns/preferences` - replaces the DNS preferences for a tailnet +Replaces the DNS preferences for a tailnet, specifically, the MagicDNS setting. Note that MagicDNS is dependent on DNS servers. If there is at least one DNS server, then MagicDNS can be enabled. Otherwise, it returns an error. Note that removing all nameservers will turn off MagicDNS. -To reenable it, nameservers must be added back, and MagicDNS must be explicity turned on. +To reenable it, nameservers must be added back, and MagicDNS must be explicitly turned on. ##### Parameters ###### POST Body The DNS preferences in JSON. Currently, MagicDNS is the only setting available. -`magicDNS` - Automatically registers DNS names for devices in your network. +`magicDNS` - Automatically registers DNS names for devices in your tailnet. ``` { "magicDNS": true @@ -587,8 +614,8 @@ The DNS preferences in JSON. Currently, MagicDNS is the only setting available. ##### Example ``` -POST /api/v2/domain/example.com/dns/preferences -curl -X POST 'https://api.tailscale.com/api/v2/domain/example.com/dns/preferences' \ +POST /api/v2/tailnet/example.com/dns/preferences +curl -X POST 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/preferences' \ -u "tskey-yourapikey123:" \ --data-binary '{"magicDNS": true}' ``` @@ -610,9 +637,9 @@ If there are DNS servers: } ``` -#### `GET /api/v2/domain/:domain/dns/searchpaths` - retrieves the search paths for a domain -Retrieves the list of search paths that is currently set for the given domain. -Supply the domain of interest in the path. +#### `GET /api/v2/tailnet/:tailnet/dns/searchpaths` - retrieves the search paths for a tailnet +Retrieves the list of search paths that is currently set for the given tailnet. +Supply the tailnet of interest in the path. ##### Parameters @@ -620,8 +647,8 @@ No parameters. ##### Example ``` -GET /api/v2/domain/example.com/dns/searchpaths -curl 'https://api.tailscale.com/api/v2/domain/example.com/dns/searchpaths' \ +GET /api/v2/tailnet/example.com/dns/searchpaths +curl 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/searchpaths' \ -u "tskey-yourapikey123:" ``` @@ -632,13 +659,13 @@ Response: } ``` -#### `POST /api/v2/domain/:domain/dns/searchpaths` - replaces the search paths for a domain -Replaces the list of search paths with the list supplied by the user and returns an error otherwise. +#### `POST /api/v2/tailnet/:tailnet/dns/searchpaths` - replaces the search paths for a tailnet +Replaces the list of searchpaths with the list supplied by the user and returns an error otherwise. ##### Parameters ###### POST Body -`searchPaths` - A list of searchpaths in JSON format. +`searchPaths` - A list of searchpaths in JSON. ``` { "searchPaths: ["user1.example.com", "user2.example.com"] @@ -647,8 +674,8 @@ Replaces the list of search paths with the list supplied by the user and returns ##### Example ``` -POST /api/v2/domain/example.com/dns/searchpaths -curl -X POST 'https://api.tailscale.com/api/v2/domain/example.com/dns/searchpaths' \ +POST /api/v2/tailnet/example.com/dns/searchpaths +curl -X POST 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/searchpaths' \ -u "tskey-yourapikey123:" \ --data-binary '{"searchPaths": ["user1.example.com", "user2.example.com"]}' ``` From a746ff5de77715b1e9ef46b8b83bf321c9803806 Mon Sep 17 00:00:00 2001 From: Christina Wen Date: Fri, 8 Jan 2021 10:38:21 -0500 Subject: [PATCH 21/34] API.md: add documentation for deleting a device Signed-off-by: Christina Wen --- api.md | 54 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/api.md b/api.md index b1d6dac39..e159f91c0 100644 --- a/api.md +++ b/api.md @@ -40,7 +40,7 @@ If the `fields` parameter is not provided, then the default option is used. ##### Example ``` GET /api/v2/device/12345 -curl https://api.tailscale.com/api/v2/device/12345?fields=all \ +curl 'https://api.tailscale.com/api/v2/device/12345?fields=all' \ -u "tskey-yourapikey123:" ``` @@ -103,6 +103,42 @@ Response } ``` + +#### `DELETE /api/v2/device/:deviceID` - deletes the device from its tailnet +Deletes the provided device from its tailnet. +The device must belong to the user's tailnet. +Deleting shared/external devices is not supported. +Supply the device of interest in the path using its ID. + + +##### Parameters +No parameters. + +##### Example +``` +DELETE /api/v2/device/12345 +curl -X DELETE 'https://api.tailscale.com/api/v2/device/12345' \ + -u "tskey-yourapikey123:" -v +``` + +Response + +If successful, the response should be empty: +``` +< HTTP/1.1 200 OK +... +* Connection #0 to host left intact +* Closing connection 0 +``` + +If the device is not owned by your tailnet: +``` +< HTTP/1.1 501 Not Implemented +... +{"message":"cannot delete devices outside of your tailnet"} +``` + + #### `GET /api/v2/device/:deviceID/routes` - fetch subnet routes that are advertised and enabled for a device Retrieves the list of subnet routes that a device is advertising, as well as those that are enabled for it. Enabled routes are not necessarily advertised (e.g. for pre-enabling), and likewise, advertised routes are not necessarily enabled. @@ -114,7 +150,7 @@ No parameters. ##### Example ``` -curl https://api.tailscale.com/api/v2/device/11055/routes \ +curl 'https://api.tailscale.com/api/v2/device/11055/routes' \ -u "tskey-yourapikey123:" ``` @@ -147,7 +183,7 @@ Sets which subnet routes are enabled to be routed by a device by replacing the e ##### Example ``` -curl https://api.tailscale.com/api/v2/device/11055/routes \ +curl 'https://api.tailscale.com/api/v2/device/11055/routes' \ -u "tskey-yourapikey123:" \ --data-binary '{"routes": ["10.0.1.0/24", "1.2.0.0/16", "2.0.0.0/24"]}' ``` @@ -215,7 +251,7 @@ Returns the ACL HuJSON by default. Returns a parsed JSON of the ACL (sans commen ###### Requesting a HuJSON response: ``` GET /api/v2/tailnet/example.com/acl -curl https://api.tailscale.com/api/v2/tailnet/example.com/acl \ +curl 'https://api.tailscale.com/api/v2/tailnet/example.com/acl' \ -u "tskey-yourapikey123:" \ -H "Accept: application/hujson" \ -v @@ -262,7 +298,7 @@ Etag: "e0b2816b418b3f266309d94426ac7668ab3c1fa87798785bf82f1085cc2f6d9c" ###### Requesting a JSON response: ``` GET /api/v2/tailnet/example.com/acl -curl https://api.tailscale.com/api/v2/tailnet/example.com/acl \ +curl 'https://api.tailscale.com/api/v2/tailnet/example.com/acl' \ -u "tskey-yourapikey123:" \ -H "Accept: application/json" \ -v @@ -318,7 +354,7 @@ ACL JSON or HuJSON (see https://tailscale.com/kb/1018/acls) ##### Example ``` POST /api/v2/tailnet/example.com/acl -curl https://api.tailscale.com/api/v2/tailnet/example.com/acl \ +curl 'https://api.tailscale.com/api/v2/tailnet/example.com/acl' \ -u "tskey-yourapikey123:" \ -H "If-Match: \"e0b2816b418b3f266309d94426ac7668ab3c1fa87798785bf82f1085cc2f6d9c\"" --data-binary '// Example/default ACLs for unrestricted connections. @@ -383,7 +419,7 @@ ACL JSON or HuJSON (see https://tailscale.com/kb/1018/acls) ##### Example ``` POST /api/v2/tailnet/example.com/acl/preiew -curl https://api.tailscale.com/api/v2/tailnet/example.com/acl?user=user1@example.com \ +curl 'https://api.tailscale.com/api/v2/tailnet/example.com/acl?user=user1@example.com' \ -u "tskey-yourapikey123:" \ --data-binary '// Example/default ACLs for unrestricted connections. { @@ -441,7 +477,7 @@ If the `fields` parameter is not provided, then the default option is used. ``` GET /api/v2/tailnet/example.com/devices -curl https://api.tailscale.com/api/v2/tailnet/example.com/devices \ +curl 'https://api.tailscale.com/api/v2/tailnet/example.com/devices' \ -u "tskey-yourapikey123:" ``` @@ -509,7 +545,7 @@ No parameters. ``` GET /api/v2/tailnet/example.com/dns/nameservers -curl https://api.tailscale.com/api/v2/tailnet/example.com/dns/nameservers \ +curl 'https://api.tailscale.com/api/v2/tailnet/example.com/dns/nameservers' \ -u "tskey-yourapikey123:" ``` From 9d73f84a7166ae10f98b7e67678cd4ea8ebb3c1f Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 12 Jan 2021 07:54:34 -0800 Subject: [PATCH 22/34] tailcfg, control/controlclient: make MapResponse.CollectServices an opt.Bool Signed-off-by: Brad Fitzpatrick --- control/controlclient/direct.go | 7 ++++++- tailcfg/tailcfg.go | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index d0d65080a..49fc6b10d 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -661,6 +661,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw var lastDERPMap *tailcfg.DERPMap var lastUserProfile = map[tailcfg.UserID]tailcfg.UserProfile{} var lastParsedPacketFilter []filter.Match + var collectServices bool // If allowStream, then the server will use an HTTP long poll to // return incremental results. There is always one response right @@ -742,6 +743,10 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw lastParsedPacketFilter = c.parsePacketFilter(pf) } + if v, ok := resp.CollectServices.Get(); ok { + collectServices = v + } + // Get latest localPort. This might've changed if // a lite map update occured meanwhile. This only affects // the end-to-end test. @@ -765,7 +770,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw DNS: resp.DNSConfig, Hostinfo: resp.Node.Hostinfo, PacketFilter: lastParsedPacketFilter, - CollectServices: resp.CollectServices, + CollectServices: collectServices, DERPMap: lastDERPMap, Debug: resp.Debug, } diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index df1cfcd60..e30b8004a 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -667,7 +667,9 @@ type MapResponse struct { // CollectServices reports whether this node's Tailnet has // requested that info about services be included in HostInfo. - CollectServices bool `json:",omitempty"` + // If unset, the most recent non-empty MapResponse value in + // the HTTP response stream is used. + CollectServices opt.Bool `json:",omitempty"` // PacketFilter are the firewall rules. // From 7acd3397d5de11cd8dfb09f6a0bb7bf04f63a316 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 12 Jan 2021 08:24:24 -0800 Subject: [PATCH 23/34] README: names of contributors, link to them instead --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c882dab3a..ac970b18b 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,13 @@ Origin](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin) ## About Us -We are apenwarr, bradfitz, crawshaw, danderson, dfcarney, josharian -from Tailscale Inc. -You can learn more about us from [our website](https://tailscale.com). +[Tailscale](https://tailscale.com/) is primarily developed by the +people at https://github.com/orgs/tailscale/people. For other contributors, +see: + +* https://github.com/tailscale/tailscale/graphs/contributors +* https://github.com/tailscale/tailscale-android/graphs/contributors + +## Legal WireGuard is a registered trademark of Jason A. Donenfeld. From b987b2ab18ff4807c9b29d77510fd9ab4c3f0779 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 12 Jan 2021 12:13:27 -0800 Subject: [PATCH 24/34] control/controlclient: treat node sharer as owner for display purposes This make clients (macOS, Windows, tailscale status) show the node sharer's profile rather than the node owner (which may be anonymized). Updates #992 --- control/controlclient/direct.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 49fc6b10d..1941b21fe 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -785,6 +785,20 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw } addUserProfile(nm.User) for _, peer := range resp.Peers { + // TODO(bradfitz): ideally we'd push down the semantically correct + // Nodes with differing User vs Sharer fields, but that means + // updating Windows, macOS, and tailscale status to respect all + // those fields, but until we have a plan for what the UI should + // be later when we treat them differently, it's easier to just + // merge it together here. The server will anonymize UserProfile + // records of those not in your network and not a sharer, which + // will be most of the peer.Users so it'll be rare when a node's + // owner-who's-different-from-sharer will have a non-scrubbed + // UserProfile: they would've also needed to share a node + // themselves. Until we care, merge the data here. + if !peer.Sharer.IsZero() { + peer.User = peer.Sharer + } addUserProfile(peer.User) } if resp.Node.MachineAuthorized { From e4f53e9b6f1a4d3d6f00091ddef617989b1ea3e4 Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Tue, 12 Jan 2021 13:31:45 -0800 Subject: [PATCH 25/34] Add logtail tests (#1114) * logtail: test parseAndRemoveLogLevel() Signed-off-by: Denton Gentry * logtail: test JSON log encoding. Expand TestUploadMessages to also exercise the encoding functions in logtail, like JSON logging and timestamps. Other tests frequently send logs but a) don't check the result and b) do so by happenstance, such that the lines in encode() were not consistently being exercised and leading to spurious changes in code coverage. Signed-off-by: Denton Gentry * logtail: add a test for drainPendingMessages Make the client buffer some messages before the upload server becomes available. Signed-off-by: Denton Gentry * logtail: use %q, raw strings, and io.WriteString %q escapes binary characters for us. raw strings avoid so much backslash escaping Signed-off-by: Denton Gentry --- logtail/logtail_test.go | 216 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 209 insertions(+), 7 deletions(-) diff --git a/logtail/logtail_test.go b/logtail/logtail_test.go index 576cc1888..6ee5627d8 100644 --- a/logtail/logtail_test.go +++ b/logtail/logtail_test.go @@ -6,8 +6,12 @@ package logtail import ( "context" + "encoding/json" + "io" + "io/ioutil" "net/http" "net/http/httptest" + "strings" "testing" "time" ) @@ -26,28 +30,177 @@ func TestFastShutdown(t *testing.T) { l.Shutdown(ctx) } -func TestUploadMessages(t *testing.T) { +// accumulate some logs before the server becomes available, exercise the drain path +func TestDrainPendingMessages(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - uploads := 0 + uploaded := make(chan int) + bodytext := "" testServ := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - uploads += 1 + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Error("failed to read HTTP request") + } + bodytext += "\n" + string(body) + + uploaded <- 0 })) defer testServ.Close() l := NewLogger(Config{BaseURL: testServ.URL}, t.Logf) - for i := 1; i < 10; i++ { + for i := 0; i < 3; i++ { l.Write([]byte("log line")) } + select { + case <-uploaded: + if strings.Count(bodytext, "log line") == 3 { + break + } + case <-time.After(1 * time.Second): + t.Errorf("Timed out waiting for log uploads") + } + l.Shutdown(ctx) cancel() - if uploads == 0 { - t.Error("no log uploads") + if strings.Count(bodytext, "log line") != 3 { + t.Errorf("got %q; want: 3 copies of %q", bodytext, "log line") } } +func TestEncodeAndUploadMessages(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + var jsonbody []byte + uploaded := make(chan int) + testServ := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Error("failed to read HTTP request") + } + jsonbody = body + uploaded <- 0 + })) + defer testServ.Close() + + l := NewLogger(Config{BaseURL: testServ.URL}, t.Logf) + + // There is always an initial "logtail started" message + <-uploaded + if !strings.Contains(string(jsonbody), "started") { + t.Errorf("initialize: got:%q; want:%q", string(jsonbody), "started") + } + + tests := []struct { + name string + log string + want string + }{ + { + "plain text", + "log line", + "log line", + }, + { + "simple JSON", + `{"text": "log line"}`, + "log line", + }, + { + "escaped characters", + `\b\f\n\r\t"\\`, + `\b\f\n\r\t"\\`, + }, + } + + for _, tt := range tests { + io.WriteString(l, tt.log) + <-uploaded + + data := make(map[string]interface{}) + err := json.Unmarshal(jsonbody, &data) + if err != nil { + t.Error(err) + } + + got, ok := data["text"] + if ok { + if got != tt.want { + t.Errorf("%s: got %q; want %q", tt.name, got.(string), tt.want) + } + } else { + t.Errorf("no text in: %v", data) + } + + ltail, ok := data["logtail"] + if ok { + logtailmap := ltail.(map[string]interface{}) + _, ok = logtailmap["client_time"] + if !ok { + t.Errorf("%s: no client_time present", tt.name) + } + } else { + t.Errorf("%s: no logtail map present", tt.name) + } + } + + // test some special cases + + // JSON log message already contains a logtail field. + io.WriteString(l, `{"logtail": "LOGTAIL", "text": "text"}`) + <-uploaded + data := make(map[string]interface{}) + err := json.Unmarshal(jsonbody, &data) + if err != nil { + t.Error(err) + } + error_has_logtail, ok := data["error_has_logtail"] + if ok { + if error_has_logtail.(string) != "LOGTAIL" { + t.Errorf("error_has_logtail: got:%q; want:%q", + error_has_logtail.(string), "LOGTAIL") + } + } else { + t.Errorf("no error_has_logtail field: %v", data) + } + + // skipClientTime to omit the logtail metadata + l.skipClientTime = true + io.WriteString(l, "text") + <-uploaded + data = make(map[string]interface{}) + err = json.Unmarshal(jsonbody, &data) + if err != nil { + t.Error(err) + } + _, ok = data["logtail"] + if ok { + t.Errorf("skipClientTime: unexpected logtail map present: %v", data) + } + + // lowMem + long string + l.skipClientTime = false + l.lowMem = true + longStr := strings.Repeat("0", 512) + io.WriteString(l, longStr) + <-uploaded + data = make(map[string]interface{}) + err = json.Unmarshal(jsonbody, &data) + if err != nil { + t.Error(err) + } + text, ok := data["text"] + if !ok { + t.Errorf("lowMem: no text %v", data) + } + if len(text.(string)) > 300 { + t.Errorf("lowMem: got %d chars; want <300 chars", len(text.(string))) + } + + l.Shutdown(ctx) + cancel() +} + var sink []byte func TestLoggerEncodeTextAllocs(t *testing.T) { @@ -75,3 +228,52 @@ func TestLoggerWriteLength(t *testing.T) { t.Errorf("logger.Write wrote %d bytes, expected %d", n, len(inBuf)) } } + +func TestParseAndRemoveLogLevel(t *testing.T) { + tests := []struct { + log string + wantLevel int + wantLog string + }{ + { + "no level", + 0, + "no level", + }, + { + "[v1] level 1", + 1, + "level 1", + }, + { + "level 1 [v1] ", + 1, + "level 1 ", + }, + { + "[v2] level 2", + 2, + "level 2", + }, + { + "level [v2] 2", + 2, + "level 2", + }, + { + "[v3] no level 3", + 0, + "[v3] no level 3", + }, + } + + for _, tt := range tests { + gotLevel, gotLog := parseAndRemoveLogLevel([]byte(tt.log)) + if gotLevel != tt.wantLevel { + t.Errorf("%q: got:%d; want %d", tt.log, gotLevel, tt.wantLevel) + } + if string(gotLog) != tt.wantLog { + t.Errorf("%q: got:%q; want %q", tt.log, gotLog, tt.wantLog) + } + } +} From b2a08ddacd1ccd4bd84861323b695418501e78da Mon Sep 17 00:00:00 2001 From: Smitty Date: Thu, 31 Dec 2020 17:31:33 -0500 Subject: [PATCH 26/34] wgengine/tsdns: return NOERROR instead of NOTIMP for most records This is what every other DNS resolver I could find does, so tsdns should do it to. This also helps avoid weird error messages about non-existent records being unimplemented, and thus fixes #848. Signed-off-by: Smitty --- wgengine/tsdns/tsdns.go | 16 +++++++++++++++- wgengine/tsdns/tsdns_test.go | 4 ++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/wgengine/tsdns/tsdns.go b/wgengine/tsdns/tsdns.go index c14503003..fad334539 100644 --- a/wgengine/tsdns/tsdns.go +++ b/wgengine/tsdns/tsdns.go @@ -228,8 +228,22 @@ func (r *Resolver) Resolve(domain string, tp dns.Type) (netaddr.IP, dns.RCode, e // It could be IPv4, IPv6, or a zero addr. // TODO: Return all available resolutions (A and AAAA, if we have them). return addr, dns.RCodeSuccess, nil - default: + + // Leave some some record types explicitly unimplemented. + // These types relate to recursive resolution or special + // DNS sematics and might be implemented in the future. + case dns.TypeNS, dns.TypeSOA, dns.TypeAXFR, dns.TypeHINFO: return netaddr.IP{}, dns.RCodeNotImplemented, errNotImplemented + + // For everything except for the few types above that are explictly not implemented, return no records. + // This is what other DNS systems do: always return NOERROR + // without any records whenever the requested record type is unknown. + // You can try this with: + // dig -t TYPE9824 example.com + // and note that NOERROR is returned, despite that record type being made up. + default: + // no records exist of this type + return netaddr.IP{}, dns.RCodeSuccess, nil } } diff --git a/wgengine/tsdns/tsdns_test.go b/wgengine/tsdns/tsdns_test.go index 7e28b9efb..2eb0df479 100644 --- a/wgengine/tsdns/tsdns_test.go +++ b/wgengine/tsdns/tsdns_test.go @@ -215,6 +215,10 @@ func TestResolve(t *testing.T) { {"nxdomain", "test3.ipn.dev.", dns.TypeA, netaddr.IP{}, dns.RCodeNameError}, {"foreign domain", "google.com.", dns.TypeA, netaddr.IP{}, dns.RCodeRefused}, {"all", "test1.ipn.dev.", dns.TypeA, testipv4, dns.RCodeSuccess}, + {"mx-ipv4", "test1.ipn.dev.", dns.TypeMX, netaddr.IP{}, dns.RCodeSuccess}, + {"mx-ipv6", "test2.ipn.dev.", dns.TypeMX, netaddr.IP{}, dns.RCodeSuccess}, + {"mx-nxdomain", "test3.ipn.dev.", dns.TypeMX, netaddr.IP{}, dns.RCodeNameError}, + {"ns-nxdomain", "test3.ipn.dev.", dns.TypeNS, netaddr.IP{}, dns.RCodeNameError}, } for _, tt := range tests { From ce058c82804ad1a60c21dbf898be3cb101183693 Mon Sep 17 00:00:00 2001 From: Denton Gentry Date: Tue, 12 Jan 2021 15:48:11 -0800 Subject: [PATCH 27/34] Revert "Add logtail tests (#1114)" (#1116) This reverts commit e4f53e9b6f1a4d3d6f00091ddef617989b1ea3e4. At least two of these tests are flakey, reverting until they can be made more robust. Signed-off-by: Denton Gentry --- logtail/logtail_test.go | 216 ++-------------------------------------- 1 file changed, 7 insertions(+), 209 deletions(-) diff --git a/logtail/logtail_test.go b/logtail/logtail_test.go index 6ee5627d8..576cc1888 100644 --- a/logtail/logtail_test.go +++ b/logtail/logtail_test.go @@ -6,12 +6,8 @@ package logtail import ( "context" - "encoding/json" - "io" - "io/ioutil" "net/http" "net/http/httptest" - "strings" "testing" "time" ) @@ -30,177 +26,28 @@ func TestFastShutdown(t *testing.T) { l.Shutdown(ctx) } -// accumulate some logs before the server becomes available, exercise the drain path -func TestDrainPendingMessages(t *testing.T) { +func TestUploadMessages(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - uploaded := make(chan int) - bodytext := "" + uploads := 0 testServ := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Error("failed to read HTTP request") - } - bodytext += "\n" + string(body) - - uploaded <- 0 + w.Header().Set("Content-Type", "application/json; charset=utf-8") + uploads += 1 })) defer testServ.Close() l := NewLogger(Config{BaseURL: testServ.URL}, t.Logf) - for i := 0; i < 3; i++ { + for i := 1; i < 10; i++ { l.Write([]byte("log line")) } - select { - case <-uploaded: - if strings.Count(bodytext, "log line") == 3 { - break - } - case <-time.After(1 * time.Second): - t.Errorf("Timed out waiting for log uploads") - } - l.Shutdown(ctx) cancel() - if strings.Count(bodytext, "log line") != 3 { - t.Errorf("got %q; want: 3 copies of %q", bodytext, "log line") + if uploads == 0 { + t.Error("no log uploads") } } -func TestEncodeAndUploadMessages(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - var jsonbody []byte - uploaded := make(chan int) - testServ := httptest.NewServer(http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Error("failed to read HTTP request") - } - jsonbody = body - uploaded <- 0 - })) - defer testServ.Close() - - l := NewLogger(Config{BaseURL: testServ.URL}, t.Logf) - - // There is always an initial "logtail started" message - <-uploaded - if !strings.Contains(string(jsonbody), "started") { - t.Errorf("initialize: got:%q; want:%q", string(jsonbody), "started") - } - - tests := []struct { - name string - log string - want string - }{ - { - "plain text", - "log line", - "log line", - }, - { - "simple JSON", - `{"text": "log line"}`, - "log line", - }, - { - "escaped characters", - `\b\f\n\r\t"\\`, - `\b\f\n\r\t"\\`, - }, - } - - for _, tt := range tests { - io.WriteString(l, tt.log) - <-uploaded - - data := make(map[string]interface{}) - err := json.Unmarshal(jsonbody, &data) - if err != nil { - t.Error(err) - } - - got, ok := data["text"] - if ok { - if got != tt.want { - t.Errorf("%s: got %q; want %q", tt.name, got.(string), tt.want) - } - } else { - t.Errorf("no text in: %v", data) - } - - ltail, ok := data["logtail"] - if ok { - logtailmap := ltail.(map[string]interface{}) - _, ok = logtailmap["client_time"] - if !ok { - t.Errorf("%s: no client_time present", tt.name) - } - } else { - t.Errorf("%s: no logtail map present", tt.name) - } - } - - // test some special cases - - // JSON log message already contains a logtail field. - io.WriteString(l, `{"logtail": "LOGTAIL", "text": "text"}`) - <-uploaded - data := make(map[string]interface{}) - err := json.Unmarshal(jsonbody, &data) - if err != nil { - t.Error(err) - } - error_has_logtail, ok := data["error_has_logtail"] - if ok { - if error_has_logtail.(string) != "LOGTAIL" { - t.Errorf("error_has_logtail: got:%q; want:%q", - error_has_logtail.(string), "LOGTAIL") - } - } else { - t.Errorf("no error_has_logtail field: %v", data) - } - - // skipClientTime to omit the logtail metadata - l.skipClientTime = true - io.WriteString(l, "text") - <-uploaded - data = make(map[string]interface{}) - err = json.Unmarshal(jsonbody, &data) - if err != nil { - t.Error(err) - } - _, ok = data["logtail"] - if ok { - t.Errorf("skipClientTime: unexpected logtail map present: %v", data) - } - - // lowMem + long string - l.skipClientTime = false - l.lowMem = true - longStr := strings.Repeat("0", 512) - io.WriteString(l, longStr) - <-uploaded - data = make(map[string]interface{}) - err = json.Unmarshal(jsonbody, &data) - if err != nil { - t.Error(err) - } - text, ok := data["text"] - if !ok { - t.Errorf("lowMem: no text %v", data) - } - if len(text.(string)) > 300 { - t.Errorf("lowMem: got %d chars; want <300 chars", len(text.(string))) - } - - l.Shutdown(ctx) - cancel() -} - var sink []byte func TestLoggerEncodeTextAllocs(t *testing.T) { @@ -228,52 +75,3 @@ func TestLoggerWriteLength(t *testing.T) { t.Errorf("logger.Write wrote %d bytes, expected %d", n, len(inBuf)) } } - -func TestParseAndRemoveLogLevel(t *testing.T) { - tests := []struct { - log string - wantLevel int - wantLog string - }{ - { - "no level", - 0, - "no level", - }, - { - "[v1] level 1", - 1, - "level 1", - }, - { - "level 1 [v1] ", - 1, - "level 1 ", - }, - { - "[v2] level 2", - 2, - "level 2", - }, - { - "level [v2] 2", - 2, - "level 2", - }, - { - "[v3] no level 3", - 0, - "[v3] no level 3", - }, - } - - for _, tt := range tests { - gotLevel, gotLog := parseAndRemoveLogLevel([]byte(tt.log)) - if gotLevel != tt.wantLevel { - t.Errorf("%q: got:%d; want %d", tt.log, gotLevel, tt.wantLevel) - } - if string(gotLog) != tt.wantLog { - t.Errorf("%q: got:%q; want %q", tt.log, gotLog, tt.wantLog) - } - } -} From 2bf49ddf90d4a43a3f325b339f69326d75ca7cc7 Mon Sep 17 00:00:00 2001 From: Smitty Date: Wed, 13 Jan 2021 15:53:59 -0500 Subject: [PATCH 28/34] Provide example when format string is rate limited Here's an example log line in the new format: [RATE LIMITED] format string "open-conn-track: timeout opening %v; no associated peer node" (example: "open-conn-track: timeout opening ([ip] => [ip]); no associated peer node") This should make debugging logging issues a bit easier, and give more context as to why something was rate limited. This change was proposed in a comment on #1110. Signed-off-by: Smitty --- types/logger/logger.go | 2 +- types/logger/logger_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/types/logger/logger.go b/types/logger/logger.go index e9a2229fe..646becdcd 100644 --- a/types/logger/logger.go +++ b/types/logger/logger.go @@ -132,7 +132,7 @@ func RateLimitedFn(logf Logf, f time.Duration, burst int, maxCache int) Logf { logf(format, args...) case warn: // For the warning, log the specific format string - logf("[RATE LIMITED] format string \"%s\"", format) + logf("[RATE LIMITED] format string \"%s\" (example: \"%s\")", format, fmt.Sprintf(format, args...)) } } } diff --git a/types/logger/logger_test.go b/types/logger/logger_test.go index ce9fa942c..15827e916 100644 --- a/types/logger/logger_test.go +++ b/types/logger/logger_test.go @@ -45,8 +45,8 @@ func TestRateLimiter(t *testing.T) { "templated format string no. 0", "boring string with constant formatting (constant)", "templated format string no. 1", - "[RATE LIMITED] format string \"boring string with constant formatting %s\"", - "[RATE LIMITED] format string \"templated format string no. %d\"", + "[RATE LIMITED] format string \"boring string with constant formatting %s\" (example: \"boring string with constant formatting (constant)\")", + "[RATE LIMITED] format string \"templated format string no. %d\" (example: \"templated format string no. 2\")", "Make sure this string makes it through the rest (that are blocked) 4", "4 shouldn't get filtered.", } From 020084e84d17879c281ee34e44bfa910cf77b8a5 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 13 Jan 2021 14:39:34 -0800 Subject: [PATCH 29/34] wgengine: adapt to removal of wgcfg.Key in wireguard-go --- wgengine/userspace.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 32ef52d47..ba7c5e655 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -295,7 +295,7 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) { opts := &device.DeviceOptions{ Logger: &logger, - HandshakeDone: func(peerKey wgcfg.Key, peer *device.Peer, deviceAllowedIPs *device.AllowedIPs) { + HandshakeDone: func(peerKey device.NoisePublicKey, peer *device.Peer, deviceAllowedIPs *device.AllowedIPs) { // Send an unsolicited status event every time a // handshake completes. This makes sure our UI can // update quickly as soon as it connects to a peer. @@ -306,13 +306,14 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) { // here. go e.RequestStatus() + peerWGKey := wgkey.Key(peerKey) if e.magicConn.PeerHasDiscoKey(tailcfg.NodeKey(peerKey)) { - e.logf("wireguard handshake complete for %v", peerKey.ShortString()) + e.logf("wireguard handshake complete for %v", peerWGKey.ShortString()) // This is a modern peer with discovery support. No need to send pings. return } - e.logf("wireguard handshake complete for %v; sending legacy pings", peerKey.ShortString()) + e.logf("wireguard handshake complete for %v; sending legacy pings", peerWGKey.ShortString()) // Ping every single-IP that peer routes. // These synthetic packets are used to traverse NATs. @@ -328,9 +329,9 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) { } } if len(ips) > 0 { - go e.pinger(wgkey.Key(peerKey), ips) + go e.pinger(peerWGKey, ips) } else { - logf("[unexpected] peer %s has no single-IP routes: %v", peerKey.ShortString(), allowedIPs) + logf("[unexpected] peer %s has no single-IP routes: %v", peerWGKey.ShortString(), allowedIPs) } }, CreateBind: e.magicConn.CreateBind, From b38fa7de29c77f08c2c37665c9c3223091cd5a04 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 13 Jan 2021 14:41:25 -0800 Subject: [PATCH 30/34] go.mod: update to latest wireguard-go --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 868a98a44..1d43f6d15 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 github.com/peterbourgon/ff/v2 v2.0.0 github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 - github.com/tailscale/wireguard-go v0.0.0-20210109012254-dc30a1b9415e + github.com/tailscale/wireguard-go v0.0.0-20210113223737-a6213b5eaf98 github.com/tcnksm/go-httpstat v0.2.0 github.com/toqueteos/webbrowser v1.2.0 go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 From c1dabd94367492a8c4cf0b0329b180250b073e26 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 13 Jan 2021 15:03:15 -0800 Subject: [PATCH 31/34] control/controlclient: let clients opt in to Sharer-vs-User split model Updates tailscale/corp#1183 --- control/controlclient/direct.go | 65 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 1941b21fe..9c69faadf 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -107,16 +107,17 @@ func (p *Persist) Pretty() string { // Direct is the client that connects to a tailcontrol server for a node. type Direct struct { - httpc *http.Client // HTTP client used to talk to tailcontrol - serverURL string // URL of the tailcontrol server - timeNow func() time.Time - lastPrintMap time.Time - newDecompressor func() (Decompressor, error) - keepAlive bool - logf logger.Logf - discoPubKey tailcfg.DiscoKey - machinePrivKey wgkey.Private - debugFlags []string + httpc *http.Client // HTTP client used to talk to tailcontrol + serverURL string // URL of the tailcontrol server + timeNow func() time.Time + lastPrintMap time.Time + newDecompressor func() (Decompressor, error) + keepAlive bool + logf logger.Logf + discoPubKey tailcfg.DiscoKey + machinePrivKey wgkey.Private + debugFlags []string + keepSharerAndUserSplit bool mu sync.Mutex // mutex guards the following fields serverKey wgkey.Key @@ -144,6 +145,10 @@ type Options struct { Logf logger.Logf HTTPTestClient *http.Client // optional HTTP client to use (for tests only) DebugFlags []string // debug settings to send to control + + // KeepSharerAndUserSplit controls whether the client + // understands Node.Sharer. If false, the Sharer is mapped to the User. + KeepSharerAndUserSplit bool } type Decompressor interface { @@ -190,17 +195,18 @@ func NewDirect(opts Options) (*Direct, error) { } c := &Direct{ - httpc: httpc, - machinePrivKey: opts.MachinePrivateKey, - serverURL: opts.ServerURL, - timeNow: opts.TimeNow, - logf: opts.Logf, - newDecompressor: opts.NewDecompressor, - keepAlive: opts.KeepAlive, - persist: opts.Persist, - authKey: opts.AuthKey, - discoPubKey: opts.DiscoPublicKey, - debugFlags: opts.DebugFlags, + httpc: httpc, + machinePrivKey: opts.MachinePrivateKey, + serverURL: opts.ServerURL, + timeNow: opts.TimeNow, + logf: opts.Logf, + newDecompressor: opts.NewDecompressor, + keepAlive: opts.KeepAlive, + persist: opts.Persist, + authKey: opts.AuthKey, + discoPubKey: opts.DiscoPublicKey, + debugFlags: opts.DebugFlags, + keepSharerAndUserSplit: opts.KeepSharerAndUserSplit, } if opts.Hostinfo == nil { c.SetHostinfo(NewHostinfo()) @@ -785,19 +791,12 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw } addUserProfile(nm.User) for _, peer := range resp.Peers { - // TODO(bradfitz): ideally we'd push down the semantically correct - // Nodes with differing User vs Sharer fields, but that means - // updating Windows, macOS, and tailscale status to respect all - // those fields, but until we have a plan for what the UI should - // be later when we treat them differently, it's easier to just - // merge it together here. The server will anonymize UserProfile - // records of those not in your network and not a sharer, which - // will be most of the peer.Users so it'll be rare when a node's - // owner-who's-different-from-sharer will have a non-scrubbed - // UserProfile: they would've also needed to share a node - // themselves. Until we care, merge the data here. if !peer.Sharer.IsZero() { - peer.User = peer.Sharer + if c.keepSharerAndUserSplit { + addUserProfile(peer.Sharer) + } else { + peer.User = peer.Sharer + } } addUserProfile(peer.User) } From 017dcd520f3cb485faea037b2a6e9fad438afa43 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 14 Jan 2021 11:49:44 -0800 Subject: [PATCH 32/34] tsweb: export VarzHandler --- tsweb/tsweb.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsweb/tsweb.go b/tsweb/tsweb.go index 411e615a4..0cbe2fb82 100644 --- a/tsweb/tsweb.go +++ b/tsweb/tsweb.go @@ -43,7 +43,7 @@ func registerCommonDebug(mux *http.ServeMux) { expvar.Publish("counter_uptime_sec", expvar.Func(func() interface{} { return int64(Uptime().Seconds()) })) mux.Handle("/debug/pprof/", Protected(http.DefaultServeMux)) // to net/http/pprof mux.Handle("/debug/vars", Protected(http.DefaultServeMux)) // to expvar - mux.Handle("/debug/varz", Protected(http.HandlerFunc(varzHandler))) + mux.Handle("/debug/varz", Protected(http.HandlerFunc(VarzHandler))) mux.Handle("/debug/gc", Protected(http.HandlerFunc(gcHandler))) } @@ -371,7 +371,7 @@ func Error(code int, msg string, err error) HTTPError { return HTTPError{Code: code, Msg: msg, Err: err} } -// varzHandler is an HTTP handler to write expvar values into the +// VarzHandler is an HTTP handler to write expvar values into the // prometheus export format: // // https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md @@ -388,7 +388,7 @@ func Error(code int, msg string, err error) HTTPError { // is not exported. // // This will evolve over time, or perhaps be replaced. -func varzHandler(w http.ResponseWriter, r *http.Request) { +func VarzHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; version=0.0.4") var dump func(prefix string, kv expvar.KeyValue) From 22507adf5489a8293e03a5af06bd6af41d031468 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 12 Jan 2021 15:28:33 -0800 Subject: [PATCH 33/34] wgengine/magicsock: stop depending on UpdateDst in legacy codepaths. This makes connectivity between ancient and new tailscale nodes slightly worse in some cases, but only in cases where the ancient version would likely have failed to get connectivity anyway. Signed-off-by: David Anderson --- cmd/tailscale/depaware.txt | 2 +- cmd/tailscaled/depaware.txt | 2 +- types/key/key.go | 9 ++ wgengine/magicsock/legacy.go | 170 +++++++++++++++++++-------- wgengine/magicsock/magicsock.go | 30 +++-- wgengine/magicsock/magicsock_test.go | 4 +- 6 files changed, 155 insertions(+), 62 deletions(-) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index e8e189677..e2c266c11 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -90,7 +90,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/wgengine/tstun from tailscale.com/wgengine W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box - golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device + golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+ golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305 golang.org/x/crypto/chacha20poly1305 from crypto/tls+ golang.org/x/crypto/cryptobyte from crypto/ecdsa+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 811052f7d..7528faa96 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -131,7 +131,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/wgengine/tstun from tailscale.com/wgengine+ W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box - golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device + golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+ golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305 golang.org/x/crypto/chacha20poly1305 from crypto/tls+ golang.org/x/crypto/cryptobyte from crypto/ecdsa+ diff --git a/types/key/key.go b/types/key/key.go index 8b70fffa6..7928c1437 100644 --- a/types/key/key.go +++ b/types/key/key.go @@ -82,6 +82,15 @@ func (k Private) Public() Public { return Public(pub) } +func (k Private) SharedSecret(pub Public) (ss [32]byte) { + apk := (*[32]byte)(&pub) + ask := (*[32]byte)(&k) + //lint:ignore SA1019 Code copied from wireguard-go, we aim for + //minimal changes from it. + curve25519.ScalarMult(&ss, ask, apk) + return ss +} + // NewPublicFromHexMem parses a public key in its hex form, given in m. // The provided m must be exactly 64 bytes in length. func NewPublicFromHexMem(m mem.RO) (Public, error) { diff --git a/wgengine/magicsock/legacy.go b/wgengine/magicsock/legacy.go index 083f313db..b6f784438 100644 --- a/wgengine/magicsock/legacy.go +++ b/wgengine/magicsock/legacy.go @@ -5,6 +5,8 @@ package magicsock import ( + "bytes" + "crypto/subtle" "encoding/binary" "errors" "fmt" @@ -16,6 +18,8 @@ import ( "github.com/tailscale/wireguard-go/conn" "github.com/tailscale/wireguard-go/device" "github.com/tailscale/wireguard-go/wgcfg" + "golang.org/x/crypto/blake2s" + "golang.org/x/crypto/chacha20poly1305" "inet.af/netaddr" "tailscale.com/ipn/ipnstate" "tailscale.com/types/key" @@ -70,17 +74,58 @@ func (c *Conn) createLegacyEndpointLocked(pk key.Public, addrs string) (conn.End return a, nil } -func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, addr *net.UDPAddr) conn.Endpoint { +func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, addr *net.UDPAddr, packet []byte) conn.Endpoint { // Pre-disco: look up their addrSet. if as, ok := c.addrsByUDP[ipp]; ok { + as.updateDst(addr) return as } - // Pre-disco: the peer that sent this packet has roamed beyond - // the knowledge provided by the control server. If the - // packet is valid wireguard will call UpdateDst on the - // original endpoint using this addr. - return (*singleEndpoint)(addr) + // We don't know who this peer is. It's possible that it's one of + // our legitimate peers and they've roamed to an address we don't + // know. If this is a handshake packet, we can try to identify the + // peer in question. + if as := c.peerFromPacketLocked(packet); as != nil { + as.updateDst(addr) + return as + } + + // We have no idea who this is, drop the packet. + // + // In the past, when this magicsock implementation was the main + // one, we tried harder to find a match here: we would pass the + // packet into wireguard-go with a "singleEndpoint" implementation + // that wrapped the UDPAddr. Then, a patch we added to + // wireguard-go would call UpdateDst on that singleEndpoint after + // decrypting the packet and identifying the peer (if any), + // allowing us to update the relevant addrSet. + // + // This was a significant out of tree patch to wireguard-go, so we + // got rid of it, and instead switched to this logic you're + // reading now, which makes a best effort to identify sources for + // handshake packets (because they're relatively easy to turn into + // a peer public key statelessly), but otherwise drops packets + // that come from "roaming" addresses that aren't known to + // magicsock. + // + // The practical consequence of this is that some complex NAT + // traversal cases will now fail between a very old Tailscale + // client (0.96 and earlier) and a very new Tailscale + // client. However, those scenarios were likely also failing on + // all-old clients, because the probabilistic NAT opening didn't + // work reliably. So, in practice, this simplification means + // connectivity looks like this: + // + // - old+old client: unchanged + // - old+new client (easy network topology): unchanged + // - old+new client (hard network topology): was bad, now a bit worse + // - new+new client: unchanged + // + // This degradation is acceptable in that it continue to support + // the incremental upgrade of old clients that currently work + // well, which is our primary goal for the <100 clients still left + // on the oldest pre-DERP versions (as of 2021-01-12). + return nil } func (c *Conn) resetAddrSetStatesLocked() { @@ -90,16 +135,6 @@ func (c *Conn) resetAddrSetStatesLocked() { } } -func (c *Conn) sendSingleEndpoint(b []byte, se *singleEndpoint) error { - addr := (*net.UDPAddr)(se) - if addr.IP.Equal(derpMagicIP) { - c.logf("magicsock: [unexpected] DERP BUG: attempting to send packet to DERP address %v", addr) - return nil - } - _, err := c.sendUDPStd(addr, b) - return err -} - func (c *Conn) sendAddrSet(b []byte, as *addrSet) error { var addrBuf [8]netaddr.IPPort dsts, roamAddr := as.appendDests(addrBuf[:0], b) @@ -129,6 +164,57 @@ func (c *Conn) sendAddrSet(b []byte, as *addrSet) error { return ret } +// peerFromPacketLocked extracts returns the addrSet for the peer who sent +// packet, if derivable. +func (c *Conn) peerFromPacketLocked(packet []byte) *addrSet { + if len(packet) < 4 { + return nil + } + msgType := binary.LittleEndian.Uint32(packet[:4]) + if msgType != device.MessageInitiationType { + // Can't get peer out of a non-handshake packet. + return nil + } + + var msg device.MessageInitiation + reader := bytes.NewReader(packet) + err := binary.Read(reader, binary.LittleEndian, &msg) + if err != nil { + return nil + } + + // Process just enough of the handshake to extract the long-term + // peer public key. We don't verify the handshake all the way, so + // this may be a spoofed packet. The extracted peer MUST NOT be + // used for any security critical function. In our case, we use it + // as a hint for roaming addresses. + var ( + pub = c.privateKey.Public() + hash [blake2s.Size]byte + chainKey [blake2s.Size]byte + peerPK key.Public + boxKey [chacha20poly1305.KeySize]byte + ) + + mixHash(&hash, &device.InitialHash, pub[:]) + mixHash(&hash, &hash, msg.Ephemeral[:]) + mixKey(&chainKey, &device.InitialChainKey, msg.Ephemeral[:]) + + ss := c.privateKey.SharedSecret(key.Public(msg.Ephemeral)) + if isZero(ss[:]) { + return nil + } + + device.KDF2(&chainKey, &boxKey, chainKey[:], ss[:]) + aead, _ := chacha20poly1305.New(boxKey[:]) + _, err = aead.Open(peerPK[:0], device.ZeroNonce[:], msg.Static[:], hash[:]) + if err != nil { + return nil + } + + return c.addrsByKey[peerPK] +} + func shouldSprayPacket(b []byte) bool { if len(b) < 4 { return false @@ -325,19 +411,6 @@ func (a *addrSet) dst() netaddr.IPPort { return a.ipPorts[i] } -// packUDPAddr packs a UDPAddr in the form wanted by WireGuard. -func packUDPAddr(ua *net.UDPAddr) []byte { - ip := ua.IP.To4() - if ip == nil { - ip = ua.IP - } - b := make([]byte, 0, len(ip)+2) - b = append(b, ip...) - b = append(b, byte(ua.Port)) - b = append(b, byte(ua.Port>>8)) - return b -} - func (a *addrSet) DstToBytes() []byte { return packIPPort(a.dst()) } @@ -353,6 +426,12 @@ func (a *addrSet) SrcToString() string { return "" } func (a *addrSet) ClearSrc() {} func (a *addrSet) UpdateDst(new *net.UDPAddr) error { + return nil +} + +// updateDst records receipt of a packet from new. This is used to +// potentially update the transmit address used for this addrSet. +func (a *addrSet) updateDst(new *net.UDPAddr) error { if new.IP.Equal(derpMagicIP) { // Never consider DERP addresses as a viable candidate for // either curAddr or roamAddr. It's only ever a last resort @@ -500,23 +579,22 @@ func (a *addrSet) Addrs() []wgcfg.Endpoint { return eps } -// singleEndpoint is a wireguard-go/conn.Endpoint used for "roaming -// addressed" in releases of Tailscale that predate discovery -// messages. New peers use discoEndpoint. -type singleEndpoint net.UDPAddr +func mixKey(dst *[blake2s.Size]byte, c *[blake2s.Size]byte, data []byte) { + device.KDF1(dst, c[:], data) +} -func (e *singleEndpoint) ClearSrc() {} -func (e *singleEndpoint) DstIP() net.IP { return (*net.UDPAddr)(e).IP } -func (e *singleEndpoint) SrcIP() net.IP { return nil } -func (e *singleEndpoint) SrcToString() string { return "" } -func (e *singleEndpoint) DstToString() string { return (*net.UDPAddr)(e).String() } -func (e *singleEndpoint) DstToBytes() []byte { return packUDPAddr((*net.UDPAddr)(e)) } -func (e *singleEndpoint) UpdateDst(dst *net.UDPAddr) error { - return fmt.Errorf("magicsock.singleEndpoint(%s).UpdateDst(%s): should never be called", (*net.UDPAddr)(e), dst) +func mixHash(dst *[blake2s.Size]byte, h *[blake2s.Size]byte, data []byte) { + hash, _ := blake2s.New256(nil) + hash.Write(h[:]) + hash.Write(data) + hash.Sum(dst[:0]) + hash.Reset() } -func (e *singleEndpoint) Addrs() []wgcfg.Endpoint { - return []wgcfg.Endpoint{{ - Host: e.IP.String(), - Port: uint16(e.Port), - }} + +func isZero(val []byte) bool { + acc := 1 + for _, b := range val { + acc &= subtle.ConstantTimeByteEq(b, 0) + } + return acc == 1 } diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 7b451a0b7..1cacc827d 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -1008,8 +1008,6 @@ func (c *Conn) Send(b []byte, ep conn.Endpoint) error { panic(fmt.Sprintf("[unexpected] Endpoint type %T", v)) case *discoEndpoint: return v.send(b) - case *singleEndpoint: - return c.sendSingleEndpoint(b, v) case *addrSet: return c.sendAddrSet(b, v) } @@ -1418,7 +1416,7 @@ func (c *Conn) runDerpWriter(ctx context.Context, dc *derphttp.Client, ch <-chan // Endpoint to find the UDPAddr to return to wireguard anyway, so no // benefit unless we can, say, always return the same fake UDPAddr for // all packets. -func (c *Conn) findEndpoint(ipp netaddr.IPPort, addr *net.UDPAddr) conn.Endpoint { +func (c *Conn) findEndpoint(ipp netaddr.IPPort, addr *net.UDPAddr, packet []byte) conn.Endpoint { c.mu.Lock() defer c.mu.Unlock() @@ -1430,7 +1428,7 @@ func (c *Conn) findEndpoint(ipp netaddr.IPPort, addr *net.UDPAddr) conn.Endpoint } } - return c.findLegacyEndpointLocked(ipp, addr) + return c.findLegacyEndpointLocked(ipp, addr, packet) } type udpReadResult struct { @@ -1513,7 +1511,10 @@ Top: if from := c.bufferedIPv4From; from != (netaddr.IPPort{}) { c.bufferedIPv4From = netaddr.IPPort{} addr = from.UDPAddr() - ep := c.findEndpoint(from, addr) + ep := c.findEndpoint(from, addr, c.bufferedIPv4Packet) + if ep == nil { + goto Top + } c.noteRecvActivityFromEndpoint(ep) return copy(b, c.bufferedIPv4Packet), ep, wgRecvAddr(ep, from, addr), nil } @@ -1600,11 +1601,10 @@ Top: } else { key := wgkey.Key(dm.src) c.logf("magicsock: DERP packet from unknown key: %s", key.ShortString()) - // TODO(danderson): after we fail to find a DERP endpoint, we - // seem to be falling through to passing the packet to - // wireguard with a garbage singleEndpoint. This feels wrong, - // should we goto Top above? - ep = c.findEndpoint(ipp, addr) + ep = c.findEndpoint(ipp, addr, b[:n]) + if ep == nil { + goto Top + } } if !didNoteRecvActivity { @@ -1617,7 +1617,10 @@ Top: return 0, nil, nil, err } n, addr, ipp = um.n, um.addr, um.ipp - ep = c.findEndpoint(ipp, addr) + ep = c.findEndpoint(ipp, addr, b[:n]) + if ep == nil { + goto Top + } c.noteRecvActivityFromEndpoint(ep) return n, ep, wgRecvAddr(ep, ipp, addr), nil @@ -1658,7 +1661,10 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, *net.UDPAddr, error) { continue } - ep := c.findEndpoint(ipp, addr) + ep := c.findEndpoint(ipp, addr, b[:n]) + if ep == nil { + continue + } c.noteRecvActivityFromEndpoint(ep) return n, ep, wgRecvAddr(ep, ipp, addr), nil } diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 4aac0cdad..72030e962 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -1216,7 +1216,7 @@ func testTwoDevicePing(t *testing.T, d *devices) { }) } -// TestAddrSet tests addrSet appendDests and UpdateDst. +// TestAddrSet tests addrSet appendDests and updateDst. func TestAddrSet(t *testing.T) { tstest.PanicOnLog() rc := tstest.NewResourceCheck() @@ -1378,7 +1378,7 @@ func TestAddrSet(t *testing.T) { faket = faket.Add(st.advance) if st.updateDst != nil { - if err := tt.as.UpdateDst(st.updateDst); err != nil { + if err := tt.as.updateDst(st.updateDst); err != nil { t.Fatal(err) } continue From 9abcb18061ec148833399545cbd94ef327efd28c Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 14 Jan 2021 12:41:02 -0800 Subject: [PATCH 34/34] wgengine/magicsock: import more of wireguard-go, update docstrings. Signed-off-by: David Anderson --- cmd/tailscale/depaware.txt | 2 +- cmd/tailscaled/depaware.txt | 2 +- wgengine/magicsock/legacy.go | 99 +++++++++++++++++++++++++++++++----- 3 files changed, 89 insertions(+), 14 deletions(-) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index e2c266c11..cbbff7734 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -24,7 +24,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/tailscale/wireguard-go/ratelimiter from github.com/tailscale/wireguard-go/device github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+ - github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device + github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device+ 💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+ W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun+ github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/conn+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 7528faa96..faf95f01e 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -28,7 +28,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de github.com/tailscale/wireguard-go/ratelimiter from github.com/tailscale/wireguard-go/device github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+ - github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device + github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device+ 💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+ W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun+ github.com/tailscale/wireguard-go/wgcfg from github.com/tailscale/wireguard-go/conn+ diff --git a/wgengine/magicsock/legacy.go b/wgengine/magicsock/legacy.go index b6f784438..2af2b664a 100644 --- a/wgengine/magicsock/legacy.go +++ b/wgengine/magicsock/legacy.go @@ -6,20 +6,23 @@ package magicsock import ( "bytes" + "crypto/hmac" "crypto/subtle" "encoding/binary" "errors" "fmt" + "hash" "net" "strings" "sync" "time" "github.com/tailscale/wireguard-go/conn" - "github.com/tailscale/wireguard-go/device" + "github.com/tailscale/wireguard-go/tai64n" "github.com/tailscale/wireguard-go/wgcfg" "golang.org/x/crypto/blake2s" "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/poly1305" "inet.af/netaddr" "tailscale.com/ipn/ipnstate" "tailscale.com/types/key" @@ -121,7 +124,7 @@ func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, addr *net.UDPAddr, p // - old+new client (hard network topology): was bad, now a bit worse // - new+new client: unchanged // - // This degradation is acceptable in that it continue to support + // This degradation is acceptable in that it continues to support // the incremental upgrade of old clients that currently work // well, which is our primary goal for the <100 clients still left // on the oldest pre-DERP versions (as of 2021-01-12). @@ -166,17 +169,22 @@ func (c *Conn) sendAddrSet(b []byte, as *addrSet) error { // peerFromPacketLocked extracts returns the addrSet for the peer who sent // packet, if derivable. +// +// The derived addrSet is a hint, not a cryptographically strong +// assertion. The returned value MUST NOT be used for any security +// critical function. Callers MUST assume that the addrset can be +// picked by a remote attacker. func (c *Conn) peerFromPacketLocked(packet []byte) *addrSet { if len(packet) < 4 { return nil } msgType := binary.LittleEndian.Uint32(packet[:4]) - if msgType != device.MessageInitiationType { + if msgType != messageInitiationType { // Can't get peer out of a non-handshake packet. return nil } - var msg device.MessageInitiation + var msg messageInitiation reader := bytes.NewReader(packet) err := binary.Read(reader, binary.LittleEndian, &msg) if err != nil { @@ -196,18 +204,18 @@ func (c *Conn) peerFromPacketLocked(packet []byte) *addrSet { boxKey [chacha20poly1305.KeySize]byte ) - mixHash(&hash, &device.InitialHash, pub[:]) + mixHash(&hash, &initialHash, pub[:]) mixHash(&hash, &hash, msg.Ephemeral[:]) - mixKey(&chainKey, &device.InitialChainKey, msg.Ephemeral[:]) + mixKey(&chainKey, &initialChainKey, msg.Ephemeral[:]) ss := c.privateKey.SharedSecret(key.Public(msg.Ephemeral)) if isZero(ss[:]) { return nil } - device.KDF2(&chainKey, &boxKey, chainKey[:], ss[:]) + kdf2(&chainKey, &boxKey, chainKey[:], ss[:]) aead, _ := chacha20poly1305.New(boxKey[:]) - _, err = aead.Open(peerPK[:0], device.ZeroNonce[:], msg.Static[:], hash[:]) + _, err = aead.Open(peerPK[:0], zeroNonce[:], msg.Static[:], hash[:]) if err != nil { return nil } @@ -221,9 +229,9 @@ func shouldSprayPacket(b []byte) bool { } msgType := binary.LittleEndian.Uint32(b[:4]) switch msgType { - case device.MessageInitiationType, - device.MessageResponseType, - device.MessageCookieReplyType: // TODO: necessary? + case messageInitiationType, + messageResponseType, + messageCookieReplyType: // TODO: necessary? return true } return false @@ -579,8 +587,41 @@ func (a *addrSet) Addrs() []wgcfg.Endpoint { return eps } +// Message types copied from wireguard-go/device/noise-protocol.go +const ( + messageInitiationType = 1 + messageResponseType = 2 + messageCookieReplyType = 3 +) + +// Cryptographic constants copied from wireguard-go/device/noise-protocol.go +var ( + noiseConstruction = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s" + wgIdentifier = "WireGuard v1 zx2c4 Jason@zx2c4.com" + initialChainKey [blake2s.Size]byte + initialHash [blake2s.Size]byte + zeroNonce [chacha20poly1305.NonceSize]byte +) + +func init() { + initialChainKey = blake2s.Sum256([]byte(noiseConstruction)) + mixHash(&initialHash, &initialChainKey, []byte(wgIdentifier)) +} + +// messageInitiation is the same as wireguard-go's MessageInitiation, +// from wireguard-go/device/noise-protocol.go. +type messageInitiation struct { + Type uint32 + Sender uint32 + Ephemeral wgcfg.Key + Static [wgcfg.KeySize + poly1305.TagSize]byte + Timestamp [tai64n.TimestampSize + poly1305.TagSize]byte + MAC1 [blake2s.Size128]byte + MAC2 [blake2s.Size128]byte +} + func mixKey(dst *[blake2s.Size]byte, c *[blake2s.Size]byte, data []byte) { - device.KDF1(dst, c[:], data) + kdf1(dst, c[:], data) } func mixHash(dst *[blake2s.Size]byte, h *[blake2s.Size]byte, data []byte) { @@ -591,6 +632,40 @@ func mixHash(dst *[blake2s.Size]byte, h *[blake2s.Size]byte, data []byte) { hash.Reset() } +func hmac1(sum *[blake2s.Size]byte, key, in0 []byte) { + mac := hmac.New(func() hash.Hash { + h, _ := blake2s.New256(nil) + return h + }, key) + mac.Write(in0) + mac.Sum(sum[:0]) +} + +func hmac2(sum *[blake2s.Size]byte, key, in0, in1 []byte) { + mac := hmac.New(func() hash.Hash { + h, _ := blake2s.New256(nil) + return h + }, key) + mac.Write(in0) + mac.Write(in1) + mac.Sum(sum[:0]) +} + +func kdf1(t0 *[blake2s.Size]byte, key, input []byte) { + hmac1(t0, key, input) + hmac1(t0, t0[:], []byte{0x1}) +} + +func kdf2(t0, t1 *[blake2s.Size]byte, key, input []byte) { + var prk [blake2s.Size]byte + hmac1(&prk, key, input) + hmac1(t0, prk[:], []byte{0x1}) + hmac2(t1, prk[:], t0[:], []byte{0x2}) + for i := range prk[:] { + prk[i] = 0 + } +} + func isZero(val []byte) bool { acc := 1 for _, b := range val {