From 180654c9082a2f3b2038ada31a24067344cb21a7 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 29 Nov 2024 19:02:58 +0100 Subject: [PATCH] feat: vsock support for remote console access Having a remote console access can be helpful for different reasons, e.g. an easy way to have multiple terminals, not using a serial which can be slow to print a lot of text, etc. With this new support, a user can simply use vng like before, and add the new --vsock option: vng --vsock (...) Then in another terminal, it can connect to the existing VM: vng --vsock-connect Advanced users can set a different CID with --vsock-cid, useful when multiple VMs are started in parallel. It is also possible to change the command that is executed when connected inside the VM. By default, 'bash -i' is used, but it is possible to pass something else, e.g. vng --vsock byobu Or zsh, fish, tmux, etc. Or a script, for example to set env vars, change dir and set other dimensions, etc., e.g. starting the VM with: $ vng --vsock "${PWD}/console.sh" ... and connecting to it from another terminal with: $ read -r rows columns <<< "$(stty size)" $ cat <<-EOF > console.sh #! /bin/bash stty rows ${rows} columns ${columns} cd "\${virtme_chdir}" HOME=${HOME} byobu EOF $ chmod +x console.sh $ vng --vsock-connect Note: on my side, 'bash' is a bit weird: I cannot see the command I'm typing. I didn't try to find a solution for a too long time as I'm usually not using bash. Regarding the kernel config, two new config are now required: VSOCKETS and VIRTIO_VSOCKETS. They seem light enough. While at it, fixed a typo in the README file with the --network option. Link: https://github.com/arighi/virtme-ng/discussions/151 Signed-off-by: Matthieu Baerts (NGI0) --- README.md | 29 ++++++++++++++++++++- virtme/commands/configkernel.py | 3 +++ virtme/commands/run.py | 30 +++++++++++++++++++++ virtme/guest/virtme-init | 6 +++++ virtme_ng/run.py | 46 +++++++++++++++++++++++++++++++++ virtme_ng_init | 2 +- 6 files changed, 114 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2956596..a4a7215 100644 --- a/README.md +++ b/README.md @@ -345,6 +345,33 @@ Examples # vmlinux available in the system. ``` + - Connect to a simple remote shell: +``` + # Start the vng instance with vsock support: + $ vng --vsock + + # In a separate terminal run the following command to connect to a remote shell: + $ vng --vsock-connect +``` + + - Connect to a remote shell with proper dimensions, env vars, and using 'Fish': +``` + # Start the vng instance with vsock support: + $ vng --vsock "${PWD}/console.sh" + + # In a separate terminal run the following commands: + $ read -r rows columns <<< "$(stty size)" + $ cat <<-EOF > console.sh + #! /bin/bash + stty rows ${rows} columns ${columns} + cd "\${virtme_chdir}" + HOME=${HOME} + fish # use use zsh, tmux, byobu, screen, etc. + EOF + $ chmod +x console.sh + $ vng --vsock-connect +``` + - Run virtme-ng inside a docker container: ``` $ docker run -it --privileged ubuntu:23.10 /bin/bash @@ -449,7 +476,7 @@ Troubleshooting $ groups | grep "kvm\|libvirt" ``` - - When using `--net bridge` to create a bridged network in the guest you + - When using `--network bridge` to create a bridged network in the guest you may get the following error: ``` ... diff --git a/virtme/commands/configkernel.py b/virtme/commands/configkernel.py index 3d1a919..1ed6592 100644 --- a/virtme/commands/configkernel.py +++ b/virtme/commands/configkernel.py @@ -159,6 +159,9 @@ def arg_fail(message): "CONFIG_ZONE_DEVICE=y", "CONFIG_FUSE_FS=y", "CONFIG_VIRTIO_FS=y", + "##: vsock support", + "CONFIG_VSOCKETS=y", + "CONFIG_VIRTIO_VSOCKETS=y", ] _GENERIC_CONFIG_OPTIONAL = [ diff --git a/virtme/commands/run.py b/virtme/commands/run.py index b491f8b..0b2cbd1 100644 --- a/virtme/commands/run.py +++ b/virtme/commands/run.py @@ -130,6 +130,25 @@ def make_parser() -> argparse.ArgumentParser: help="The MAC address to assign to the NIC interface, e.g. 52:54:00:12:34:56. " + "The last octet will be incremented for the next network devices.", ) + g.add_argument( + "--vsock", + action="store", + default=None, + help="Enable a VSock to communicate from the host to the device and " + + "execute the specified command.", + ) + g.add_argument( + "--vsock-cid", + action="store", + type=int, + default=3, + help="CID for the VSock.", + ) + g.add_argument( + "--vsock-connect", + action="store_true", + help="Connect to a VM using VSock.", + ) g.add_argument( "--balloon", action="store_true", @@ -847,6 +866,13 @@ def is_subpath(path, potential_parent): def do_it() -> int: args = _ARGPARSER.parse_args() + if args.vsock_connect: + tty = os.ttyname(sys.stdin.fileno()) + command = ['socat', f'file:{tty},raw,echo=0', + f'VSOCK-CONNECT:{args.vsock_cid}:1024'] + os.execvp('socat', command) + sys.exit(0) + arch = architectures.get(args.arch) is_native = args.arch == platform.machine() @@ -1383,6 +1409,10 @@ def get_net_mac(index): ] ) + if args.vsock: + kernelargs.extend([f"virtme.vsockexec=`{args.vsock}`"]) + qemuargs.extend(["-device", "vhost-vsock-pci,guest-cid=%d" % args.vsock_cid]) + if args.pwd: rel_pwd = os.path.relpath(os.getcwd(), args.root) if rel_pwd.startswith(".."): diff --git a/virtme/guest/virtme-init b/virtme/guest/virtme-init index 8a3325f..2681ad7 100755 --- a/virtme/guest/virtme-init +++ b/virtme/guest/virtme-init @@ -275,6 +275,12 @@ if cat /proc/cmdline |grep -q -E '(^| )virtme.snapd($| )'; then fi fi +vsock_exec=$(sed -ne "s/.*virtme.vsockexec=\`\(.*\)\`.*/\1/p" /proc/cmdline) +if [[ -n "${vsock_exec}" ]]; then + socat "VSOCK-LISTEN:1024,reuseaddr,fork" \ + "EXEC:\"${vsock_exec}\",pty,stderr,setsid,sigint,sane,echo=0" & +fi + user_cmd=$(sed -ne "s/.*virtme.exec=\`\(.*\)\`.*/\1/p" /proc/cmdline) if [[ -n "${user_cmd}" ]]; then if [[ ! -e "/dev/virtio-ports/virtme.stdin" || diff --git a/virtme_ng/run.py b/virtme_ng/run.py index e95eb83..48744a8 100644 --- a/virtme_ng/run.py +++ b/virtme_ng/run.py @@ -359,6 +359,28 @@ def make_parser(): + "The last octet will be incremented for the next network devices.", ) + parser.add_argument( + "--vsock", + action="store", + const="bash -i", + nargs="?", + help="Enable a VSock to communicate from the host to the device. " + + "An argument can be optionally specified to start a different shell.", + ) + + parser.add_argument( + "--vsock-cid", + action="store", + type=int, + help="CID for the VSock.", + ) + + parser.add_argument( + "--vsock-connect", + action="store_true", + help="Connect to a VM using VSock.", + ) + parser.add_argument( "--disk", "-D", @@ -947,6 +969,24 @@ def _get_virtme_net_mac_address(self, args): else: self.virtme_param["net_mac_address"] = "" + def _get_virtme_vsock(self, args): + if args.vsock is not None: + self.virtme_param["vsock"] = "--vsock '" + args.vsock + "'" + else: + self.virtme_param["vsock"] = "" + + def _get_virtme_vsock_cid(self, args): + if args.vsock_cid is not None: + self.virtme_param["vsock_cid"] = "--vsock-cid " + str(args.vsock_cid) + else: + self.virtme_param["vsock_cid"] = "" + + def _get_virtme_vsock_connect(self, args): + if args.vsock_connect: + self.virtme_param["vsock_connect"] = "--vsock-connect" + else: + self.virtme_param["vsock_connect"] = "" + def _get_virtme_disk(self, args): if args.disk is not None: disk_str = "" @@ -1102,6 +1142,9 @@ def run(self, args): self._get_virtme_mods(args) self._get_virtme_network(args) self._get_virtme_net_mac_address(args) + self._get_virtme_vsock(args) + self._get_virtme_vsock_cid(args) + self._get_virtme_vsock_connect(args) self._get_virtme_disk(args) self._get_virtme_sound(args) self._get_virtme_disable_microvm(args) @@ -1141,6 +1184,9 @@ def run(self, args): + f'{self.virtme_param["mods"]} ' + f'{self.virtme_param["network"]} ' + f'{self.virtme_param["net_mac_address"]} ' + + f'{self.virtme_param["vsock"]} ' + + f'{self.virtme_param["vsock_cid"]} ' + + f'{self.virtme_param["vsock_connect"]} ' + f'{self.virtme_param["disk"]} ' + f'{self.virtme_param["sound"]} ' + f'{self.virtme_param["disable_microvm"]} ' diff --git a/virtme_ng_init b/virtme_ng_init index f9d3c4a..c43ba1c 160000 --- a/virtme_ng_init +++ b/virtme_ng_init @@ -1 +1 @@ -Subproject commit f9d3c4aef44834351fc93ecd1d5f5860f44bc85c +Subproject commit c43ba1ca5d91953a723eb1d6d0f7b4b88da327ca