From 814375c127b97f8d6f31f1a1fc5c68117d18fc57 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 15 Apr 2022 09:00:27 -0700 Subject: [PATCH] control/controlclient: finish wiring up PingRequest TSMP support Most of the code existed already but wasn't connected between two spots. Updates tailscale/corp#754 Change-Id: I41c3386c4194bdcaabf4ce6b96eb6499ec0a513c Signed-off-by: Brad Fitzpatrick --- control/controlclient/direct.go | 63 ++++++++++++++++++--------------- tailcfg/tailcfg.go | 3 +- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index e84ee68d6..f08d2b4e2 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -850,7 +850,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm if pr := resp.PingRequest; pr != nil && c.isUniquePingRequest(pr) { metricMapResponsePings.Add(1) - go answerPing(c.logf, c.httpc, pr) + go answerPing(c.logf, c.httpc, pr, c.pinger) } if u := resp.PopBrowserURL; u != "" && u != sess.lastPopBrowserURL { sess.lastPopBrowserURL = u @@ -1181,29 +1181,42 @@ func (c *Direct) isUniquePingRequest(pr *tailcfg.PingRequest) bool { return true } -func answerPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest) { +func answerPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, pinger Pinger) { if pr.URL == "" { logf("invalid PingRequest with no URL") return } + if pr.Types == "" { + answerHeadPing(logf, c, pr) + return + } + for _, t := range strings.Split(pr.Types, ",") { + switch t { + case "TSMP": + go tsmpPing(logf, c, pr, pinger) + } + } +} + +func answerHeadPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest) { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx, "HEAD", pr.URL, nil) if err != nil { - logf("http.NewRequestWithContext(%q): %v", pr.URL, err) + logf("answerHeadPing: NewRequestWithContext: %v", err) return } if pr.Log { - logf("answerPing: sending ping to %v ...", pr.URL) + logf("answerHeadPing: sending HEAD ping to %v ...", pr.URL) } t0 := time.Now() _, err = c.Do(req) d := time.Since(t0).Round(time.Millisecond) if err != nil { - logf("answerPing error: %v to %v (after %v)", err, pr.URL, d) + logf("answerHeadPing error: %v to %v (after %v)", err, pr.URL, d) } else if pr.Log { - logf("answerPing complete to %v (after %v)", pr.URL, d) + logf("answerHeadPing complete to %v (after %v)", pr.URL, d) } } @@ -1378,33 +1391,25 @@ func (c *Direct) DoNoiseRequest(req *http.Request) (*http.Response, error) { // tsmpPing sends a Ping to pr.IP, and sends an http request back to pr.URL // with ping response data. -func tsmpPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, pinger Pinger) error { - var err error - if pr.URL == "" { - return errors.New("invalid PingRequest with no URL") +func tsmpPing(logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, pinger Pinger) { + if pr.URL == "" || pr.IP.IsZero() || pinger == nil { + return } - if pr.IP.IsZero() { - return errors.New("PingRequest without IP") - } - if !strings.Contains(pr.Types, "TSMP") { - return fmt.Errorf("PingRequest with no TSMP in Types, got %q", pr.Types) - } - - now := time.Now() + start := time.Now() pinger.Ping(pr.IP, true, func(res *ipnstate.PingResult) { // Currently does not check for error since we just return if it fails. - err = postPingResult(now, logf, c, pr, res) + postPingResult(start, logf, c, pr, res) }) - return err } -func postPingResult(now time.Time, logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, res *ipnstate.PingResult) error { - if res.Err != "" { - return errors.New(res.Err) - } - duration := time.Since(now) +func postPingResult(start time.Time, logf logger.Logf, c *http.Client, pr *tailcfg.PingRequest, res *ipnstate.PingResult) error { + duration := time.Since(start).Round(time.Millisecond) if pr.Log { - logf("TSMP ping to %v completed in %v seconds. pinger.Ping took %v seconds", pr.IP, res.LatencySeconds, duration.Seconds()) + if res.Err == "" { + logf("TSMP ping to %v completed in %v. pinger.Ping took %v seconds", pr.IP, res.LatencySeconds, duration) + } else { + logf("TSMP ping to %v failed after %v: %v", pr.IP, duration, res.Err) + } } ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() @@ -1414,7 +1419,7 @@ func postPingResult(now time.Time, logf logger.Logf, c *http.Client, pr *tailcfg return err } // Send the results of the Ping, back to control URL. - req, err := http.NewRequestWithContext(ctx, "POST", pr.URL, bytes.NewBuffer(jsonPingRes)) + req, err := http.NewRequestWithContext(ctx, "POST", pr.URL, bytes.NewReader(jsonPingRes)) if err != nil { return fmt.Errorf("http.NewRequestWithContext(%q): %w", pr.URL, err) } @@ -1425,9 +1430,9 @@ func postPingResult(now time.Time, logf logger.Logf, c *http.Client, pr *tailcfg _, err = c.Do(req) d := time.Since(t0).Round(time.Millisecond) if err != nil { - return fmt.Errorf("tsmpPing error: %w to %v (after %v)", err, pr.URL, d) + return fmt.Errorf("postPingResult error: %w to %v (after %v)", err, pr.URL, d) } else if pr.Log { - logf("tsmpPing complete to %v (after %v)", pr.URL, d) + logf("postPingResult complete to %v (after %v)", pr.URL, d) } return nil } diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 33f89dbc5..a27818ceb 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -67,7 +67,8 @@ type CapabilityVersion int // 28: 2022-03-09: client can communicate over Noise. // 29: 2022-03-21: MapResponse.PopBrowserURL // 30: 2022-03-22: client can request id tokens. -const CurrentCapabilityVersion CapabilityVersion = 30 +// 31: 2022-04-15: PingRequest TSMP support +const CurrentCapabilityVersion CapabilityVersion = 31 type StableID string