From nobody Wed Apr 1 22:36:27 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=pass(p=reject dis=none) header.from=lists.libvirt.org ARC-Seal: i=1; a=rsa-sha256; t=1775031805; cv=none; d=zohomail.com; s=zohoarc; b=oIE/wwolCngx2v+TeHnaImxU9kGZ6ZAj1NPI0bFCXUAcvSaO7i8NwG759pxMz8hRKI3+9nJe6TkOrd2B9wwUH/XpL4rOhDgQeFVGKkCB2nvIR+BY89FalVXxraUecWRuqOtIt+ko9CFmcbcaU8ADSEdf4sZwWqEfylWV70PrDtE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1775031805; h=Content-Type:Content-Transfer-Encoding:Date:Date:From:From:List-Subscribe:List-Post:List-Owner:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:Reply-To:Subject:Subject:To:To:Message-Id:Cc; bh=cRPXtK2uHvd0o2WAG2U2uLKtlaRG50to/TDlALFdO9I=; b=d0fZn7GsDQ2y5JQvjsjxiQcjwe7GSHRhrzGTkMGeFx7Ku+79sirbds1/NUs7dD3IhMEP/Lts7QCDtRCuC6+Dp2xjtkEYXKOaIHQydBcntP8brc/5k1Rf34hfHhhikIF5sdo20ZrtLXkHzujrL14SNWp5i0ZfZbZEm4L1QAeUyDo= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=pass header.from= (p=reject dis=none) Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 1775031805671671.4840297811479; Wed, 1 Apr 2026 01:23:25 -0700 (PDT) Received: by lists.libvirt.org (Postfix, from userid 993) id 6045E3F8AF; Wed, 1 Apr 2026 04:23:24 -0400 (EDT) Received: from [172.19.199.12] (lists.libvirt.org [8.43.85.245]) by lists.libvirt.org (Postfix) with ESMTP id B03174180B; Wed, 1 Apr 2026 04:22:30 -0400 (EDT) Received: by lists.libvirt.org (Postfix, from userid 993) id 8BCDD3F308; Wed, 1 Apr 2026 04:22:26 -0400 (EDT) Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id 7872E3F27C for ; Wed, 1 Apr 2026 04:22:23 -0400 (EDT) Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-488-UsIZ1gNPMzu5Ta-7RZcK-g-1; Wed, 01 Apr 2026 04:22:21 -0400 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 1292018002F4 for ; Wed, 1 Apr 2026 08:22:21 +0000 (UTC) Received: from moe (unknown [10.43.3.236]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 71D221954102 for ; Wed, 1 Apr 2026 08:22:20 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=0.2 required=5.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,HELO_MISC_IP,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED,SPF_PASS,URI_TRY_3LD autolearn=no autolearn_force=no version=4.0.1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1775031743; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=cRPXtK2uHvd0o2WAG2U2uLKtlaRG50to/TDlALFdO9I=; b=ctaZb6HMDmTzJehSG7tFjz9kVhzP7DGkWUk5+rUiSQsTcC/4LMPjsM+4vbA+5OygAeuS0r ffPbu1sd3AexOhZx7BPcAvh1baDYtru08ROilNQaf0tTB7SO3aEinyusOIiT2RhUvwVjSD VqOJwN70Bt2NEF0IRRBVKG9IKawUyDo= X-MC-Unique: UsIZ1gNPMzu5Ta-7RZcK-g-1 X-Mimecast-MFC-AGG-ID: UsIZ1gNPMzu5Ta-7RZcK-g_1775031741 To: devel@lists.libvirt.org Subject: [PATCH] hyperv: Implement virDomainGetGuestInfo() Date: Wed, 1 Apr 2026 10:22:16 +0200 Message-ID: <3f5f750efadbc99d069928cf99b5b78ec81f0825.1775031736.git.mprivozn@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: 6XtgbqV-gBGcifGxteWtVN0oYyFx5XsWnEeAbZXDoEo_1775031741 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable Message-ID-Hash: 6WAIHUCOBMGH3MZCKH5ESI5GW6I2VNIC X-Message-ID-Hash: 6WAIHUCOBMGH3MZCKH5ESI5GW6I2VNIC X-MailFrom: mprivozn@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-devel.lists.libvirt.org-0; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Michal Privoznik via Devel Reply-To: Michal Privoznik X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1775031807967154100 Content-Type: text/plain; charset="utf-8"; x-default="true" From: Michal Privoznik The hyperv hypervisor also has guest agent, in fact multiple ones [1][2]. The first one, KVP, is for storing Key-Value Pairs and in fact it's already used by our hyperv driver when querying domifaddr (see v12.1.0-rc1~148 for more info). Anyhow, the KVP service is capable of more, it can provide guest OS info, guest FQDN and others. These informations are exposed via GuestIntrinsicExchangeItems member of the Msvm_KvpExchangeComponent struct [3]. You may have noticed the member is an array of strings, well those strings are in fact XML documents. For instance: 6.12.61-1-lts OSBuildNumber 2 This is a bit messy to work with, because it's not like in QEMU's world where each type of guest info (virDomainGuestInfoTypes) corresponds 1:1 to a guest agent command. Hence the lookupTable in hypervGetServicesProcessOne(). NB, the original jira issue asks for exposing plain fact whether KVP daemon is running inside the guest and this commit implements seemingly different feature. Well, thing is, in case of QEMU there's a domain XML part where guest agent is configured and where we expose whether there's somebody listening inside the guest. But in case of hyperv there's no to be configured as communication with KVP daemon happens through vmbus [4]. Users are advised to call the virDomainGetGuestInfo() API with non-zero 'types' argument and if they get an error with VIR_ERR_AGENT_UNRESPONSIVE code then the KVP daemon is not running. 1: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/= tools/hv/hv_kvp_daemon.c 2: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/= tools/hv/hv_vss_daemon.c 3: https://learn.microsoft.com/en-us/windows/win32/hyperv_v2/msvm-kvpexchan= gecomponent 4: https://docs.kernel.org/virt/hyperv/vmbus.html Resolves: https://redhat.atlassian.net/browse/RHEL-147661 Signed-off-by: Michal Privoznik --- src/hyperv/hyperv_driver.c | 262 ++++++++++++++++++++++++++ src/hyperv/hyperv_wmi_generator.input | 43 +++++ 2 files changed, 305 insertions(+) diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c index 18e06f0e2a..7ad80bc8ec 100644 --- a/src/hyperv/hyperv_driver.c +++ b/src/hyperv/hyperv_driver.c @@ -4379,6 +4379,267 @@ hypervDomainSnapshotGetParent(virDomainSnapshotPtr = snapshot, } =20 =20 +static int +hypervGetServicesProcessOne(const char *xmlStr, + unsigned int supportedTypes, + unsigned int *processedTypes, + virTypedParamList *ifaceAddrList, + virTypedParamList *list) +{ + g_autoptr(xmlDoc) xml =3D NULL; + g_autoptr(xmlXPathContext) ctxt =3D NULL; + g_autofree char *name =3D NULL; + g_autofree char *val =3D NULL; + const struct { + const char *name; + const char *key; + unsigned int type; + } lookupTable[] =3D { + { "FullyQualifiedDomainName", VIR_DOMAIN_GUEST_INFO_HOSTNAME_HOSTN= AME, + VIR_DOMAIN_GUEST_INFO_HOSTNAME }, + { "OSBuildNumber", VIR_DOMAIN_GUEST_INFO_OS_KERNEL_RELEASE, + VIR_DOMAIN_GUEST_INFO_OS }, + { "OSName", VIR_DOMAIN_GUEST_INFO_OS_NAME, + VIR_DOMAIN_GUEST_INFO_OS}, + { "OSVersion", VIR_DOMAIN_GUEST_INFO_OS_VERSION, + VIR_DOMAIN_GUEST_INFO_OS}, + { "OSMajorVersion", VIR_DOMAIN_GUEST_INFO_OS_VERSION_ID, + VIR_DOMAIN_GUEST_INFO_OS}, + { "ProcessorArchitecture", VIR_DOMAIN_GUEST_INFO_OS_MACHINE, + VIR_DOMAIN_GUEST_INFO_OS}, + }; + size_t i; + + VIR_DEBUG("xmlStr=3D%s", xmlStr); + + if (!(xml =3D virXMLParseStringCtxt(xmlStr, _("guest intrinsic exchang= e data"), &ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("cannot parse guest intrinsic exchange data")); + return -1; + } + + name =3D virXPathString("string(//INSTANCE/PROPERTY[@NAME=3D'Name']/VA= LUE/text())", ctxt); + if (!name) { + VIR_DEBUG("Skipping unexpected guest intrinsic XML (no name): %s",= xmlStr); + return 0; + } + + val =3D virXPathString("string(//INSTANCE/PROPERTY[@NAME=3D'Data']/VAL= UE/text())", ctxt); + if (!val) { + VIR_DEBUG("Skipping unexpected guest intrinsic XML (no value): %s"= , xmlStr); + return 0; + } + + for (i =3D 0; i < G_N_ELEMENTS(lookupTable); i++) { + if (supportedTypes & lookupTable[i].type && + STREQ(name, lookupTable[i].name)) { + virTypedParamListAddString(list, val, "%s", lookupTable[i].key= ); + *processedTypes |=3D lookupTable[i].type; + return 1; + } + } + + if (supportedTypes & VIR_DOMAIN_GUEST_INFO_INTERFACES && + (STREQ(name, "NetworkAddressIPv4") || STREQ(name, "NetworkAddressI= Pv6"))) { + g_auto(GStrv) tmp =3D NULL; + unsigned int newAddrCount =3D 0; + size_t addrIndex =3D 0; + const char *addrType =3D "ipv4"; + + if (STREQ(name, "NetworkAddressIPv6")) + addrType =3D "ipv6"; + + if (virTypedParamListFetch(ifaceAddrList, NULL, &addrIndex) < 0) + return -1; + + /* Per Microsoft docs: + * - NetworkAddressIPv4 + * A string that contains a semicolon-delimited list of the IPv4 + * addresses currently assigned to the guest virtual machine. + * - NetworkAddressIPv6 + * A string that contains a semicolon-delimited list of the IPv6 + * addresses currently assigned to the guest virtual machine. + */ + + tmp =3D g_strsplit(val, ";", 0); + newAddrCount =3D g_strv_length(tmp); + + for (i =3D 0; i < newAddrCount; i++) { + virTypedParamListAddString(ifaceAddrList, addrType, + VIR_DOMAIN_GUEST_INFO_IF_PREFIX "0"= VIR_DOMAIN_GUEST_INFO_IF_SUFFIX_ADDR_PREFIX "%zu" VIR_DOMAIN_GUEST_INFO_IF= _SUFFIX_ADDR_SUFFIX_TYPE, + addrIndex + i); + virTypedParamListAddString(ifaceAddrList, tmp[i], + VIR_DOMAIN_GUEST_INFO_IF_PREFIX "0"= VIR_DOMAIN_GUEST_INFO_IF_SUFFIX_ADDR_PREFIX "%zu" VIR_DOMAIN_GUEST_INFO_IF= _SUFFIX_ADDR_SUFFIX_ADDR, + addrIndex + i); + *processedTypes |=3D VIR_DOMAIN_GUEST_INFO_INTERFACES; + } + + return 1; + } + + return 0; +} + + +static int +hypervDomainGetGuestInfoImpl(hypervPrivate *priv, + const char *uuid, + unsigned int supportedTypes, + bool reportUnsupported, + virTypedParamList *list) +{ + g_autoptr(Msvm_KvpExchangeComponent) kvpList =3D NULL; + Msvm_KvpExchangeComponent_Data *data =3D NULL; + g_auto(virBuffer) query =3D VIR_BUFFER_INITIALIZER; + g_autoptr(virTypedParamList) ifaceAddrList =3D virTypedParamListNew(); + unsigned int processedTypes =3D 0; + size_t nIfaceAddresses =3D 0; + size_t i; + + virBufferEscapeSQL(&query, + MSVM_KVPEXCHANGECOMPONENT_WQL_SELECT + "WHERE SystemName =3D '%s'", + uuid); + + if (hypervGetWmiClass(Msvm_KvpExchangeComponent, &kvpList) < 0) + return -1; + + if (!kvpList) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Empty reply from guest. Maybe KVP service is not= running?")); + return -1; + } + + data =3D kvpList->data; + + for (i =3D 0; i < data->GuestIntrinsicExchangeItems.count; i++) { + const char *xml =3D ((const char **)data->GuestIntrinsicExchangeIt= ems.data)[i]; + + if (hypervGetServicesProcessOne(xml, supportedTypes, + &processedTypes, ifaceAddrList, li= st) < 0) { + return -1; + } + } + + if (virTypedParamListFetch(ifaceAddrList, NULL, &nIfaceAddresses) < 0) + return -1; + + if (nIfaceAddresses > 0) { + virTypedParamListAddUInt(list, 1, VIR_DOMAIN_GUEST_INFO_IF_COUNT); + virTypedParamListAddUInt(list, nIfaceAddresses, + VIR_DOMAIN_GUEST_INFO_IF_PREFIX "0" VIR_D= OMAIN_GUEST_INFO_IF_SUFFIX_ADDR_COUNT); + virTypedParamListConcat(list, &ifaceAddrList); + } + + if (processedTypes !=3D supportedTypes && + reportUnsupported) { + uint16_t status =3D ((uint16_t *)kvpList->data->OperationalStatus.= data)[0]; + + /* Per Microsoft doc, the status value can be: + * 2 - OK, + * 3 - Degraded, + * 7 - Non-recoverable error, + * 12 - No contact, + * 13 - Lost communication. + */ + switch (status) { + case 3: + virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", + _("Unable to fetch all requested information. D= ata Exchange service (KVP) is degraded")); + break; + case 7: + virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", + _("Unable to fetch all requested information. D= ata Exchange service (KVP) encountered a non-recoverable error")); + break; + case 12: + case 13: + virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", + _("Unable to fetch all requested information. D= ata Exchange service (KVP) is not running")); + break; + default: + virReportError(VIR_ERR_AGENT_UNRESPONSIVE, "%s", + _("Unable to fetch all requested information")); + break; + } + return -1; + } + + return 0; +} + + +static int +hypervDomainGetGuestInfoCheckSupport(unsigned int types, + unsigned int *supportedTypes) +{ + const unsigned int hypervSupportedTypes =3D + VIR_DOMAIN_GUEST_INFO_OS | + VIR_DOMAIN_GUEST_INFO_HOSTNAME | + VIR_DOMAIN_GUEST_INFO_INTERFACES; + + if (types =3D=3D 0) { + *supportedTypes =3D hypervSupportedTypes; + return 0; + } + + *supportedTypes =3D types & hypervSupportedTypes; + + if (types !=3D *supportedTypes) { + virReportError(VIR_ERR_INVALID_ARG, + _("unsupported guest information types '0x%1$x'"), + types & ~hypervSupportedTypes); + return -1; + } + + return 0; +} + + +static int +hypervDomainGetGuestInfo(virDomainPtr domain, + unsigned int types, + virTypedParameterPtr *params, + int *nparams, + unsigned int flags) +{ + hypervPrivate *priv =3D NULL; + char uuid_string[VIR_UUID_STRING_BUFLEN]; + unsigned int supportedTypes =3D 0; + bool reportUnsupported =3D types !=3D 0; + g_autoptr(Msvm_ComputerSystem) computerSystem =3D NULL; + g_autoptr(virTypedParamList) list =3D virTypedParamListNew(); + + virCheckFlags(0, -1); + + if (hypervDomainGetGuestInfoCheckSupport(types, &supportedTypes) < 0) + return -1; + + if (hypervMsvmComputerSystemFromDomain(domain, &computerSystem) < 0) + return -1; + + if (!hypervIsMsvmComputerSystemActive(computerSystem, NULL)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + + return -1; + } + + priv =3D domain->conn->privateData; + virUUIDFormat(domain->uuid, uuid_string); + + if (hypervDomainGetGuestInfoImpl(priv, + uuid_string, supportedTypes, + reportUnsupported, list) < 0) { + return -1; + } + + if (virTypedParamListSteal(list, params, nparams) < 0) + return -1; + + return 0; +} + + static virHypervisorDriver hypervHypervisorDriver =3D { .name =3D "Hyper-V", .connectOpen =3D hypervConnectOpen, /* 0.9.5 */ @@ -4452,6 +4713,7 @@ static virHypervisorDriver hypervHypervisorDriver =3D= { .domainHasCurrentSnapshot =3D hypervDomainHasCurrentSnapshot, /* 12.2.= 0 */ .domainSnapshotCurrent =3D hypervDomainSnapshotCurrent, /* 12.2.0 */ .domainSnapshotGetParent =3D hypervDomainSnapshotGetParent, /* 12.2.0 = */ + .domainGetGuestInfo =3D hypervDomainGetGuestInfo, /* 12.3.0 */ }; =20 =20 diff --git a/src/hyperv/hyperv_wmi_generator.input b/src/hyperv/hyperv_wmi_= generator.input index b3cd9d19fb..1f8d4258e1 100644 --- a/src/hyperv/hyperv_wmi_generator.input +++ b/src/hyperv/hyperv_wmi_generator.input @@ -1261,3 +1261,46 @@ class Msvm_SecuritySettingData boolean EncryptStateAndVmMigrationTraffic boolean VirtualizationBasedSecurityOptOut end + +class Msvm_KvpExchangeComponent + string InstanceID + string Caption + string Description + string ElementName + datetime InstallDate + string Name + uint16 OperationalStatus[] + string StatusDescriptions[] + string Status + uint16 HealthState + uint16 CommunicationStatus + uint16 DetailedStatus + uint16 OperatingStatus + uint16 PrimaryStatus + uint16 EnabledState + string OtherEnabledState + uint16 RequestedState + uint16 EnabledDefault + datetime TimeOfLastStateChange + uint16 AvailableRequestedStates[] + uint16 TransitioningToState + string SystemCreationClassName + string SystemName + string CreationClassName + string DeviceID + boolean PowerManagementSupported + uint16 PowerManagementCapabilities[] + uint16 Availability + uint16 StatusInfo + uint32 LastErrorCode + string ErrorDescription + boolean ErrorCleared + string OtherIdentifyingInfo[] + uint64 PowerOnHours + uint64 TotalPowerOnHours + string IdentifyingDescriptions[] + uint16 AdditionalAvailability[] + uint64 MaxQuiesceTime + string GuestExchangeItems[] + string GuestIntrinsicExchangeItems[] +end --=20 2.52.0