diff --git a/cmd/derpprobe/derpprobe.go b/cmd/derpprobe/derpprobe.go index 897be74cc..117d73582 100644 --- a/cmd/derpprobe/derpprobe.go +++ b/cmd/derpprobe/derpprobe.go @@ -20,7 +20,7 @@ import ( ) var ( - derpMapURL = flag.String("derp-map", "https://login.tailscale.com/derpmap/default", "URL to DERP map (https:// or file://)") + derpMapURL = flag.String("derp-map", "https://login.tailscale.com/derpmap/default", "URL to DERP map (https:// or file://) or 'local' to use the local tailscaled's DERP map") versionFlag = flag.Bool("version", false, "print version and exit") listen = flag.String("listen", ":8030", "HTTP listen address") probeOnce = flag.Bool("once", false, "probe once and print results, then exit; ignores the listen flag") diff --git a/prober/derp.go b/prober/derp.go index 338782048..60a31a1bf 100644 --- a/prober/derp.go +++ b/prober/derp.go @@ -21,6 +21,7 @@ import ( "time" "github.com/prometheus/client_golang/prometheus" + "tailscale.com/client/tailscale" "tailscale.com/derp" "tailscale.com/derp/derphttp" "tailscale.com/net/netmon" @@ -35,7 +36,7 @@ import ( // based on the current DERPMap. type derpProber struct { p *Prober - derpMapURL string + derpMapURL string // or "local" udpInterval time.Duration meshInterval time.Duration tlsInterval time.Duration @@ -97,6 +98,9 @@ func WithTLSProbing(interval time.Duration) DERPOpt { } // DERP creates a new derpProber. +// +// If derpMapURL is "local", the DERPMap is fetched via +// the local machine's tailscaled. func DERP(p *Prober, derpMapURL string, opts ...DERPOpt) (*derpProber, error) { d := &derpProber{ p: p, @@ -268,31 +272,42 @@ func (d *derpProber) getNodePair(n1, n2 string) (ret1, ret2 *tailcfg.DERPNode, _ return ret1, ret2, nil } +var tsLocalClient tailscale.LocalClient + // updateMap refreshes the locally-cached DERP map. func (d *derpProber) updateMap(ctx context.Context) error { - req, err := http.NewRequestWithContext(ctx, "GET", d.derpMapURL, nil) - if err != nil { - return nil - } - res, err := httpOrFileClient.Do(req) - if err != nil { - d.Lock() - defer d.Unlock() - if d.lastDERPMap != nil && time.Since(d.lastDERPMapAt) < 10*time.Minute { - log.Printf("Error while fetching DERP map, using cached one: %s", err) - // Assume that control is restarting and use - // the same one for a bit. + var dm *tailcfg.DERPMap + if d.derpMapURL == "local" { + var err error + dm, err = tsLocalClient.CurrentDERPMap(ctx) + if err != nil { + return err + } + } else { + req, err := http.NewRequestWithContext(ctx, "GET", d.derpMapURL, nil) + if err != nil { return nil } - return err - } - defer res.Body.Close() - if res.StatusCode != 200 { - return fmt.Errorf("fetching %s: %s", d.derpMapURL, res.Status) - } - dm := new(tailcfg.DERPMap) - if err := json.NewDecoder(res.Body).Decode(dm); err != nil { - return fmt.Errorf("decoding %s JSON: %v", d.derpMapURL, err) + res, err := httpOrFileClient.Do(req) + if err != nil { + d.Lock() + defer d.Unlock() + if d.lastDERPMap != nil && time.Since(d.lastDERPMapAt) < 10*time.Minute { + log.Printf("Error while fetching DERP map, using cached one: %s", err) + // Assume that control is restarting and use + // the same one for a bit. + return nil + } + return err + } + defer res.Body.Close() + if res.StatusCode != 200 { + return fmt.Errorf("fetching %s: %s", d.derpMapURL, res.Status) + } + dm = new(tailcfg.DERPMap) + if err := json.NewDecoder(res.Body).Decode(dm); err != nil { + return fmt.Errorf("decoding %s JSON: %v", d.derpMapURL, err) + } } d.Lock()