diff --git a/cmd/tailcat/perf.go b/cmd/tailcat/perf.go index dc50e8970..565740794 100644 --- a/cmd/tailcat/perf.go +++ b/cmd/tailcat/perf.go @@ -191,6 +191,7 @@ func probePath(ctx context.Context, cl *tailcat.Client) (pathInfo, error) { // discovery along. func waitForDirectPath(ctx context.Context, cl *tailcat.Client, timeout time.Duration) (pathInfo, error) { deadline := time.Now().Add(timeout) + var last *pathInfo for { t0 := time.Now() pingCtx, cancel := context.WithDeadline(ctx, deadline) @@ -198,6 +199,9 @@ func waitForDirectPath(ctx context.Context, cl *tailcat.Client, timeout time.Dur cancel() if err != nil { if ctx.Err() == nil && errors.Is(err, context.DeadlineExceeded) { + if last != nil { + return *last, nil + } return pathInfo{}, fmt.Errorf("no reply to pings after %v", timeout) } return pathInfo{}, err @@ -205,6 +209,7 @@ func waitForDirectPath(ctx context.Context, cl *tailcat.Client, timeout time.Dur if p.Direct || time.Until(deadline) < time.Second/2 { return p, nil } + last = &p select { case <-ctx.Done(): return pathInfo{}, ctx.Err() diff --git a/cmd/tailcat/perf_test.go b/cmd/tailcat/perf_test.go index 243a07421..9e365dcf8 100644 --- a/cmd/tailcat/perf_test.go +++ b/cmd/tailcat/perf_test.go @@ -6,6 +6,7 @@ package main import ( "encoding/json" "regexp" + "slices" "strings" "testing" "time" @@ -97,6 +98,33 @@ func TestPerf(t *testing.T) { t.Errorf("no RTT samples in %+v", got.Result) } }) + + // A path that stays relayed must still come back from the wait for + // a direct path, whatever --timeout is. Whole seconds used to fail: + // the last once-a-second ping saw about 1s left, slept past the + // deadline, and reported "no reply to pings". + t.Run("relayed", func(t *testing.T) { + relayedPerf := func(args ...string) ([]byte, error) { + all := append([]string{"--key=new", "--derpmap-url=" + e.derpMapURL, "perf", "--timeout=2s", "--time=1s", "--interval=0"}, args...) + all = append(all, addr) + cmd := e.cmd(all...) + cmd.Env = append(slices.Clone(cmd.Env), "TS_DEBUG_ALWAYS_USE_DERP=1") + return cmd.CombinedOutput() + } + + out, err := relayedPerf("--via-derp") + if err != nil { + t.Fatalf("perf --via-derp: %v\n%s", err, out) + } + if !regexp.MustCompile(`(?m)^# path: relayed via DERP\(`).Match(out) { + t.Errorf("output missing relayed path:\n%s", out) + } + + out, err = relayedPerf() + if err == nil || !strings.Contains(string(out), "no direct path to the server") { + t.Errorf("perf without --via-derp: err=%v, want a no-direct-path refusal:\n%s", err, out) + } + }) } // TestPerfServeRejectsPortClash checks that a port list can't proxy