From nobody Sun May 12 02:27:13 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) client-ip=170.10.133.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1653644890; cv=none; d=zohomail.com; s=zohoarc; b=Mh2J5uWY/uKC7Saw3w8RbCCa1tMpRG6ZpLqHjfDxI2C5KkJ5KNtR50eepCA7X17DflUP6qa66Pz4evH/R7W32wFgIBctnbY976O1RSu5q3sfScfKp3mMgP09rh/RITizneDSxdeIZgQnZipAyuhD2cdzw3opJqAdJx8y6Pt8k+w= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1653644890; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Sender:Subject:To; bh=CXwycN7FStxi5TgIh/0kR0J3iaMwdG+ahAqcAVI/XKA=; b=NWYZPI93ZSpGMtKwromvWRJq9YKiWhDtXLV4gGdUQ9YdrN9uOBHhmBzrYl8fgcd5X9/sSjF+y0mgAZ09sbiHH0LECPnwFag7DxHzR9rrY9D9Aoh6DxUAeDIzL1b+a6Q7XadyAgPe0Up4YTYu7KQn0LJdWRW71bdLyZzqqxvvKOg= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by mx.zohomail.com with SMTPS id 1653644890086693.5927443844962; Fri, 27 May 2022 02:48:10 -0700 (PDT) Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-360-wSg7DcEWN3O_oWNR60OwIA-1; Fri, 27 May 2022 05:48:06 -0400 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.rdu2.redhat.com [10.11.54.6]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 2252B18A6525; Fri, 27 May 2022 09:48:05 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (unknown [10.30.29.100]) by smtp.corp.redhat.com (Postfix) with ESMTP id 36FE52166B29; Fri, 27 May 2022 09:48:04 +0000 (UTC) Received: from mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (localhost [IPv6:::1]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id D5F54194704C; Fri, 27 May 2022 09:48:03 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) by mm-prod-listman-01.mail-001.prod.us-east-1.aws.redhat.com (Postfix) with ESMTP id 097551947040 for ; Fri, 27 May 2022 09:48:02 +0000 (UTC) Received: by smtp.corp.redhat.com (Postfix) id C8DD682872; Fri, 27 May 2022 09:48:02 +0000 (UTC) Received: from localhost.localdomain.com (unknown [10.33.36.86]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7A0F31678F; Fri, 27 May 2022 09:48:00 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1653644888; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=CXwycN7FStxi5TgIh/0kR0J3iaMwdG+ahAqcAVI/XKA=; b=DnMdukN5fGAnESCI0sUDI5YQI4UiR6897/qKeEsf4B105xdGU+MzcjilkPDAZEkCBVyHfG tNGgvdajFVJZCLaGdQPr6el1uwWj4j0vhCZvPtsUJw6vOgBeLPsLrQT97uDgZ2Ep0pkaN6 6gfzhJrC7ZvJ4nBKix8xyYLWZr+hDI0= X-MC-Unique: wSg7DcEWN3O_oWNR60OwIA-1 X-Original-To: libvir-list@listman.corp.redhat.com From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= To: libvir-list@redhat.com Subject: [libvirt PATCH] tools: add virt-qmp-proxy for proxying QMP clients to libvirt QEMU guests Date: Fri, 27 May 2022 10:47:58 +0100 Message-Id: <20220527094758.604621-1-berrange@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.11.54.5 X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Markus Armbruster , John Snow , "Dr . David Alan Gilbert" , qemu-devel@nongnu.org Errors-To: libvir-list-bounces@redhat.com Sender: "libvir-list" X-Scanned-By: MIMEDefang 2.78 on 10.11.54.6 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1653644892276100001 Libvirt provides QMP passthrough APIs for the QEMU driver and these are exposed in virsh. It is not especially pleasant, however, using the raw QMP JSON syntax. QEMU has a tool 'qmp-shell' which can speak QMP and exposes a human friendly interactive shell. It is not possible to use this with libvirt managed guest, however, since only one client can attach to he QMP socket at any point in time. The virt-qmp-proxy tool aims to solve this problem. It opens a UNIX socket and listens for incoming client connections, speaking QMP on the connected socket. It will forward any QMP commands received onto the running libvirt QEMU guest, and forward any replies back to the QMP client. $ virsh start demo $ virt-qmp-proxy demo demo.qmp & $ qmp-shell demo.qmp Welcome to the QMP low-level shell! Connected to QEMU 6.2.0 (QEMU) query-kvm { "return": { "enabled": true, "present": true } } Note this tool of course has the same risks as the raw libvirt QMP passthrough. It is safe to run query commands to fetch information but commands which change the QEMU state risk disrupting libvirt's management of QEMU, potentially resulting in data loss/corruption in the worst case. Signed-off-by: Daniel P. Berrang=C3=A9 --- CC'ing QEMU since this is likely of interest to maintainers and users who work with QEMU and libvirt Note this impl is fairly crude in that it assumes it is receiving the QMP commands linewise one at a time. None the less it is good enough to work with qmp-shell already, so I figured it was worth exposing to the world. It also lacks support for forwarding events back to the QMP client. docs/manpages/meson.build | 1 + docs/manpages/virt-qmp-proxy.rst | 123 ++++++++++++++++++++++++++++ tools/meson.build | 5 ++ tools/virt-qmp-proxy | 133 +++++++++++++++++++++++++++++++ 4 files changed, 262 insertions(+) create mode 100644 docs/manpages/virt-qmp-proxy.rst create mode 100755 tools/virt-qmp-proxy diff --git a/docs/manpages/meson.build b/docs/manpages/meson.build index ba673cf472..4162a9969a 100644 --- a/docs/manpages/meson.build +++ b/docs/manpages/meson.build @@ -18,6 +18,7 @@ docs_man_files =3D [ { 'name': 'virt-pki-query-dn', 'section': '1', 'install': true }, { 'name': 'virt-pki-validate', 'section': '1', 'install': true }, { 'name': 'virt-qemu-run', 'section': '1', 'install': conf.has('WITH_QEM= U') }, + { 'name': 'virt-qmp-proxy', 'section': '1', 'install': conf.has('WITH_QE= MU') }, { 'name': 'virt-xml-validate', 'section': '1', 'install': true }, =20 { 'name': 'libvirt-guests', 'section': '8', 'install': conf.has('WITH_LI= BVIRTD') }, diff --git a/docs/manpages/virt-qmp-proxy.rst b/docs/manpages/virt-qmp-prox= y.rst new file mode 100644 index 0000000000..94679406ab --- /dev/null +++ b/docs/manpages/virt-qmp-proxy.rst @@ -0,0 +1,123 @@ +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +virt-qmp-proxy +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +-------------------------------------------------- +Expose a QMP proxy server for a libvirt QEMU guest +-------------------------------------------------- + +:Manual section: 1 +:Manual group: Virtualization Support + +.. contents:: + + +SYNOPSIS +=3D=3D=3D=3D=3D=3D=3D=3D + +``virt-qmp-proxy`` [*OPTION*]... *DOMAIN* *QMP-SOCKET-PATH* + + +DESCRIPTION +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +This tool provides a way to expose a QMP proxy server that communicates +with a QEMU guest managed by libvirt. This enables standard QMP client +tools to interact with libvirt managed guests. + +**NOTE: use of this tool will result in the running QEMU guest being +marked as tainted.** It is strongly recommended that this tool *only be +used to send commands which query information* about the running guest. +If this tool is used to make changes to the state of the guest, this +may have negative interactions with the QEMU driver, resulting in an +inability to manage the guest operation thereafter, and in the worst +case **potentially lead to data loss or corruption**. + +The ``virt-qmp-proxy`` program will listen on a UNIX socket for incoming +client connections, and run the QMP protocol over the connection. Any +commands received will be sent to the running libvirt guest, and replies +sent back. + +The ``virt-qemu-proxy`` program may be interrupted (eg Ctrl-C) when it +is no longer required. The libvirt QEMU guest will continue running. + + +OPTIONS +=3D=3D=3D=3D=3D=3D=3D + +*DOMAIN* + +The ID or UUID or Name of the libvirt QEMU guest. + +*QMP-SOCKET-PATH* + +The filesystem path at which to run the QMP server, listening for +incoming connections. + +``-c`` *CONNECTION-URI* +``--connect``\ =3D\ *CONNECTION-URI* + +The URI for the connection to the libvirt QEMU driver. If omitted, +a URI will be auto-detected. + +``-v``, ``--verbose`` + +Run in verbose mode, printing all QMP commands and replies that +are handled. + +``-h``, ``--help`` + +Display the command line help. + + +EXIT STATUS +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Upon successful shutdown, an exit status of 0 will be set. Upon +failure a non-zero status will be set. + + +AUTHOR +=3D=3D=3D=3D=3D=3D + +Daniel P. Berrang=C3=A9 + + +BUGS +=3D=3D=3D=3D + +Please report all bugs you discover. This should be done via either: + +#. the mailing list + + `https://libvirt.org/contact.html `_ + +#. the bug tracker + + `https://libvirt.org/bugs.html `_ + +Alternatively, you may report bugs to your software distributor / vendor. + +NOTE: at this time there is no support for forwarding QMP events back +to the clients + +COPYRIGHT +=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Copyright (C) 2022 by Red Hat, Inc. + + +LICENSE +=3D=3D=3D=3D=3D=3D=3D + +``virt-qemu-proxy`` is distributed under the terms of the GNU LGPL v2+. +This is free software; see the source for copying conditions. There +is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE + + +SEE ALSO +=3D=3D=3D=3D=3D=3D=3D=3D + +virsh(1), `https://libvirt.org/ `_, +`QMP reference `_ diff --git a/tools/meson.build b/tools/meson.build index bb28a904dc..4e959ecf0b 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -320,6 +320,11 @@ if conf.has('WITH_LIBVIRTD') endif endif =20 +if conf.has('WITH_QEMU') + install_data('virt-qmp-proxy', + install_dir: bindir) +endif + if bash_completion_dep.found() subdir('bash-completion') endif diff --git a/tools/virt-qmp-proxy b/tools/virt-qmp-proxy new file mode 100755 index 0000000000..57f9759fab --- /dev/null +++ b/tools/virt-qmp-proxy @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 + +import argparse +import libvirt +import libvirt_qemu +import os +import re +import socket +import sys +import json + + +def get_domain(uri, domstr): + conn =3D libvirt.open(uri) + + dom =3D None + if re.match(r'^\d+$', domstr): + dom =3D conn.lookupByID(int(domstr)) + elif re.match(r'^[+a-f0-9]+$', domstr): + dom =3D conn.lookupByUUIDString(domstr) + else: + dom =3D conn.lookupByName(domstr) + + if not dom.isActive(): + raise Exception( + "Domain must be running to validate measurement") + + return conn, dom + + +def qmp_server(conn, dom, client, verbose): + ver =3D conn.getVersion() + major =3D int(ver / 1000000) % 1000 + minor =3D int(ver / 1000) % 1000 + micro =3D ver % 1000 + + greetingobj =3D { + "QMP": { + "version": { + "qemu": { + "major": major, + "minor": minor, + "micro": micro, + }, + "package": f"qemu-{major}.{minor}.{micro}", + }, + "capabilities": [ + "oob" + ], + } + } + greeting =3D json.dumps(greetingobj) + "\r\n" + if verbose: + print(greeting, end=3D'') + client.send(greeting.encode("utf-8")) + + while True: + # XXX shouldn't blindly assume this one read + # will fully capture one-and-only-one cmd + cmd =3D client.recv(1024).decode('utf8') + if verbose: + print(cmd) + + if cmd =3D=3D "": + break + + if "qmp_capabilities" in cmd: + capabilitiesobj =3D { + "return": {}, + } + capabilities =3D json.dumps(capabilitiesobj) + "\r\n" + if verbose: + print(capabilities, end=3D'') + client.send(capabilities.encode("utf-8")) + continue + + id =3D None + if "id" in cmd: + id =3D cmd[id] + + res =3D libvirt_qemu.qemuMonitorCommand(dom, cmd, 0) + + resobj =3D json.loads(res) + del resobj["id"] + if id is not None: + resobj["id"] =3D id + res =3D json.dumps(resobj) + "\r\n" + if verbose: + print(res, end=3D'') + + client.send(res.encode('utf8')) + + +def parse_commandline(): + parser =3D argparse.ArgumentParser(description=3D"Libvirt QMP proxy") + parser.add_argument("--connect", "-c", + help=3D"Libvirt QEMU driver connection URI") + parser.add_argument("--verbose", "-v", action=3D'store_true', + help=3D"Display QMP traffic") + parser.add_argument("domain", metavar=3D"DOMAIN", + help=3D"Libvirt guest domain ID/UUID/Name") + parser.add_argument("sockpath", metavar=3D"QMP-SOCK-PATH", + help=3D"UNIX socket path for QMP server") + + return parser.parse_args() + + +def main(): + args =3D parse_commandline() + + conn, dom =3D get_domain(args.connect, args.domain) + + if conn.getType() !=3D "QEMU": + raise Exception("QMP proxy requires a QEMU driver connection not %= s" % + conn.getType()) + + sock =3D socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + if os.path.exists(args.sockpath): + os.unlink(args.sockpath) + sock.bind(args.sockpath) + sock.listen(1) + + while True: + client, peeraddr =3D sock.accept() + qmp_server(conn, dom, client, args.verbose) + + +try: + main() + sys.exit(0) +except Exception as e: + print("%s: %s" % (sys.argv[0], str(e))) + sys.exit(1) --=20 2.36.1