| 166 | } |
| 167 | |
| 168 | func waitForDirectConnection(ctx context.Context, logs io.Writer, conn workspacesdk.AgentConn) error { |
| 169 | const directConnectionAttempts = 30 |
| 170 | const directConnectionDelay = 1 * time.Second |
| 171 | |
| 172 | ctx, span := tracing.StartSpan(ctx) |
| 173 | defer span.End() |
| 174 | |
| 175 | for i := 0; i < directConnectionAttempts; i++ { |
| 176 | _, _ = fmt.Fprintf(logs, "\tDirect connection check %d/%d...\n", i+1, directConnectionAttempts) |
| 177 | status := conn.TailnetConn().Status() |
| 178 | |
| 179 | var err error |
| 180 | if len(status.Peers()) != 1 { |
| 181 | _, _ = fmt.Fprintf(logs, "\t\tExpected 1 peer, found %d\n", len(status.Peers())) |
| 182 | err = xerrors.Errorf("expected 1 peer, got %d", len(status.Peers())) |
| 183 | } else { |
| 184 | peer := status.Peer[status.Peers()[0]] |
| 185 | _, _ = fmt.Fprintf(logs, "\t\tCurAddr: %s\n", peer.CurAddr) |
| 186 | _, _ = fmt.Fprintf(logs, "\t\tRelay: %s\n", peer.Relay) |
| 187 | if peer.Relay != "" && peer.CurAddr == "" { |
| 188 | err = xerrors.Errorf("peer is connected via DERP, not direct") |
| 189 | } |
| 190 | } |
| 191 | if err == nil { |
| 192 | break |
| 193 | } |
| 194 | if i == directConnectionAttempts-1 { |
| 195 | return xerrors.Errorf("wait for direct connection to agent: %w", err) |
| 196 | } |
| 197 | |
| 198 | select { |
| 199 | case <-ctx.Done(): |
| 200 | return xerrors.Errorf("wait for direct connection to agent: %w", ctx.Err()) |
| 201 | // We use time.After here since it's a very short duration so |
| 202 | // leaking a timer is fine. |
| 203 | case <-time.After(directConnectionDelay): |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | return nil |
| 208 | } |
| 209 | |
| 210 | func verifyConnection(ctx context.Context, logs io.Writer, conn workspacesdk.AgentConn) error { |
| 211 | const verifyConnectionAttempts = 30 |