mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
cmd/tailscale: make netcheck use active DERP map, delete static copy
After allowing for custom DERP maps, it's convenient to be able to see their latency in netcheck. This adds a query to the local tailscaled for the current DERPMap. Updates #1264 Signed-off-by: julianknodt <julianknodt@gmail.com>
This commit is contained in:
parent
15677d8a0e
commit
506c2fe8e2
@ -24,6 +24,7 @@
|
|||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/paths"
|
"tailscale.com/paths"
|
||||||
"tailscale.com/safesocket"
|
"tailscale.com/safesocket"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TailscaledSocket is the tailscaled Unix socket.
|
// TailscaledSocket is the tailscaled Unix socket.
|
||||||
@ -278,3 +279,17 @@ func SetDNS(ctx context.Context, name, value string) error {
|
|||||||
_, err := send(ctx, "POST", "/localapi/v0/set-dns?"+v.Encode(), 200, nil)
|
_, err := send(ctx, "POST", "/localapi/v0/set-dns?"+v.Encode(), 200, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CurrentDERPMap returns the current DERPMap that is being used by the local tailscaled.
|
||||||
|
// It is intended to be used with netcheck to see availability of DERPs.
|
||||||
|
func CurrentDERPMap(ctx context.Context) (*tailcfg.DERPMap, error) {
|
||||||
|
var derpMap tailcfg.DERPMap
|
||||||
|
res, err := send(ctx, "GET", "/localapi/v0/derpmap", 200, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(res, &derpMap); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid derp map json: %w", err)
|
||||||
|
}
|
||||||
|
return &derpMap, nil
|
||||||
|
}
|
||||||
|
@ -9,14 +9,18 @@
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v2/ffcli"
|
"github.com/peterbourgon/ff/v2/ffcli"
|
||||||
"tailscale.com/derp/derpmap"
|
"tailscale.com/client/tailscale"
|
||||||
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/net/netcheck"
|
"tailscale.com/net/netcheck"
|
||||||
"tailscale.com/net/portmapper"
|
"tailscale.com/net/portmapper"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@ -59,7 +63,13 @@ func runNetcheck(ctx context.Context, args []string) error {
|
|||||||
fmt.Fprintln(os.Stderr, "# Warning: this JSON format is not yet considered a stable interface")
|
fmt.Fprintln(os.Stderr, "# Warning: this JSON format is not yet considered a stable interface")
|
||||||
}
|
}
|
||||||
|
|
||||||
dm := derpmap.Prod()
|
dm, err := tailscale.CurrentDERPMap(ctx)
|
||||||
|
if err != nil {
|
||||||
|
dm, err = prodDERPMap(ctx, http.DefaultClient)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
report, err := c.GetReport(ctx, dm)
|
report, err := c.GetReport(ctx, dm)
|
||||||
@ -176,3 +186,27 @@ func portMapping(r *netcheck.Report) string {
|
|||||||
}
|
}
|
||||||
return strings.Join(got, ", ")
|
return strings.Join(got, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prodDERPMap(ctx context.Context, httpc *http.Client) (*tailcfg.DERPMap, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", ipn.DefaultControlURL+"/derpmap/default", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create prodDERPMap request: %w", err)
|
||||||
|
}
|
||||||
|
res, err := httpc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fetch prodDERPMap failed: %w", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fetch prodDERPMap failed: %w", err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("fetch prodDERPMap: %v: %s", res.Status, b)
|
||||||
|
}
|
||||||
|
var derpMap tailcfg.DERPMap
|
||||||
|
if err = json.Unmarshal(b, &derpMap); err != nil {
|
||||||
|
return nil, fmt.Errorf("fetch prodDERPMap: %w", err)
|
||||||
|
}
|
||||||
|
return &derpMap, nil
|
||||||
|
}
|
||||||
|
@ -20,7 +20,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscale
|
tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscale
|
||||||
tailscale.com/derp from tailscale.com/derp/derphttp
|
tailscale.com/derp from tailscale.com/derp/derphttp
|
||||||
tailscale.com/derp/derphttp from tailscale.com/net/netcheck
|
tailscale.com/derp/derphttp from tailscale.com/net/netcheck
|
||||||
tailscale.com/derp/derpmap from tailscale.com/cmd/tailscale/cli
|
|
||||||
tailscale.com/disco from tailscale.com/derp
|
tailscale.com/disco from tailscale.com/derp
|
||||||
tailscale.com/hostinfo from tailscale.com/net/interfaces
|
tailscale.com/hostinfo from tailscale.com/net/interfaces
|
||||||
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli+
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptrace"
|
"net/http/httptrace"
|
||||||
@ -19,7 +21,7 @@
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/derp/derphttp"
|
"tailscale.com/derp/derphttp"
|
||||||
"tailscale.com/derp/derpmap"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/interfaces"
|
||||||
"tailscale.com/net/tshttpproxy"
|
"tailscale.com/net/tshttpproxy"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@ -131,7 +133,26 @@ func getURL(ctx context.Context, urlStr string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkDerp(ctx context.Context, derpRegion string) error {
|
func checkDerp(ctx context.Context, derpRegion string) error {
|
||||||
dmap := derpmap.Prod()
|
req, err := http.NewRequestWithContext(ctx, "GET", ipn.DefaultControlURL+"/derpmap/default", nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create derp map request: %w", err)
|
||||||
|
}
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fetch derp map failed: %w", err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
b, err := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("fetch derp map failed: %w", err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("fetch derp map: %v: %s", res.Status, b)
|
||||||
|
}
|
||||||
|
var dmap tailcfg.DERPMap
|
||||||
|
if err = json.Unmarshal(b, &dmap); err != nil {
|
||||||
|
return fmt.Errorf("fetch DERP map: %w", err)
|
||||||
|
}
|
||||||
getRegion := func() *tailcfg.DERPRegion {
|
getRegion := func() *tailcfg.DERPRegion {
|
||||||
for _, r := range dmap.Regions {
|
for _, r := range dmap.Regions {
|
||||||
if r.RegionCode == derpRegion {
|
if r.RegionCode == derpRegion {
|
||||||
|
@ -81,7 +81,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+
|
tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+
|
||||||
tailscale.com/derp from tailscale.com/derp/derphttp+
|
tailscale.com/derp from tailscale.com/derp/derphttp+
|
||||||
tailscale.com/derp/derphttp from tailscale.com/net/netcheck+
|
tailscale.com/derp/derphttp from tailscale.com/net/netcheck+
|
||||||
tailscale.com/derp/derpmap from tailscale.com/cmd/tailscaled
|
|
||||||
tailscale.com/disco from tailscale.com/derp+
|
tailscale.com/disco from tailscale.com/derp+
|
||||||
tailscale.com/health from tailscale.com/control/controlclient+
|
tailscale.com/health from tailscale.com/control/controlclient+
|
||||||
tailscale.com/hostinfo from tailscale.com/control/controlclient+
|
tailscale.com/hostinfo from tailscale.com/control/controlclient+
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package derpmap contains information about Tailscale.com's production DERP nodes.
|
|
||||||
//
|
|
||||||
// This package is only used by the "tailscale netcheck" command for debugging.
|
|
||||||
// In normal operation the Tailscale nodes get this sent to them from the control
|
|
||||||
// server.
|
|
||||||
//
|
|
||||||
// TODO: remove this package and make "tailscale netcheck" get the
|
|
||||||
// list from the control server too.
|
|
||||||
package derpmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"tailscale.com/tailcfg"
|
|
||||||
)
|
|
||||||
|
|
||||||
func derpNode(suffix, v4, v6 string) *tailcfg.DERPNode {
|
|
||||||
return &tailcfg.DERPNode{
|
|
||||||
Name: suffix, // updated later
|
|
||||||
RegionID: 0, // updated later
|
|
||||||
IPv4: v4,
|
|
||||||
IPv6: v6,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func derpRegion(id int, code, name string, nodes ...*tailcfg.DERPNode) *tailcfg.DERPRegion {
|
|
||||||
region := &tailcfg.DERPRegion{
|
|
||||||
RegionID: id,
|
|
||||||
RegionName: name,
|
|
||||||
RegionCode: code,
|
|
||||||
Nodes: nodes,
|
|
||||||
}
|
|
||||||
for _, n := range nodes {
|
|
||||||
n.Name = fmt.Sprintf("%d%s", id, n.Name)
|
|
||||||
n.RegionID = id
|
|
||||||
n.HostName = fmt.Sprintf("derp%s.tailscale.com", strings.TrimSuffix(n.Name, "a"))
|
|
||||||
}
|
|
||||||
return region
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prod returns Tailscale's map of relay servers.
|
|
||||||
//
|
|
||||||
// This list is only used by cmd/tailscale's netcheck subcommand. In
|
|
||||||
// normal operation the Tailscale nodes get this sent to them from the
|
|
||||||
// control server.
|
|
||||||
//
|
|
||||||
// This list is subject to change and should not be relied on.
|
|
||||||
func Prod() *tailcfg.DERPMap {
|
|
||||||
return &tailcfg.DERPMap{
|
|
||||||
Regions: map[int]*tailcfg.DERPRegion{
|
|
||||||
1: derpRegion(1, "nyc", "New York City",
|
|
||||||
derpNode("a", "159.89.225.99", "2604:a880:400:d1::828:b001"),
|
|
||||||
),
|
|
||||||
2: derpRegion(2, "sfo", "San Francisco",
|
|
||||||
derpNode("a", "167.172.206.31", "2604:a880:2:d1::c5:7001"),
|
|
||||||
),
|
|
||||||
3: derpRegion(3, "sin", "Singapore",
|
|
||||||
derpNode("a", "68.183.179.66", "2400:6180:0:d1::67d:8001"),
|
|
||||||
),
|
|
||||||
4: derpRegion(4, "fra", "Frankfurt",
|
|
||||||
derpNode("a", "167.172.182.26", "2a03:b0c0:3:e0::36e:9001"),
|
|
||||||
),
|
|
||||||
5: derpRegion(5, "syd", "Sydney",
|
|
||||||
derpNode("a", "103.43.75.49", "2001:19f0:5801:10b7:5400:2ff:feaa:284c"),
|
|
||||||
),
|
|
||||||
6: derpRegion(6, "blr", "Bangalore",
|
|
||||||
derpNode("a", "68.183.90.120", "2400:6180:100:d0::982:d001"),
|
|
||||||
),
|
|
||||||
7: derpRegion(7, "tok", "Tokyo",
|
|
||||||
derpNode("a", "167.179.89.145", "2401:c080:1000:467f:5400:2ff:feee:22aa"),
|
|
||||||
),
|
|
||||||
8: derpRegion(8, "lhr", "London",
|
|
||||||
derpNode("a", "167.71.139.179", "2a03:b0c0:1:e0::3cc:e001"),
|
|
||||||
),
|
|
||||||
9: derpRegion(9, "dfw", "Dallas",
|
|
||||||
derpNode("a", "207.148.3.137", "2001:19f0:6401:1d9c:5400:2ff:feef:bb82"),
|
|
||||||
),
|
|
||||||
10: derpRegion(10, "sea", "Seattle",
|
|
||||||
derpNode("a", "137.220.36.168", "2001:19f0:8001:2d9:5400:2ff:feef:bbb1"),
|
|
||||||
),
|
|
||||||
11: derpRegion(11, "sao", "São Paulo",
|
|
||||||
derpNode("a", "18.230.97.74", "2600:1f1e:ee4:5611:ec5c:1736:d43b:a454"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
@ -2752,3 +2752,13 @@ func (b *LocalBackend) PeerDialControlFunc() func(network, address string, c sys
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DERPMap returns the current DERPMap in use, or nil if not connected.
|
||||||
|
func (b *LocalBackend) DERPMap() *tailcfg.DERPMap {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
if b.netMap == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return b.netMap.DERPMap
|
||||||
|
}
|
||||||
|
@ -102,6 +102,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.serveFileTargets(w, r)
|
h.serveFileTargets(w, r)
|
||||||
case "/localapi/v0/set-dns":
|
case "/localapi/v0/set-dns":
|
||||||
h.serveSetDNS(w, r)
|
h.serveSetDNS(w, r)
|
||||||
|
case "/localapi/v0/derpmap":
|
||||||
|
h.serveDERPMap(w, r)
|
||||||
case "/":
|
case "/":
|
||||||
io.WriteString(w, "tailscaled\n")
|
io.WriteString(w, "tailscaled\n")
|
||||||
default:
|
default:
|
||||||
@ -403,6 +405,17 @@ func (h *Handler) serveSetDNS(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(struct{}{})
|
json.NewEncoder(w).Encode(struct{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) serveDERPMap(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "GET" {
|
||||||
|
http.Error(w, "want GET", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
e := json.NewEncoder(w)
|
||||||
|
e.SetIndent("", "\t")
|
||||||
|
e.Encode(h.b.DERPMap())
|
||||||
|
}
|
||||||
|
|
||||||
var dialPeerTransportOnce struct {
|
var dialPeerTransportOnce struct {
|
||||||
sync.Once
|
sync.Once
|
||||||
v *http.Transport
|
v *http.Transport
|
||||||
|
@ -28,7 +28,6 @@
|
|||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
"golang.org/x/crypto/nacl/box"
|
"golang.org/x/crypto/nacl/box"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/derp/derpmap"
|
|
||||||
"tailscale.com/smallzstd"
|
"tailscale.com/smallzstd"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
@ -616,8 +615,6 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey tailcfg.M
|
|||||||
KeepAlive: true,
|
KeepAlive: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var prodDERPMap = derpmap.Prod()
|
|
||||||
|
|
||||||
// MapResponse generates a MapResponse for a MapRequest.
|
// MapResponse generates a MapResponse for a MapRequest.
|
||||||
//
|
//
|
||||||
// No updates to s are done here.
|
// No updates to s are done here.
|
||||||
@ -627,14 +624,10 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse,
|
|||||||
// node key rotated away (once test server supports that)
|
// node key rotated away (once test server supports that)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
derpMap := s.DERPMap
|
|
||||||
if derpMap == nil {
|
|
||||||
derpMap = prodDERPMap
|
|
||||||
}
|
|
||||||
user, _ := s.getUser(req.NodeKey)
|
user, _ := s.getUser(req.NodeKey)
|
||||||
res = &tailcfg.MapResponse{
|
res = &tailcfg.MapResponse{
|
||||||
Node: node,
|
Node: node,
|
||||||
DERPMap: derpMap,
|
DERPMap: s.DERPMap,
|
||||||
Domain: string(user.Domain),
|
Domain: string(user.Domain),
|
||||||
CollectServices: "true",
|
CollectServices: "true",
|
||||||
PacketFilter: tailcfg.FilterAllowAll,
|
PacketFilter: tailcfg.FilterAllowAll,
|
||||||
|
@ -32,7 +32,6 @@
|
|||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/derp"
|
"tailscale.com/derp"
|
||||||
"tailscale.com/derp/derphttp"
|
"tailscale.com/derp/derphttp"
|
||||||
"tailscale.com/derp/derpmap"
|
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/stun/stuntest"
|
"tailscale.com/net/stun/stuntest"
|
||||||
"tailscale.com/net/tstun"
|
"tailscale.com/net/tstun"
|
||||||
@ -396,7 +395,19 @@ func TestPickDERPFallback(t *testing.T) {
|
|||||||
tstest.ResourceCheck(t)
|
tstest.ResourceCheck(t)
|
||||||
|
|
||||||
c := newConn()
|
c := newConn()
|
||||||
c.derpMap = derpmap.Prod()
|
dm := &tailcfg.DERPMap{
|
||||||
|
Regions: map[int]*tailcfg.DERPRegion{
|
||||||
|
1: &tailcfg.DERPRegion{},
|
||||||
|
2: &tailcfg.DERPRegion{},
|
||||||
|
3: &tailcfg.DERPRegion{},
|
||||||
|
4: &tailcfg.DERPRegion{},
|
||||||
|
5: &tailcfg.DERPRegion{},
|
||||||
|
6: &tailcfg.DERPRegion{},
|
||||||
|
7: &tailcfg.DERPRegion{},
|
||||||
|
8: &tailcfg.DERPRegion{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.derpMap = dm
|
||||||
a := c.pickDERPFallback()
|
a := c.pickDERPFallback()
|
||||||
if a == 0 {
|
if a == 0 {
|
||||||
t.Fatalf("pickDERPFallback returned 0")
|
t.Fatalf("pickDERPFallback returned 0")
|
||||||
@ -415,7 +426,7 @@ func TestPickDERPFallback(t *testing.T) {
|
|||||||
got := map[int]int{}
|
got := map[int]int{}
|
||||||
for i := 0; i < 50; i++ {
|
for i := 0; i < 50; i++ {
|
||||||
c = newConn()
|
c = newConn()
|
||||||
c.derpMap = derpmap.Prod()
|
c.derpMap = dm
|
||||||
got[c.pickDERPFallback()]++
|
got[c.pickDERPFallback()]++
|
||||||
}
|
}
|
||||||
t.Logf("distribution: %v", got)
|
t.Logf("distribution: %v", got)
|
||||||
|
Loading…
Reference in New Issue
Block a user