From 874be6566db0f5747cbc24a3c9a5070f3eeb9f9e Mon Sep 17 00:00:00 2001 From: halulu Date: Mon, 11 May 2020 11:23:09 -0400 Subject: [PATCH] netcheck: DERP latency over HTTPS when UDP is blocked * netcheck: DERP letency over HTTPS when UDP failed Updates #207 Signed-off-by: Zijie Lu * netcheck: async DERP latency check over HTTPS Updates #207 Signed-off-by: Zijie Lu * netcheck: DERP latency check over HTTPS: fix concurrent map Updates #207 Signed-off-by: Zijie Lu * netcheck: DERP latency check over HTTPS: some improvements Updates #207 Signed-off-by: Zijie Lu * netcheck: DERP latency check over HTTPS: use timeout context Updates #207 Signed-off-by: Zijie Lu * netcheck: DERP latency check over HTTPS: use report mutex Updates #207 Signed-off-by: Zijie Lu * netcheck: DERP latency check over HTTPS if UDP is BLOCKED Updates #207 Signed-off-by: Zijie Lu * netcheck: DERP latency check over HTTPS: new function measureHTTPSLatency Updates #207 Signed-off-by: Zijie Lu --- go.mod | 1 + go.sum | 2 ++ netcheck/netcheck.go | 68 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 3a4cc3339..7721be307 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/peterbourgon/ff/v2 v2.0.0 github.com/tailscale/winipcfg-go v0.0.0-20200413171540-609dcf2df55f github.com/tailscale/wireguard-go v0.0.0-20200424121617-8d10f231531a + github.com/tcnksm/go-httpstat v0.2.0 github.com/toqueteos/webbrowser v1.2.0 go4.org/mem v0.0.0-20200411205429-f77f31c81751 golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 diff --git a/go.sum b/go.sum index cfac605b3..9bb08fe67 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/tailscale/winipcfg-go v0.0.0-20200413171540-609dcf2df55f h1:uFj5bslHs github.com/tailscale/winipcfg-go v0.0.0-20200413171540-609dcf2df55f/go.mod h1:x880GWw5fvrl2DVTQ04ttXQD4DuppTt1Yz6wLibbjNE= github.com/tailscale/wireguard-go v0.0.0-20200424121617-8d10f231531a h1:HMkTFyhcvZaKf7+7T76rks4HqB83fptUemBIfLGI6TM= github.com/tailscale/wireguard-go v0.0.0-20200424121617-8d10f231531a/go.mod h1:JPm5cTfu1K+qDFRbiHy0sOlHUylYQbpl356sdYFD8V4= +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= diff --git a/netcheck/netcheck.go b/netcheck/netcheck.go index fa97e8995..bbd1f163b 100644 --- a/netcheck/netcheck.go +++ b/netcheck/netcheck.go @@ -11,12 +11,15 @@ import ( "errors" "fmt" "io" + "io/ioutil" "log" "net" + "net/http" "sort" "sync" "time" + "github.com/tcnksm/go-httpstat" "golang.org/x/sync/errgroup" "tailscale.com/derp/derpmap" "tailscale.com/net/dnscache" @@ -442,8 +445,6 @@ func (c *Client) GetReport(ctx context.Context) (*Report, error) { } mu.Lock() - defer mu.Unlock() - // Check hairpinning. if ret.MappingVariesByDestIP == "false" && gotEP4 != "" { select { @@ -453,12 +454,32 @@ func (c *Client) GetReport(ctx context.Context) (*Report, error) { ret.HairPinning.Set(false) } } + mu.Unlock() + + // Try HTTPS latency check if UDP is blocked and all checkings failed + if !anyV4() { + c.logf("netcheck: UDP is blocked, try HTTPS") + var wg sync.WaitGroup + for _, server := range stuns4 { + server := server + if _, ok := ret.DERPLatency[server]; ok { + continue + } - // TODO: if UDP is blocked, try to measure TCP connect times - // to DERP nodes instead? So UDP-blocked users still get a - // decent DERP node, rather than being randomly assigned to - // the other side of the planet? Or try ICMP? (likely also - // blocked?) + wg.Add(1) + go func() { + defer wg.Done() + if d, err := c.measureHTTPSLatency(server); err != nil { + c.logf("netcheck: measuring HTTPS latency of %v: %v", server, err) + } else { + mu.Lock() + ret.DERPLatency[server] = d + mu.Unlock() + } + }() + } + wg.Wait() + } report := ret.Clone() @@ -468,6 +489,39 @@ func (c *Client) GetReport(ctx context.Context) (*Report, error) { return report, nil } +func (c *Client) measureHTTPSLatency(server string) (time.Duration, error) { + host, _, err := net.SplitHostPort(server) + if err != nil { + return 0, err + } + + var result httpstat.Result + hctx, cancel := context.WithTimeout(httpstat.WithHTTPStat(context.Background(), &result), 5*time.Second) + defer cancel() + + u := fmt.Sprintf("https://%s/derp/latency-check", host) + req, err := http.NewRequestWithContext(hctx, "GET", u, nil) + if err != nil { + return 0, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return 0, err + } + defer resp.Body.Close() + + _, err = io.Copy(ioutil.Discard, resp.Body) + if err != nil { + return 0, err + } + result.End(c.timeNow()) + + // TODO: decide best timing heuristic here. + // Maybe the server should return the tcpinfo_rtt? + return result.ServerProcessing, nil +} + func (c *Client) logConciseReport(r *Report) { buf := bytes.NewBuffer(make([]byte, 0, 256)) // empirically: 5 DERPs + IPv6 == ~233 bytes fmt.Fprintf(buf, "udp=%v", r.UDP)