From c9471bb3ad15a5fa4fcbaa7226fff635c6639d9c Mon Sep 17 00:00:00 2001 From: Viktor Oreshkin Date: Mon, 14 Jul 2025 03:59:44 +0300 Subject: [PATCH 1/4] portfwd: create separate gRPC streams for each UDP client The UDP port forwarder previously used a single gRPC stream for all clients, which could cause responses from the guest to be sent to the wrong client on the host. This occurred because the stream was created before client connections were demultiplexed by `gvisor-tap-vsock`'s `UDPProxy`. The root cause is the interaction with `gvisor-tap-vsock`'s `UDPProxy`, which handles client demultiplexing internally based on the source address of incoming datagrams. It expects its `dialer` function to return a new `net.Conn` for each new client it detects. This commit moves the gRPC stream creation into the `UDPProxy` dialer function. This ensures a new, dedicated stream is created for each new client, fixing the incorrect response routing. Signed-off-by: Viktor Oreshkin (cherry picked from commit a04f2447db7127203b64c6f92e608817d6d6f7ef) Signed-off-by: Akihiro Suda --- pkg/portfwd/client.go | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/pkg/portfwd/client.go b/pkg/portfwd/client.go index 81c5c21e17b..944032945a4 100644 --- a/pkg/portfwd/client.go +++ b/pkg/portfwd/client.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "net" + "sync/atomic" "time" "github.com/containers/gvisor-tap-vsock/pkg/services/forwarder" @@ -40,33 +41,35 @@ func HandleTCPConnection(ctx context.Context, client *guestagentclient.GuestAgen } func HandleUDPConnection(ctx context.Context, client *guestagentclient.GuestAgentClient, conn net.PacketConn, guestAddr string) { - id := fmt.Sprintf("udp-%s", conn.LocalAddr().String()) - - stream, err := client.Tunnel(ctx) - if err != nil { - logrus.Errorf("could not open udp tunnel for id: %s error:%v", id, err) - return - } - - // Handshake message to start tunnel - if err := stream.Send(&api.TunnelMessage{Id: id, Protocol: "udp", GuestAddr: guestAddr}); err != nil { - logrus.Errorf("could not start udp tunnel for id: %s error:%v", id, err) - return - } + var udpConnectionCounter atomic.Uint32 + initialID := fmt.Sprintf("udp-%s", conn.LocalAddr().String()) + // gvisor-tap-vsock's UDPProxy demultiplexes client connections internally based on their source address. + // It calls this dialer function only when it receives a datagram from a new, unrecognized client. + // For each new client, we must return a new net.Conn, which in our case is a new gRPC stream. + // The atomic counter ensures that each stream has a unique ID to distinguish them on the server side. proxy, err := forwarder.NewUDPProxy(conn, func() (net.Conn, error) { + id := fmt.Sprintf("%s-%d", initialID, udpConnectionCounter.Add(1)) + stream, err := client.Tunnel(ctx) + if err != nil { + return nil, fmt.Errorf("could not open udp tunnel for id: %s error:%w", id, err) + } + // Handshake message to start tunnel + if err := stream.Send(&api.TunnelMessage{Id: id, Protocol: "udp", GuestAddr: guestAddr}); err != nil { + return nil, fmt.Errorf("could not start udp tunnel for id: %s error:%w", id, err) + } rw := &GrpcClientRW{stream: stream, id: id, addr: guestAddr, protocol: "udp"} return rw, nil }) if err != nil { - logrus.Errorf("error in udp tunnel proxy for id: %s error:%v", id, err) + logrus.Errorf("error in udp tunnel proxy for id: %s error:%v", initialID, err) return } defer func() { err := proxy.Close() if err != nil { - logrus.Errorf("error in closing udp tunnel proxy for id: %s error:%v", id, err) + logrus.Errorf("error in closing udp tunnel proxy for id: %s error:%v", initialID, err) } }() proxy.Run() From 1e224c26129f84d3d4ac77ea90d5a96720467540 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Thu, 17 Jul 2025 13:33:13 +0900 Subject: [PATCH 2/4] qemuimgutil: fix "Failed to get \"write\" lock" error Fix issue 3720 Thanks to pavelanni for providing the fix generated by Claude Co-authored-by: Oleksandr Redko Signed-off-by: Akihiro Suda (cherry picked from commit 2b8bb395026b46816a66966012917cb2b84fb678) Signed-off-by: Akihiro Suda --- pkg/qemuimgutil/qemuimgutil.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/qemuimgutil/qemuimgutil.go b/pkg/qemuimgutil/qemuimgutil.go index fc065450c59..8872eb56d52 100644 --- a/pkg/qemuimgutil/qemuimgutil.go +++ b/pkg/qemuimgutil/qemuimgutil.go @@ -103,6 +103,31 @@ type InfoFormatSpecificDataVmdkExtent struct { } func convertToRaw(source, dest string) error { + if source != dest { + return execQemuImgConvert(source, dest) + } + + // If source == dest, we need to use a temporary file to avoid file locking issues + + info, err := getInfo(source) + if err != nil { + return fmt.Errorf("failed to get info for source disk %q: %w", source, err) + } + if info.Format == "raw" { + return nil + } + + tempFile := dest + ".lima-qemu-convert.tmp" + defer os.Remove(tempFile) + + if err := execQemuImgConvert(source, tempFile); err != nil { + return err + } + + return os.Rename(tempFile, dest) +} + +func execQemuImgConvert(source, dest string) error { var stdout, stderr bytes.Buffer cmd := exec.Command("qemu-img", "convert", "-O", "raw", source, dest) cmd.Stdout = &stdout From b44feb4f45ea25c9edd46cc2fc3925c7071213da Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Fri, 18 Jul 2025 00:24:26 +0900 Subject: [PATCH 3/4] pkg/instance/start.go: fix lint error on windows ``` Error: pkg\instance\start.go:274:65: SA4023: this comparison is always true (staticcheck) if err := syscall.Exec(limactl, haCmd.Args, haCmd.Environ()); err != nil { ``` Signed-off-by: Akihiro Suda (cherry picked from commit bdf7c7f3d7004a7cd8f0698f0eca67558859bde2) > Conflicts: > pkg/instance/start.go Signed-off-by: Akihiro Suda --- pkg/instance/start.go | 23 +---------------- pkg/instance/start_unix.go | 48 +++++++++++++++++++++++++++++++++++ pkg/instance/start_windows.go | 13 ++++++++++ 3 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 pkg/instance/start_unix.go create mode 100644 pkg/instance/start_windows.go diff --git a/pkg/instance/start.go b/pkg/instance/start.go index e465fa2755f..960b1ae1ae9 100644 --- a/pkg/instance/start.go +++ b/pkg/instance/start.go @@ -12,13 +12,11 @@ import ( "os" "os/exec" "path/filepath" - "syscall" "text/template" "time" "github.com/docker/go-units" "github.com/lima-vm/go-qcow2reader" - "github.com/mattn/go-isatty" "github.com/sirupsen/logrus" "github.com/lima-vm/lima/pkg/downloader" @@ -29,7 +27,6 @@ import ( hostagentevents "github.com/lima-vm/lima/pkg/hostagent/events" "github.com/lima-vm/lima/pkg/imgutil/proxyimgutil" "github.com/lima-vm/lima/pkg/limayaml" - "github.com/lima-vm/lima/pkg/osutil" "github.com/lima-vm/lima/pkg/store" "github.com/lima-vm/lima/pkg/store/filenames" "github.com/lima-vm/lima/pkg/usrlocalsharelima" @@ -249,25 +246,7 @@ func Start(ctx context.Context, inst *store.Instance, limactl string, launchHost begin := time.Now() // used for logrus propagation if launchHostAgentForeground { - logrus.Info("Running the host agent in the foreground") - if isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd()) { - // Write message to standard log files to avoid confusing users - message := "This log file is not used because `limactl start` was launched in the terminal with the `--foreground` option." - if _, err := haStdoutW.WriteString(message); err != nil { - return err - } - if _, err := haStderrW.WriteString(message); err != nil { - return err - } - } else { - if err := osutil.Dup2(int(haStdoutW.Fd()), syscall.Stdout); err != nil { - return err - } - if err := osutil.Dup2(int(haStderrW.Fd()), syscall.Stderr); err != nil { - return err - } - } - if err := syscall.Exec(limactl, haCmd.Args, haCmd.Environ()); err != nil { + if err := execHostAgentForeground(limactl, haCmd); err != nil { return err } } else if err := haCmd.Start(); err != nil { diff --git a/pkg/instance/start_unix.go b/pkg/instance/start_unix.go new file mode 100644 index 00000000000..e1c44d71dd0 --- /dev/null +++ b/pkg/instance/start_unix.go @@ -0,0 +1,48 @@ +//go:build !windows + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package instance + +import ( + "fmt" + "os" + "os/exec" + "syscall" + + "github.com/mattn/go-isatty" + "github.com/sirupsen/logrus" + + "github.com/lima-vm/lima/pkg/osutil" +) + +func execHostAgentForeground(limactl string, haCmd *exec.Cmd) error { + haStdoutW, ok := haCmd.Stdout.(*os.File) + if !ok { + return fmt.Errorf("expected haCmd.Stdout to be *os.File, got %T", haCmd.Stdout) + } + haStderrW, ok := haCmd.Stderr.(*os.File) + if !ok { + return fmt.Errorf("expected haCmd.Stderr to be *os.File, got %T", haCmd.Stderr) + } + logrus.Info("Running the host agent in the foreground") + if isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd()) { + // Write message to standard log files to avoid confusing users + message := "This log file is not used because `limactl start` was launched in the terminal with the `--foreground` option." + if _, err := haStdoutW.WriteString(message); err != nil { + return err + } + if _, err := haStderrW.WriteString(message); err != nil { + return err + } + } else { + if err := osutil.Dup2(int(haStdoutW.Fd()), syscall.Stdout); err != nil { + return err + } + if err := osutil.Dup2(int(haStderrW.Fd()), syscall.Stderr); err != nil { + return err + } + } + return syscall.Exec(limactl, haCmd.Args, haCmd.Environ()) +} diff --git a/pkg/instance/start_windows.go b/pkg/instance/start_windows.go new file mode 100644 index 00000000000..2f85c03aec9 --- /dev/null +++ b/pkg/instance/start_windows.go @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package instance + +import ( + "errors" + "os/exec" +) + +func execHostAgentForeground(_ string, _ *exec.Cmd) error { + return errors.New("`limactl start --foreground` is not supported on Windows") +} From 1279fa76bdea53401088f29879edb8c013743872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20F=20Bj=C3=B6rklund?= Date: Thu, 17 Jul 2025 13:27:28 +0200 Subject: [PATCH 4/4] Embed templates edited from the user interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Anders F Björklund (cherry picked from commit 86b4495ffc3b32f868459591cd399cde9296cb4f) Signed-off-by: Akihiro Suda --- cmd/limactl/start.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/limactl/start.go b/cmd/limactl/start.go index 0d78bc1e0cd..ede6f4c1712 100644 --- a/cmd/limactl/start.go +++ b/cmd/limactl/start.go @@ -329,6 +329,7 @@ func chooseNextCreatorState(ctx context.Context, tmpl *limatmpl.Template, yq str hdr += editutil.GenerateEditorWarningHeader() var err error tmpl.Bytes, err = editutil.OpenEditor(tmpl.Bytes, hdr) + tmpl.Config = nil if err != nil { return tmpl, err } @@ -337,6 +338,10 @@ func chooseNextCreatorState(ctx context.Context, tmpl *limatmpl.Template, yq str logrus.Info(msg) return nil, exitSuccessError{Msg: msg} } + err = tmpl.Embed(ctx, true, true) + if err != nil { + return nil, err + } return tmpl, nil case 2: // "Choose another template..." templates, err := filterHiddenTemplates()