Skip to content

Commit 75df537

Browse files
committed
pkg/driver/qemu: Wait for SSH to be ready in AdditionalSetupForSSH()
- pkg/sshutil: Add `WaitSSHReady()` `WaitSSHReady` waits until the SSH port is ready to accept connections. The `dialContext` function is used to create a connection to the SSH server. The `addressForLogging` parameter is used for logging purposes. The `timeoutSeconds` parameter specifies the maximum number of seconds to wait. Signed-off-by: Norio Nomura <[email protected]>
1 parent 6ead149 commit 75df537

File tree

3 files changed

+54
-21
lines changed

3 files changed

+54
-21
lines changed

pkg/driver/qemu/qemu_driver.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/lima-vm/lima/v2/pkg/osutil"
3838
"github.com/lima-vm/lima/v2/pkg/ptr"
3939
"github.com/lima-vm/lima/v2/pkg/reflectutil"
40+
"github.com/lima-vm/lima/v2/pkg/sshutil"
4041
"github.com/lima-vm/lima/v2/pkg/version/versionutil"
4142
)
4243

@@ -721,6 +722,15 @@ func (l *LimaQemuDriver) ForwardGuestAgent() bool {
721722
return l.vSockPort == 0 && l.virtioPort == ""
722723
}
723724

724-
func (l *LimaQemuDriver) AdditionalSetupForSSH(_ context.Context) error {
725+
func (l *LimaQemuDriver) AdditionalSetupForSSH(ctx context.Context) error {
726+
// Wait until the port is available.
727+
addr := net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", l.SSHLocalPort))
728+
dialContext := func(ctx context.Context) (net.Conn, error) {
729+
dialer := net.Dialer{Timeout: 1 * time.Second}
730+
return dialer.DialContext(ctx, "tcp", addr)
731+
}
732+
if err := sshutil.WaitSSHReady(ctx, dialContext, addr, 600); err != nil {
733+
return err
734+
}
725735
return nil
726736
}

pkg/networks/usernet/gvproxy.go

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"github.com/containers/gvisor-tap-vsock/pkg/virtualnetwork"
2323
"github.com/sirupsen/logrus"
2424
"golang.org/x/sync/errgroup"
25+
26+
"github.com/lima-vm/lima/v2/pkg/sshutil"
2527
)
2628

2729
type GVisorNetstackOpts struct {
@@ -267,27 +269,14 @@ func muxWithExtension(n *virtualnetwork.VirtualNetwork) *http.ServeMux {
267269
}
268270
timeoutSeconds = int(timeout16)
269271
}
270-
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSeconds)*time.Second)
271-
defer cancel()
272+
dialContext := func(ctx context.Context) (net.Conn, error) {
273+
return n.DialContextTCP(ctx, addr)
274+
}
272275
// Wait until the port is available.
273-
for {
274-
conn, err := n.DialContextTCP(ctx, addr)
275-
if err == nil {
276-
conn.Close()
277-
logrus.Debugf("Port is available on %s", addr)
278-
w.WriteHeader(http.StatusOK)
279-
break
280-
}
281-
select {
282-
case <-ctx.Done():
283-
msg := fmt.Sprintf("timed out waiting for port to become available on %s", addr)
284-
logrus.Warn(msg)
285-
http.Error(w, msg, http.StatusRequestTimeout)
286-
return
287-
default:
288-
}
289-
logrus.Debugf("Waiting for port to become available on %s", addr)
290-
time.Sleep(1 * time.Second)
276+
if err = sshutil.WaitSSHReady(r.Context(), dialContext, addr, timeoutSeconds); err != nil {
277+
http.Error(w, err.Error(), http.StatusRequestTimeout)
278+
} else {
279+
w.WriteHeader(http.StatusOK)
291280
}
292281
})
293282
return m

pkg/sshutil/sshutil.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,3 +608,37 @@ func findRegexpInSSHArgs(sshArgs []string, re *regexp.Regexp) string {
608608
}
609609
return ""
610610
}
611+
612+
// WaitSSHReady waits until the SSH port is ready to accept connections.
613+
// The dialContext function is used to create a connection to the SSH server.
614+
// The addressForLogging parameter is used for logging purposes.
615+
// The timeoutSeconds parameter specifies the maximum number of seconds to wait.
616+
func WaitSSHReady(ctx context.Context, dialContext func(context.Context) (net.Conn, error), addressForLogging string, timeoutSeconds int) error {
617+
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
618+
defer cancel()
619+
// Wait until the SSH port is available.
620+
for {
621+
conn, err := dialContext(ctx)
622+
if err == nil {
623+
// Check if the SSH banner is received.
624+
buf := make([]byte, 128)
625+
if err := conn.SetReadDeadline(time.Now().Add(1 * time.Second)); err != nil {
626+
conn.Close()
627+
return fmt.Errorf("failed to set read deadline on SSH connection to %s: %w", addressForLogging, err)
628+
}
629+
n, err := conn.Read(buf)
630+
conn.Close()
631+
if err == nil && bytes.HasPrefix(buf[:n], []byte("SSH-")) {
632+
logrus.Debugf("SSH port is available on %s", addressForLogging)
633+
break // SSH ready!
634+
}
635+
}
636+
logrus.Debugf("Waiting for SSH port to become available on %s", addressForLogging)
637+
select {
638+
case <-ctx.Done():
639+
return fmt.Errorf("failed to waiting for SSH port to become available on %s: %w", addressForLogging, ctx.Err())
640+
case <-time.After(1 * time.Second):
641+
}
642+
}
643+
return nil
644+
}

0 commit comments

Comments
 (0)