[lxc-devel] [lxd/master] Fix console disconnection

stgraber on Github lxc-bot at linuxcontainers.org
Wed Nov 27 01:18:35 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191126/08524b56/attachment.bin>
-------------- next part --------------
From 880fca008dd619f6f9e0add0f42aeb00b5970187 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 26 Nov 2019 19:59:36 -0500
Subject: [PATCH 1/4] shared: Cleanup console on error
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/network.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/shared/network.go b/shared/network.go
index beb927e66c..089480b318 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -474,6 +474,7 @@ func WebsocketConsoleMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadClo
 			if !ok {
 				r.Close()
 				logger.Debugf("sending write barrier")
+				conn.WriteMessage(websocket.BinaryMessage, []byte("\r"))
 				conn.WriteMessage(websocket.TextMessage, []byte{})
 				readDone <- true
 				return
@@ -491,6 +492,7 @@ func WebsocketConsoleMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadClo
 				break
 			}
 		}
+
 		closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
 		conn.WriteMessage(websocket.CloseMessage, closeMsg)
 		readDone <- true

From 95eaf283341200b3c065ea1cb1fe9ec74454e6c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 26 Nov 2019 20:00:00 -0500
Subject: [PATCH 2/4] lxd: Cleanup console on error
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_console.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lxd/container_console.go b/lxd/container_console.go
index 41440db208..791682db0d 100644
--- a/lxd/container_console.go
+++ b/lxd/container_console.go
@@ -222,9 +222,10 @@ func (s *consoleWs) Do(op *operations.Operation) error {
 
 	// Get the console websocket and close it.
 	s.connsLock.Lock()
-	consolConn := s.conns[0]
+	consoleConn := s.conns[0]
 	s.connsLock.Unlock()
-	consolConn.Close()
+	consoleConn.WriteMessage(websocket.BinaryMessage, []byte("\n\r"))
+	consoleConn.Close()
 
 	// Get the control websocket and close it.
 	s.connsLock.Lock()

From d91543582f6b3efc03ae1455311b84da1888d82b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 26 Nov 2019 20:00:36 -0500
Subject: [PATCH 3/4] lxd/console: Improve disconnection handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #6512

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_console.go           |  5 +++--
 lxd/container_lxc.go               | 23 ++++++++++++++++++-----
 lxd/instance/instance_interface.go |  2 +-
 lxd/vm_qemu.go                     | 18 ++++++++++--------
 4 files changed, 32 insertions(+), 16 deletions(-)

diff --git a/lxd/container_console.go b/lxd/container_console.go
index 791682db0d..e63d599dc0 100644
--- a/lxd/container_console.go
+++ b/lxd/container_console.go
@@ -112,7 +112,7 @@ func (s *consoleWs) Do(op *operations.Operation) error {
 	<-s.allConnected
 
 	// Get console from instance.
-	console, err := s.instance.Console()
+	console, consoleDisconnectCh, err := s.instance.Console()
 	if err != nil {
 		return err
 	}
@@ -204,14 +204,15 @@ func (s *consoleWs) Do(op *operations.Operation) error {
 		<-readDone
 		logger.Debugf("Finished mirroring console to websocket")
 		<-writeDone
-		conn.Close()
 		close(mirrorDoneCh)
 	}()
 
 	// Wait until either the console or the websocket is done.
 	select {
 	case <-mirrorDoneCh:
+		close(consoleDisconnectCh)
 	case <-consoleDoneCh:
+		close(consoleDisconnectCh)
 	}
 
 	// Write a reset escape sequence to the console to cancel any ongoing reads to the handle
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 4a8a41a203..c15289ac17 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -5837,7 +5837,9 @@ func (c *containerLXC) FileRemove(path string) error {
 	return nil
 }
 
-func (c *containerLXC) Console() (*os.File, error) {
+func (c *containerLXC) Console() (*os.File, chan error, error) {
+	chDisconnect := make(chan error, 1)
+
 	args := []string{
 		c.state.OS.ExecPath,
 		"forkconsole",
@@ -5849,7 +5851,7 @@ func (c *containerLXC) Console() (*os.File, error) {
 
 	idmapset, err := c.CurrentIdmap()
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	var rootUID, rootGID int64
@@ -5859,7 +5861,7 @@ func (c *containerLXC) Console() (*os.File, error) {
 
 	master, slave, err := shared.OpenPty(rootUID, rootGID)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	cmd := exec.Cmd{}
@@ -5871,10 +5873,21 @@ func (c *containerLXC) Console() (*os.File, error) {
 
 	err = cmd.Start()
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
-	return master, nil
+	go func() {
+		err = cmd.Wait()
+		master.Close()
+		slave.Close()
+	}()
+
+	go func() {
+		<-chDisconnect
+		cmd.Process.Kill()
+	}()
+
+	return master, chDisconnect, nil
 }
 
 func (c *containerLXC) ConsoleLog(opts lxc.ConsoleLogOptions) (string, error) {
diff --git a/lxd/instance/instance_interface.go b/lxd/instance/instance_interface.go
index ae0c2e3daa..9c56b0ddfe 100644
--- a/lxd/instance/instance_interface.go
+++ b/lxd/instance/instance_interface.go
@@ -51,7 +51,7 @@ type Instance interface {
 	FileRemove(path string) error
 
 	// Console - Allocate and run a console tty.
-	Console() (*os.File, error)
+	Console() (*os.File, chan error, error)
 	Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File, cwd string, uid uint32, gid uint32) (Cmd, error)
 
 	// Status
diff --git a/lxd/vm_qemu.go b/lxd/vm_qemu.go
index c68ca7e95e..2a8101575a 100644
--- a/lxd/vm_qemu.go
+++ b/lxd/vm_qemu.go
@@ -2240,23 +2240,25 @@ func (vm *vmQemu) FileRemove(path string) error {
 	return fmt.Errorf("FileRemove Not implemented")
 }
 
-func (vm *vmQemu) Console() (*os.File, error) {
+func (vm *vmQemu) Console() (*os.File, chan error, error) {
+	chError := make(chan error, 1)
+
 	// Connect to the monitor.
 	monitor, err := qmp.NewSocketMonitor("unix", vm.getMonitorPath(), vmVsockTimeout)
 	if err != nil {
-		return nil, err // The VM isn't running as no monitor socket available.
+		return nil, nil, err // The VM isn't running as no monitor socket available.
 	}
 
 	err = monitor.Connect()
 	if err != nil {
-		return nil, err // The capabilities handshake failed.
+		return nil, nil, err // The capabilities handshake failed.
 	}
 	defer monitor.Disconnect()
 
 	// Send the status command.
 	respRaw, err := monitor.Run([]byte("{'execute': 'query-chardev'}"))
 	if err != nil {
-		return nil, err // Status command failed.
+		return nil, nil, err // Status command failed.
 	}
 
 	var respDecoded struct {
@@ -2268,7 +2270,7 @@ func (vm *vmQemu) Console() (*os.File, error) {
 
 	err = json.Unmarshal(respRaw, &respDecoded)
 	if err != nil {
-		return nil, err // JSON decode failed.
+		return nil, nil, err // JSON decode failed.
 	}
 
 	var ptsPath string
@@ -2280,15 +2282,15 @@ func (vm *vmQemu) Console() (*os.File, error) {
 	}
 
 	if ptsPath == "" {
-		return nil, fmt.Errorf("No PTS path found")
+		return nil, nil, fmt.Errorf("No PTS path found")
 	}
 
 	console, err := os.OpenFile(ptsPath, os.O_RDWR, 0600)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
-	return console, nil
+	return console, chError, nil
 }
 
 func (vm *vmQemu) forwardSignal(control *websocket.Conn, sig unix.Signal) error {

From 3065865ac24b65e610ff195e08b739c570a433a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 26 Nov 2019 20:17:08 -0500
Subject: [PATCH 4/4] lxd/vm: Add locking around console
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/vm_qemu.go | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/lxd/vm_qemu.go b/lxd/vm_qemu.go
index 2a8101575a..e9002f18f9 100644
--- a/lxd/vm_qemu.go
+++ b/lxd/vm_qemu.go
@@ -13,6 +13,7 @@ import (
 	"path/filepath"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/digitalocean/go-qemu/qmp"
@@ -49,6 +50,9 @@ import (
 
 var vmVsockTimeout time.Duration = time.Second
 
+var vmConsole = map[int]bool{}
+var vmConsoleLock sync.Mutex
+
 func vmQemuLoad(s *state.State, args db.InstanceArgs, profiles []api.Profile) (instance.Instance, error) {
 	// Create the container struct.
 	vm := vmQemuInstantiate(s, args)
@@ -2241,7 +2245,15 @@ func (vm *vmQemu) FileRemove(path string) error {
 }
 
 func (vm *vmQemu) Console() (*os.File, chan error, error) {
-	chError := make(chan error, 1)
+	chDisconnect := make(chan error, 1)
+
+	// Avoid duplicate connects.
+	vmConsoleLock.Lock()
+	if vmConsole[vm.id] {
+		vmConsoleLock.Unlock()
+		return nil, nil, fmt.Errorf("There is already an active console for this instance")
+	}
+	vmConsoleLock.Unlock()
 
 	// Connect to the monitor.
 	monitor, err := qmp.NewSocketMonitor("unix", vm.getMonitorPath(), vmVsockTimeout)
@@ -2290,7 +2302,19 @@ func (vm *vmQemu) Console() (*os.File, chan error, error) {
 		return nil, nil, err
 	}
 
-	return console, chError, nil
+	vmConsoleLock.Lock()
+	vmConsole[vm.id] = true
+	vmConsoleLock.Unlock()
+
+	go func() {
+		<-chDisconnect
+
+		vmConsoleLock.Lock()
+		vmConsole[vm.id] = false
+		vmConsoleLock.Unlock()
+	}()
+
+	return console, chDisconnect, nil
 }
 
 func (vm *vmQemu) forwardSignal(control *websocket.Conn, sig unix.Signal) error {


More information about the lxc-devel mailing list