diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go index db56c4a44..347f1fd7f 100644 --- a/derp/derphttp/derphttp_client.go +++ b/derp/derphttp/derphttp_client.go @@ -386,8 +386,6 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien } }() - var node *tailcfg.DERPNode // nil when using c.url to dial - var idealNodeInRegion bool switch { case canWebsockets && useWebsockets(): var urlStr string @@ -425,15 +423,45 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien case c.url != nil: c.logf("%s: connecting to %v", caller, c.url) tcpConn, err = c.dialURL(ctx) + return c.connectWithConn(ctx, reg, nil, tcpConn) default: c.logf("%s: connecting to derp-%d (%v)", caller, reg.RegionID, reg.RegionCode) - tcpConn, node, err = c.dialRegion(ctx, reg) - idealNodeInRegion = err == nil && reg.Nodes[0] == node - } - if err != nil { - return nil, 0, err + if len(reg.Nodes) == 0 { + return nil, 0, fmt.Errorf("no nodes for %s", c.targetString(reg)) + } + var firstErr error + var derpClient *derp.Client + for _, n := range reg.Nodes { + if n.STUNOnly { + if firstErr == nil { + firstErr = fmt.Errorf("no non-STUNOnly nodes for %s", c.targetString(reg)) + } + continue + } + tcpConn, err = c.dialNode(ctx, n) + if err != nil { + if firstErr == nil { + firstErr = err + } + continue + } + derpClient, connGen, err = c.connectWithConn(ctx, reg, n, tcpConn) + if err != nil { + if firstErr == nil { + firstErr = err + } + if tcpConn != nil { + go tcpConn.Close() + } + continue + } + return derpClient, connGen, nil + } + return nil, 0, firstErr } +} +func (c *Client) connectWithConn(ctx context.Context, reg *tailcfg.DERPRegion, node *tailcfg.DERPNode, tcpConn net.Conn) (client *derp.Client, connGen int, err error) { // Now that we have a TCP connection, force close it if the // TLS handshake + DERP setup takes too long. done := make(chan struct{}) @@ -502,7 +530,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien } req.Header.Set("Upgrade", "DERP") req.Header.Set("Connection", "Upgrade") - if !idealNodeInRegion && reg != nil { + if reg != nil && reg.Nodes[0] != node { // This is purely informative for now (2024-07-06) for stats: req.Header.Set(derp.IdealNodeHeader, reg.Nodes[0].Name) // TODO(bradfitz,raggi): start a time.AfterFunc for 30m-1h or so to