|
|
@ -11,12 +11,15 @@ import (
|
|
|
|
"errors"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"net"
|
|
|
|
|
|
|
|
"net/http"
|
|
|
|
"sort"
|
|
|
|
"sort"
|
|
|
|
"sync"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/tcnksm/go-httpstat"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"tailscale.com/derp/derpmap"
|
|
|
|
"tailscale.com/derp/derpmap"
|
|
|
|
"tailscale.com/net/dnscache"
|
|
|
|
"tailscale.com/net/dnscache"
|
|
|
@ -442,8 +445,6 @@ func (c *Client) GetReport(ctx context.Context) (*Report, error) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mu.Lock()
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check hairpinning.
|
|
|
|
// Check hairpinning.
|
|
|
|
if ret.MappingVariesByDestIP == "false" && gotEP4 != "" {
|
|
|
|
if ret.MappingVariesByDestIP == "false" && gotEP4 != "" {
|
|
|
|
select {
|
|
|
|
select {
|
|
|
@ -453,12 +454,32 @@ func (c *Client) GetReport(ctx context.Context) (*Report, error) {
|
|
|
|
ret.HairPinning.Set(false)
|
|
|
|
ret.HairPinning.Set(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: if UDP is blocked, try to measure TCP connect times
|
|
|
|
// Try HTTPS latency check if UDP is blocked and all checkings failed
|
|
|
|
// to DERP nodes instead? So UDP-blocked users still get a
|
|
|
|
if !anyV4() {
|
|
|
|
// decent DERP node, rather than being randomly assigned to
|
|
|
|
c.logf("netcheck: UDP is blocked, try HTTPS")
|
|
|
|
// the other side of the planet? Or try ICMP? (likely also
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
// blocked?)
|
|
|
|
for _, server := range stuns4 {
|
|
|
|
|
|
|
|
server := server
|
|
|
|
|
|
|
|
if _, ok := ret.DERPLatency[server]; ok {
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
report := ret.Clone()
|
|
|
|
|
|
|
|
|
|
|
@ -468,6 +489,39 @@ func (c *Client) GetReport(ctx context.Context) (*Report, error) {
|
|
|
|
return report, nil
|
|
|
|
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) {
|
|
|
|
func (c *Client) logConciseReport(r *Report) {
|
|
|
|
buf := bytes.NewBuffer(make([]byte, 0, 256)) // empirically: 5 DERPs + IPv6 == ~233 bytes
|
|
|
|
buf := bytes.NewBuffer(make([]byte, 0, 256)) // empirically: 5 DERPs + IPv6 == ~233 bytes
|
|
|
|
fmt.Fprintf(buf, "udp=%v", r.UDP)
|
|
|
|
fmt.Fprintf(buf, "udp=%v", r.UDP)
|
|
|
|