* [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux
@ 2024-05-22 15:06 Alexander Ivanov
2024-05-22 15:23 ` Test scripts Alexander Ivanov
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Alexander Ivanov @ 2024-05-22 15:06 UTC (permalink / raw)
To: qemu-devel; +Cc: den, michael.roth, kkostiuk, marcandre.lureau
Add an interactive mode to the guest-exec command in the QEMU Guest Agent
using the VSOCK communication mechanism. It enables interactive sessions
with the executed command in the guest, allowing real-time input/output.
Introduce "interactive" mode in the GuestExecCaptureOutputMode enumeration
and add optional "cid" and "port" fields to the guest-exec response. In
such a way user can execute guest-exec command, get CID and port number
from the response and connect to the guest server. After connection user
can communicate with the started process. All the data transmitted to the
server is redirected to stdin. Data from stdout and stderr is redirected
to the client. All data blocks are preceded by 32-bit headers (network
byte order): most significant bit contains a sign of stream (stdout - 0,
stderr - 1), all the other bits contain the payload size.
Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
---
qga/commands.c | 272 +++++++++++++++++++++++++++++++++++++++++--
qga/qapi-schema.json | 11 +-
2 files changed, 273 insertions(+), 10 deletions(-)
diff --git a/qga/commands.c b/qga/commands.c
index 88c1c99fe5..377a79c816 100644
--- a/qga/commands.c
+++ b/qga/commands.c
@@ -20,6 +20,11 @@
#include "qemu/cutils.h"
#include "commands-common.h"
+#ifdef CONFIG_LINUX
+#include <sys/ioctl.h>
+#include <linux/vm_sockets.h>
+#endif
+
/* Maximum captured guest-exec out_data/err_data - 16MB */
#define GUEST_EXEC_MAX_OUTPUT (16 * 1024 * 1024)
/* Allocation and I/O buffer for reading guest-exec out_data/err_data - 4KB */
@@ -92,6 +97,27 @@ struct GuestExecIOData {
};
typedef struct GuestExecIOData GuestExecIOData;
+#define GE_INT_IO_SIZE (256 * 1024)
+#define GE_INT_STREAM_MASK 0x80000000
+
+struct GEIntPacket {
+ uint32_t header;
+ gchar buf[GE_INT_IO_SIZE];
+} __attribute__((aligned(1)));
+typedef struct GEIntPacket GEIntPacket;
+
+struct GEIntData {
+ unsigned int cid;
+ unsigned int port;
+ GIOChannel *ch_srv;
+ GIOChannel *ch_clt;
+ GIOChannel *ch_in;
+ GIOChannel *ch_out;
+ GIOChannel *ch_err;
+ GEIntPacket packet;
+};
+typedef struct GEIntData GEIntData;
+
struct GuestExecInfo {
GPid pid;
int64_t pid_numeric;
@@ -101,6 +127,7 @@ struct GuestExecInfo {
GuestExecIOData in;
GuestExecIOData out;
GuestExecIOData err;
+ GEIntData *int_data;
QTAILQ_ENTRY(GuestExecInfo) next;
};
typedef struct GuestExecInfo GuestExecInfo;
@@ -257,6 +284,194 @@ static char **guest_exec_get_args(const strList *entry, bool log)
return args;
}
+#ifdef CONFIG_LINUX
+static void guest_exec_close_channel(GIOChannel *ch)
+{
+ g_io_channel_shutdown(ch, true, NULL);
+ g_io_channel_unref(ch);
+}
+
+static void guest_exec_interactive_cleanup(GuestExecInfo *gei)
+{
+ GEIntData *data = gei->int_data;
+
+ guest_exec_close_channel(data->ch_clt);
+ guest_exec_close_channel(data->ch_srv);
+ guest_exec_close_channel(data->ch_in);
+ guest_exec_close_channel(data->ch_out);
+ guest_exec_close_channel(data->ch_err);
+
+ g_free(data);
+ gei->int_data = NULL;
+}
+
+static gboolean guest_exec_interactive_watch(GIOChannel *ch, GIOCondition cond,
+ gpointer data_)
+{
+ GuestExecInfo *gei = (GuestExecInfo *)data_;
+ GEIntData *data = gei->int_data;
+ gsize size, bytes_written;
+ GIOStatus gstatus;
+ GError *gerr = NULL;
+ GIOChannel *dst_ch;
+ gchar *p;
+
+ if (data == NULL) {
+ return false;
+ }
+
+ if (cond == G_IO_HUP || cond == G_IO_ERR) {
+ goto close;
+ }
+
+ gstatus = g_io_channel_read_chars(ch, data->packet.buf,
+ sizeof(data->packet.buf), &size, NULL);
+
+ if (gstatus == G_IO_STATUS_EOF || gstatus == G_IO_STATUS_ERROR) {
+ if (gerr) {
+ g_warning("qga: i/o error reading from a channel: %s",
+ gerr->message);
+ g_error_free(gerr);
+ }
+ goto close;
+ }
+
+ if (ch == data->ch_clt) {
+ dst_ch = data->ch_in;
+ p = data->packet.buf;
+ } else {
+ assert(size < GE_INT_STREAM_MASK);
+
+ dst_ch = data->ch_clt;
+ p = (gchar *)&(data->packet);
+ data->packet.header = htonl(size);
+ if (ch == data->ch_err) {
+ data->packet.header |= htonl(GE_INT_STREAM_MASK);
+ }
+ size += sizeof(data->packet.header);
+ }
+
+ do {
+ gstatus = g_io_channel_write_chars(dst_ch, p, size,
+ &bytes_written, &gerr);
+
+ if (gstatus == G_IO_STATUS_EOF || gstatus == G_IO_STATUS_ERROR) {
+ if (gerr) {
+ g_warning("qga: i/o error writing to a channel: %s",
+ gerr->message);
+ g_error_free(gerr);
+ }
+ goto close;
+ }
+ size -= bytes_written;
+ p += bytes_written;
+ } while (size > 0);
+
+ return true;
+
+close:
+ guest_exec_interactive_cleanup(gei);
+ return false;
+}
+
+static gboolean
+guest_exec_interactive_accept_watch(GIOChannel *ch, GIOCondition cond,
+ gpointer data_)
+{
+ GuestExecInfo *gei = (GuestExecInfo *)data_;
+ GEIntData *data = gei->int_data;
+ int fd;
+
+ if (cond == G_IO_HUP || cond == G_IO_ERR) {
+ goto close;
+ }
+
+ fd = accept(g_io_channel_unix_get_fd(ch), NULL, NULL);
+ if (fd < 0) {
+ goto close;
+ }
+
+ data->ch_clt = g_io_channel_unix_new(fd);
+ g_io_channel_set_encoding(data->ch_clt, NULL, NULL);
+ g_io_channel_set_buffered(data->ch_clt, false);
+ g_io_channel_set_close_on_unref(data->ch_clt, true);
+
+ g_io_add_watch(data->ch_clt, G_IO_IN | G_IO_HUP,
+ guest_exec_interactive_watch, gei);
+ g_io_add_watch(data->ch_out, G_IO_IN | G_IO_HUP,
+ guest_exec_interactive_watch, gei);
+ g_io_add_watch(data->ch_err, G_IO_IN | G_IO_HUP,
+ guest_exec_interactive_watch, gei);
+ return false;
+
+close:
+ guest_exec_interactive_cleanup(gei);
+ return false;
+}
+
+static int get_cid(unsigned int *cid)
+{
+ int fd, ret;
+ fd = open("/dev/vsock", O_RDONLY);
+ if (fd == -1) {
+ return errno;
+ }
+ ret = ioctl(fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, cid);
+ close(fd);
+ return ret;
+}
+
+static int guest_exec_interactive_listen(GuestExecInfo *gei)
+{
+ struct sockaddr_vm server_addr;
+ socklen_t len;
+ int fd, res;
+ GEIntData *data = (GEIntData *)gei->int_data;
+
+ if (get_cid(&data->cid) != 0) {
+ slog("Can't get CID: %s", strerror(errno));
+ return -1;
+ }
+
+ fd = socket(AF_VSOCK, SOCK_STREAM, 0);
+ if (fd == -1) {
+ slog("Socket creation error: %s", strerror(errno));
+ return -1;
+ }
+
+ memset(&server_addr, 0, sizeof(server_addr));
+ server_addr.svm_family = AF_VSOCK;
+ server_addr.svm_port = VMADDR_PORT_ANY;
+ server_addr.svm_cid = VMADDR_CID_ANY;
+
+ if (bind(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
+ slog("Bind error: %s", strerror(errno));
+ goto err;
+ }
+
+ len = sizeof(struct sockaddr_vm);
+ res = getsockname(fd, (struct sockaddr *)&server_addr, &len);
+ if (res == -1) {
+ slog("Can't get port: %s", strerror(errno));
+ goto err;
+ }
+
+ if (listen(fd, 1) == -1) {
+ slog("Can't listen port %d: %s", server_addr.svm_port, strerror(errno));
+ goto err;
+ }
+
+ data->port = server_addr.svm_port;
+ data->ch_srv = g_io_channel_unix_new(fd);
+ g_io_add_watch(data->ch_srv, G_IO_IN | G_IO_HUP,
+ guest_exec_interactive_accept_watch, gei);
+ return 0;
+err:
+ close(fd);
+ return -1;
+}
+#endif
+
static void guest_exec_child_watch(GPid pid, gint status, gpointer data)
{
GuestExecInfo *gei = (GuestExecInfo *)data;
@@ -424,6 +639,8 @@ GuestExec *qmp_guest_exec(const char *path,
GSpawnFlags flags;
bool has_output = false;
bool has_merge = false;
+ bool interactive = false;
+
GuestExecCaptureOutputMode output_mode;
g_autofree uint8_t *input = NULL;
size_t ninput = 0;
@@ -465,6 +682,11 @@ GuestExec *qmp_guest_exec(const char *path,
has_output = true;
has_merge = true;
break;
+#endif
+#ifdef CONFIG_LINUX
+ case GUEST_EXEC_CAPTURE_OUTPUT_MODE_INTERACTIVE:
+ interactive = true;
+ break;
#endif
case GUEST_EXEC_CAPTURE_OUTPUT_MODE__MAX:
/* Silence warning; impossible branch */
@@ -472,8 +694,10 @@ GuestExec *qmp_guest_exec(const char *path,
}
ret = g_spawn_async_with_pipes(NULL, argv, envp, flags,
- guest_exec_task_setup, &has_merge, &pid, input_data ? &in_fd : NULL,
- has_output ? &out_fd : NULL, has_output ? &err_fd : NULL, &gerr);
+ guest_exec_task_setup, &has_merge, &pid,
+ (input_data || interactive) ? &in_fd : NULL,
+ (has_output || interactive) ? &out_fd : NULL,
+ (has_output || interactive) ? &err_fd : NULL, &gerr);
if (!ret) {
error_setg(errp, QERR_QGA_COMMAND_FAILED, gerr->message);
g_error_free(gerr);
@@ -485,9 +709,14 @@ GuestExec *qmp_guest_exec(const char *path,
gei = guest_exec_info_add(pid);
gei->has_output = has_output;
+
g_child_watch_add(pid, guest_exec_child_watch, gei);
- if (input_data) {
+ if (interactive) {
+ gei->int_data = g_malloc0(sizeof(GEIntData));
+ }
+
+ if (input_data || interactive) {
gei->in.data = g_steal_pointer(&input);
gei->in.size = ninput;
#ifdef G_OS_WIN32
@@ -499,10 +728,14 @@ GuestExec *qmp_guest_exec(const char *path,
g_io_channel_set_buffered(in_ch, false);
g_io_channel_set_flags(in_ch, G_IO_FLAG_NONBLOCK, NULL);
g_io_channel_set_close_on_unref(in_ch, true);
- g_io_add_watch(in_ch, G_IO_OUT, guest_exec_input_watch, &gei->in);
+ if (interactive) {
+ gei->int_data->ch_in = in_ch;
+ } else {
+ g_io_add_watch(in_ch, G_IO_OUT, guest_exec_input_watch, &gei->in);
+ }
}
- if (has_output) {
+ if (has_output || interactive) {
#ifdef G_OS_WIN32
out_ch = g_io_channel_win32_new_fd(out_fd);
err_ch = g_io_channel_win32_new_fd(err_fd);
@@ -516,12 +749,33 @@ GuestExec *qmp_guest_exec(const char *path,
g_io_channel_set_buffered(err_ch, false);
g_io_channel_set_close_on_unref(out_ch, true);
g_io_channel_set_close_on_unref(err_ch, true);
- g_io_add_watch(out_ch, G_IO_IN | G_IO_HUP,
- guest_exec_output_watch, &gei->out);
- g_io_add_watch(err_ch, G_IO_IN | G_IO_HUP,
- guest_exec_output_watch, &gei->err);
+
+ if (interactive) {
+ gei->int_data->ch_out = out_ch;
+ gei->int_data->ch_err = err_ch;
+ } else {
+ g_io_add_watch(out_ch, G_IO_IN | G_IO_HUP,
+ guest_exec_output_watch, &gei->out);
+ g_io_add_watch(err_ch, G_IO_IN | G_IO_HUP,
+ guest_exec_output_watch, &gei->err);
+ }
}
+#ifdef CONFIG_LINUX
+ if (interactive) {
+ if (guest_exec_interactive_listen(gei) != 0) {
+ QTAILQ_REMOVE(&guest_exec_state.processes, gei, next);
+ g_free(gei->int_data);
+ g_free(gei);
+ goto done;
+ }
+ ge->has_cid = true;
+ ge->cid = gei->int_data->cid;
+ ge->has_port = true;
+ ge->port = gei->int_data->port;
+ }
+#endif
+
done:
g_free(argv);
g_free(envp);
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index d5af155007..77efa847af 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -1262,10 +1262,16 @@
#
# @pid: pid of child process in guest OS
#
+# @cid: context identifier for interactive mode
+#
+# @port: port number for interactive mode
+#
# Since: 2.5
##
{ 'struct': 'GuestExec',
- 'data': { 'pid': 'int'} }
+ 'data': { 'pid': 'int',
+ '*cid': 'int',
+ '*port': 'int' } }
##
# @GuestExecCaptureOutputMode:
@@ -1284,10 +1290,13 @@
# @merged: capture both stdout and stderr, but merge together into
# out-data. Not effective on windows guests.
#
+# @interactive: stdin, stdout and stderr are transmitted via Vsock
+#
# Since: 8.0
##
{ 'enum': 'GuestExecCaptureOutputMode',
'data': [ 'none', 'stdout', 'stderr', 'separated',
+ { 'name': 'interactive', 'if': 'CONFIG_LINUX' },
{ 'name': 'merged', 'if': { 'not': 'CONFIG_WIN32' } } ] }
##
--
2.40.1
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Test scripts
2024-05-22 15:06 [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux Alexander Ivanov
@ 2024-05-22 15:23 ` Alexander Ivanov
2024-05-23 9:56 ` Alex Bennée
2024-05-22 15:38 ` [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux Michal Prívozník
2024-05-22 16:10 ` Daniel P. Berrangé
2 siblings, 1 reply; 8+ messages in thread
From: Alexander Ivanov @ 2024-05-22 15:23 UTC (permalink / raw)
To: qemu-devel; +Cc: den, michael.roth, kkostiuk, marcandre.lureau
[-- Attachment #1: Type: text/plain, Size: 469 bytes --]
There are two python scripts in the attachment:
vsock_guest_exec_simple.py - simple example of a client;
vsock_guest_exec_test.py - tests with different payload size.
The last file should be copied to a guest VM. Edit SRV_PATH variable
in the host copy of the script - there should be path to the directory
containing a copy of the script in VM. Execute the host script with
net arguments:
./vsock_guest_exec_test.py srv <VM_NAME>
--
Best regards,
Alexander Ivanov
[-- Attachment #2: vsock_guest_exec_simple.py --]
[-- Type: text/x-python, Size: 1656 bytes --]
#!/usr/bin/python3
import sys, os, struct, subprocess, json, socket
TYPE_MASK = 0x80000000
def parse_block_header(data):
res = struct.unpack('!I', data)
size = res[0]
if size & TYPE_MASK:
size -= TYPE_MASK
tp = 'err'
else:
tp = 'out'
return (size, tp)
def recv_block(sock):
hdr = sock.recv(4)
if not hdr:
print('ERROR: header receiving')
sys.exit(-1)
size, tp = parse_block_header(hdr)
res = b''
received = 0
while size > 0:
part = sock.recv(size)
res += part
size -= len(part)
return (res, tp)
def guest_exec(vm_name):
print('run guest-exec command...')
cmd = ['virsh',
'qemu-agent-command',
vm_name,
'{"execute":"guest-exec", "arguments":{"path": "bash", "capture-output": "interactive"}}']
p = subprocess.run(cmd, stdout=subprocess.PIPE)
response = p.stdout.decode('utf-8')
print('response: %s' % response.strip())
response = json.loads(response)['return']
cid = int(response['cid'])
port = int(response['port'])
return (cid, port)
def srv_conn(cid, port):
sock = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
print('connect...')
sock.connect((cid, port))
print('connected')
return sock
def main():
if len(sys.argv) != 2:
print('Usage:\n\t%s <VM name>' % sys.argv[0])
return
vm_name = sys.argv[1]
cid, port = guest_exec(vm_name)
sock = srv_conn(cid, port)
sock.send(b'echo "Hello world!"\n')
data, tp = recv_block(sock)
print('Received from %s: "%s"' % (tp, data.decode('utf-8')))
main()
[-- Attachment #3: vsock_guest_exec_test.py --]
[-- Type: text/x-python, Size: 4807 bytes --]
#!/usr/bin/python3
import sys, os, time, struct, hashlib, subprocess, json, socket, random
SRV_PATH = '/home/user/'
PKT_HDR_FMT = '!Ib32s'
TYPE_MASK = 0x80000000
TYPE_OUT = 1
TYPE_ERR = 2
def parse_block_header(data):
res = struct.unpack('!I', data)
size = res[0]
if size & TYPE_MASK:
size -= TYPE_MASK
tp = 'err'
else:
tp = 'out'
return (size, tp)
def make_pkt(size, tp):
payload = os.urandom(size)
digest = hashlib.sha256(payload).digest()
return struct.pack(PKT_HDR_FMT, size, tp, digest) + payload
def recv_block(sock):
hdr = b''
while len(hdr) < 4:
part = sock.recv(4 - len(hdr))
if not part:
return (None, None)
hdr += part
size, tp = parse_block_header(hdr)
res = b''
received = 0
while size > 0:
part = sock.recv(size)
res += part
size -= len(part)
return (res, tp)
def recv_pkt(sock, spkt):
hdr_size = struct.calcsize(PKT_HDR_FMT)
data = b''
size = None
tries = 0
while True:
block, btp = recv_block(sock)
if block == None:
if len(data) == 0:
time.sleep(0.01)
tries += 1
print('retry')
if tries > 10:
print('Connection closed')
return
else:
data += block
tries = 0
if len(data) < hdr_size:
continue
if size == None:
hdr = data[:hdr_size]
data = data[hdr_size:]
size, tp, digest = struct.unpack(PKT_HDR_FMT, hdr)
tp = 'err' if tp == TYPE_ERR else 'out'
if tp != btp:
print('ERR type')
return None
if len(data) < size:
continue
payload = data[:size]
data = data[size:]
if size != len(payload):
print('ERR size', size, len(payload))
return None
if digest != hashlib.sha256(payload).digest():
print('ERR digest', digest, hashlib.sha256(payload).digest())
for i in range(size):
if payload[i] != spkt[i + hdr_size]:
print('%d: %d != %d\n', i, payload[i], spkt[i + hdr_size])
return None
return payload
def run_command(vm_name, path):
print('run command...')
cmd = ['virsh',
'qemu-agent-command',
vm_name,
'{"execute":"guest-exec", "arguments":{"path": "%s", "arg": ["srv"], "capture-output": "interactive"}}' % path]
p = subprocess.run(cmd, stdout=subprocess.PIPE)
response = p.stdout.decode('utf-8')
print('response: %s' % response)
response = json.loads(response)['return']
cid = int(response['cid'])
port = int(response['port'])
return (cid, port)
def srv_conn(cid, port):
sock = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
print('connect...')
sock.connect((cid, port))
print('connected')
return sock
def run_cycle(sock, size):
tp = random.choice([TYPE_OUT, TYPE_ERR])
pkt = make_pkt(size, tp)
if len(pkt) > 1024*1024:
print('Send pkt, size = %d' % len(pkt))
sock.send(pkt)
payload = recv_pkt(sock, pkt)
def test_sequence(sock, n):
for i in range(n):
run_cycle(sock, i)
def test_rand_small(sock):
for i in range(1000):
size = random.randint(0, 1024*1024)
run_cycle(sock, size)
def test_rand_big(sock):
for i in range(10):
size = random.randint(1024*1024*100, 1024*1024*200)
run_cycle(sock, size)
def run_client(vm_name):
path = SRV_PATH + sys.argv[0]
cid, port = run_command(vm_name, path)
print('start on %d %d' % (cid, port))
sock = srv_conn(cid, port)
print('test 65537')
test_sequence(sock, 65537)
print('test rand small')
test_rand_small(sock)
print('test rand big')
test_rand_big(sock)
# run_cycle(sock, 740471)
def run_server():
hdr_size = struct.calcsize(PKT_HDR_FMT)
while True:
hdr = sys.stdin.buffer.read(hdr_size)
if not hdr:
return
size, tp, digest = struct.unpack(PKT_HDR_FMT, hdr)
payload = sys.stdin.buffer.read(size)
buf = sys.stderr.buffer if tp == TYPE_ERR else sys.stdout.buffer
res = buf.write(hdr + payload)
buf.flush()
def usage():
fn = sys.argv[0]
print('Run as server:\n' +
('\t%s srv\n\n' % fn) +
'Run as client:\n' +
('\t%s clt <VM name>' % fn))
sys.exit(-1)
def main():
if len(sys.argv) < 2:
usage()
side = sys.argv[1]
if side == 'srv':
run_server()
elif side == 'clt' and len(sys.argv) == 3:
run_client(sys.argv[2])
else:
usage()
main()
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: Test scripts
2024-05-22 15:23 ` Test scripts Alexander Ivanov
@ 2024-05-23 9:56 ` Alex Bennée
0 siblings, 0 replies; 8+ messages in thread
From: Alex Bennée @ 2024-05-23 9:56 UTC (permalink / raw)
To: Alexander Ivanov
Cc: qemu-devel, den, michael.roth, kkostiuk, marcandre.lureau
Alexander Ivanov <alexander.ivanov@virtuozzo.com> writes:
> There are two python scripts in the attachment:
> vsock_guest_exec_simple.py - simple example of a client;
> vsock_guest_exec_test.py - tests with different payload size.
>
> The last file should be copied to a guest VM. Edit SRV_PATH variable
> in the host copy of the script - there should be path to the directory
> containing a copy of the script in VM. Execute the host script with
> net arguments:
> ./vsock_guest_exec_test.py srv <VM_NAME>
Maybe these would best live in contrib/vsock with a README?
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux
2024-05-22 15:06 [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux Alexander Ivanov
2024-05-22 15:23 ` Test scripts Alexander Ivanov
@ 2024-05-22 15:38 ` Michal Prívozník
2024-05-22 16:10 ` Daniel P. Berrangé
2 siblings, 0 replies; 8+ messages in thread
From: Michal Prívozník @ 2024-05-22 15:38 UTC (permalink / raw)
To: Alexander Ivanov, qemu-devel
Cc: den, michael.roth, kkostiuk, marcandre.lureau
On 5/22/24 17:06, Alexander Ivanov wrote:
> Add an interactive mode to the guest-exec command in the QEMU Guest Agent
> using the VSOCK communication mechanism. It enables interactive sessions
> with the executed command in the guest, allowing real-time input/output.
>
> Introduce "interactive" mode in the GuestExecCaptureOutputMode enumeration
> and add optional "cid" and "port" fields to the guest-exec response. In
> such a way user can execute guest-exec command, get CID and port number
> from the response and connect to the guest server. After connection user
> can communicate with the started process. All the data transmitted to the
> server is redirected to stdin. Data from stdout and stderr is redirected
> to the client. All data blocks are preceded by 32-bit headers (network
> byte order): most significant bit contains a sign of stream (stdout - 0,
> stderr - 1), all the other bits contain the payload size.
>
> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
> ---
> qga/commands.c | 272 +++++++++++++++++++++++++++++++++++++++++--
> qga/qapi-schema.json | 11 +-
> 2 files changed, 273 insertions(+), 10 deletions(-)
FYI, libvirt recently added support for SSH over VSOCK:
https://libvirt.org/ssh-proxy.html
and it'll be released soon:
https://libvirt.org/news.html
Though, it requires (soon to be released) systemd to automatically set
up SSHD to listen on VSOCK (but it can be configured manually).
Michal
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux
2024-05-22 15:06 [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux Alexander Ivanov
2024-05-22 15:23 ` Test scripts Alexander Ivanov
2024-05-22 15:38 ` [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux Michal Prívozník
@ 2024-05-22 16:10 ` Daniel P. Berrangé
2024-05-23 7:12 ` Denis V. Lunev
2024-05-23 7:57 ` Alexander Ivanov
2 siblings, 2 replies; 8+ messages in thread
From: Daniel P. Berrangé @ 2024-05-22 16:10 UTC (permalink / raw)
To: Alexander Ivanov
Cc: qemu-devel, den, michael.roth, kkostiuk, marcandre.lureau
On Wed, May 22, 2024 at 05:06:57PM +0200, Alexander Ivanov wrote:
> Add an interactive mode to the guest-exec command in the QEMU Guest Agent
> using the VSOCK communication mechanism. It enables interactive sessions
> with the executed command in the guest, allowing real-time input/output.
>
> Introduce "interactive" mode in the GuestExecCaptureOutputMode enumeration
> and add optional "cid" and "port" fields to the guest-exec response. In
> such a way user can execute guest-exec command, get CID and port number
> from the response and connect to the guest server. After connection user
> can communicate with the started process. All the data transmitted to the
> server is redirected to stdin. Data from stdout and stderr is redirected
> to the client. All data blocks are preceded by 32-bit headers (network
> byte order): most significant bit contains a sign of stream (stdout - 0,
> stderr - 1), all the other bits contain the payload size.
Every patch to 'guest-exec' takes us torwards re-inventing yet more
SSH/telnet functionality, but a poor simulation of it. For exmaple
this still lacks any separation of stdout/stderr streams, just
interleaving all their data back to the host. There is also zero
access control facilities beyond turning off the 'guest-exec'
command entirely.
IMHO we should really consider "arbitrary command execution" to be
something to be handled by a separate process. Let the guest OS admin
decide separately from running QEMU GA, whether they want to enable
arbitrary host processes to have a trival privileged backdoor into
their guest.
systemd now supports exposing SSH over VSOCK, and provides an SSH
proxy in the host to connect to VMs, while libvirt also has added
its own host SSH proxy to allow SSH based on libvirt VM name.
For windows guests, there is something called PowerShell Direct
which exposes PowerShell over vmbus under HyperV. Possibly that
can be enabled in QEMU too if someone understands windows & vmbus
enough... ?
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux
2024-05-22 16:10 ` Daniel P. Berrangé
@ 2024-05-23 7:12 ` Denis V. Lunev
2024-05-23 7:50 ` Daniel P. Berrangé
2024-05-23 7:57 ` Alexander Ivanov
1 sibling, 1 reply; 8+ messages in thread
From: Denis V. Lunev @ 2024-05-23 7:12 UTC (permalink / raw)
To: Daniel P. Berrangé, Alexander Ivanov
Cc: qemu-devel, michael.roth, kkostiuk, marcandre.lureau
On 5/22/24 18:10, Daniel P. Berrangé wrote:
> On Wed, May 22, 2024 at 05:06:57PM +0200, Alexander Ivanov wrote:
>> Add an interactive mode to the guest-exec command in the QEMU Guest Agent
>> using the VSOCK communication mechanism. It enables interactive sessions
>> with the executed command in the guest, allowing real-time input/output.
>>
>> Introduce "interactive" mode in the GuestExecCaptureOutputMode enumeration
>> and add optional "cid" and "port" fields to the guest-exec response. In
>> such a way user can execute guest-exec command, get CID and port number
>> from the response and connect to the guest server. After connection user
>> can communicate with the started process. All the data transmitted to the
>> server is redirected to stdin. Data from stdout and stderr is redirected
>> to the client. All data blocks are preceded by 32-bit headers (network
>> byte order): most significant bit contains a sign of stream (stdout - 0,
>> stderr - 1), all the other bits contain the payload size.
> Every patch to 'guest-exec' takes us torwards re-inventing yet more
> SSH/telnet functionality, but a poor simulation of it. For exmaple
> this still lacks any separation of stdout/stderr streams, just
> interleaving all their data back to the host. There is also zero
> access control facilities beyond turning off the 'guest-exec'
> command entirely.
>
> IMHO we should really consider "arbitrary command execution" to be
> something to be handled by a separate process. Let the guest OS admin
> decide separately from running QEMU GA, whether they want to enable
> arbitrary host processes to have a trival privileged backdoor into
> their guest.
>
> systemd now supports exposing SSH over VSOCK, and provides an SSH
> proxy in the host to connect to VMs, while libvirt also has added
> its own host SSH proxy to allow SSH based on libvirt VM name.
>
> For windows guests, there is something called PowerShell Direct
> which exposes PowerShell over vmbus under HyperV. Possibly that
> can be enabled in QEMU too if someone understands windows & vmbus
> enough... ?
>
> With regards,
> Daniel
That makes a lot of sense. Why to support something that is
already written. Though I have a note about Windows. The
approach could be exactly the same - OpenSSH port for Windows
is already known and on top of that VirtIO VSock driver is
available too. Why not?
Den
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux
2024-05-23 7:12 ` Denis V. Lunev
@ 2024-05-23 7:50 ` Daniel P. Berrangé
0 siblings, 0 replies; 8+ messages in thread
From: Daniel P. Berrangé @ 2024-05-23 7:50 UTC (permalink / raw)
To: Denis V. Lunev
Cc: Alexander Ivanov, qemu-devel, michael.roth, kkostiuk,
marcandre.lureau
On Thu, May 23, 2024 at 09:12:51AM +0200, Denis V. Lunev wrote:
> On 5/22/24 18:10, Daniel P. Berrangé wrote:
> > On Wed, May 22, 2024 at 05:06:57PM +0200, Alexander Ivanov wrote:
> > > Add an interactive mode to the guest-exec command in the QEMU Guest Agent
> > > using the VSOCK communication mechanism. It enables interactive sessions
> > > with the executed command in the guest, allowing real-time input/output.
> > >
> > > Introduce "interactive" mode in the GuestExecCaptureOutputMode enumeration
> > > and add optional "cid" and "port" fields to the guest-exec response. In
> > > such a way user can execute guest-exec command, get CID and port number
> > > from the response and connect to the guest server. After connection user
> > > can communicate with the started process. All the data transmitted to the
> > > server is redirected to stdin. Data from stdout and stderr is redirected
> > > to the client. All data blocks are preceded by 32-bit headers (network
> > > byte order): most significant bit contains a sign of stream (stdout - 0,
> > > stderr - 1), all the other bits contain the payload size.
> > Every patch to 'guest-exec' takes us torwards re-inventing yet more
> > SSH/telnet functionality, but a poor simulation of it. For exmaple
> > this still lacks any separation of stdout/stderr streams, just
> > interleaving all their data back to the host. There is also zero
> > access control facilities beyond turning off the 'guest-exec'
> > command entirely.
> >
> > IMHO we should really consider "arbitrary command execution" to be
> > something to be handled by a separate process. Let the guest OS admin
> > decide separately from running QEMU GA, whether they want to enable
> > arbitrary host processes to have a trival privileged backdoor into
> > their guest.
> >
> > systemd now supports exposing SSH over VSOCK, and provides an SSH
> > proxy in the host to connect to VMs, while libvirt also has added
> > its own host SSH proxy to allow SSH based on libvirt VM name.
> >
> > For windows guests, there is something called PowerShell Direct
> > which exposes PowerShell over vmbus under HyperV. Possibly that
> > can be enabled in QEMU too if someone understands windows & vmbus
> > enough... ?
> >
>
> That makes a lot of sense. Why to support something that is
> already written. Though I have a note about Windows. The
> approach could be exactly the same - OpenSSH port for Windows
> is already known and on top of that VirtIO VSock driver is
> available too. Why not?
I've not tested it myself, but I would assume (hope) that the Powershell
Direct feature is available in Windows guests "out of the box". The OpenSSH
+ VSock option would require extra user install work. I tend to favour
things which will "just work" without extra config in the guest.
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux
2024-05-22 16:10 ` Daniel P. Berrangé
2024-05-23 7:12 ` Denis V. Lunev
@ 2024-05-23 7:57 ` Alexander Ivanov
1 sibling, 0 replies; 8+ messages in thread
From: Alexander Ivanov @ 2024-05-23 7:57 UTC (permalink / raw)
To: Daniel P. Berrangé
Cc: qemu-devel, den, michael.roth, kkostiuk, marcandre.lureau
On 5/22/24 18:10, Daniel P. Berrangé wrote:
> On Wed, May 22, 2024 at 05:06:57PM +0200, Alexander Ivanov wrote:
>> Add an interactive mode to the guest-exec command in the QEMU Guest Agent
>> using the VSOCK communication mechanism. It enables interactive sessions
>> with the executed command in the guest, allowing real-time input/output.
>>
>> Introduce "interactive" mode in the GuestExecCaptureOutputMode enumeration
>> and add optional "cid" and "port" fields to the guest-exec response. In
>> such a way user can execute guest-exec command, get CID and port number
>> from the response and connect to the guest server. After connection user
>> can communicate with the started process. All the data transmitted to the
>> server is redirected to stdin. Data from stdout and stderr is redirected
>> to the client. All data blocks are preceded by 32-bit headers (network
>> byte order): most significant bit contains a sign of stream (stdout - 0,
>> stderr - 1), all the other bits contain the payload size.
> Every patch to 'guest-exec' takes us torwards re-inventing yet more
> SSH/telnet functionality, but a poor simulation of it. For exmaple
> this still lacks any separation of stdout/stderr streams, just
There IS separation of stdout/stderr. Receiving data on the host you can
see from which stream it is.
> interleaving all their data back to the host. There is also zero
> access control facilities beyond turning off the 'guest-exec'
> command entirely.
>
> IMHO we should really consider "arbitrary command execution" to be
> something to be handled by a separate process. Let the guest OS admin
> decide separately from running QEMU GA, whether they want to enable
> arbitrary host processes to have a trival privileged backdoor into
> their guest.
>
> systemd now supports exposing SSH over VSOCK, and provides an SSH
> proxy in the host to connect to VMs, while libvirt also has added
> its own host SSH proxy to allow SSH based on libvirt VM name.
>
> For windows guests, there is something called PowerShell Direct
> which exposes PowerShell over vmbus under HyperV. Possibly that
> can be enabled in QEMU too if someone understands windows & vmbus
> enough... ?
>
> With regards,
> Daniel
Otherwise, you are right, it makes sense to use SSH over VSOCK. Thank you.
--
Best regards,
Alexander Ivanov
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2024-05-23 9:56 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-05-22 15:06 [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux Alexander Ivanov
2024-05-22 15:23 ` Test scripts Alexander Ivanov
2024-05-23 9:56 ` Alex Bennée
2024-05-22 15:38 ` [PATCH] qga: Add an interactive mode to guest-exec via VSOCK for Linux Michal Prívozník
2024-05-22 16:10 ` Daniel P. Berrangé
2024-05-23 7:12 ` Denis V. Lunev
2024-05-23 7:50 ` Daniel P. Berrangé
2024-05-23 7:57 ` Alexander Ivanov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).