On 2/10/26 18:40, Peter Krempa wrote:
> On Fri, Feb 06, 2026 at 14:52:37 +0100, Michal Privoznik via Devel wrote:
>> From: Michal Privoznik <[email protected]>
>>
>> The virDomainInterfaceAddresses() API accepts @source argument,
>> but since this is hyperv, we can't really use _SRC_LEASE (we
>> didn't spawn any dnsmasq there), not _SRC_ARP. The only source
>> that's more or less usable is _SRC_AGENT. Okay, there's no QEMU
>> guest agent running, but hyperv has its own guest agent. In my
>> testing (with Linux guest) I had to install 'hyperv' package and
>> then enable 'hv_kvp_daemon.service'. After that,
>> Msvm_GuestNetworkAdapterConfiguration struct [1] contained guest
>> IP addresses.
>>
>> There's one caveat though: the interface name
>> (virDomainInterface::name). We don't fetch that one even for
>> hypervDomainGetXMLDesc() case. And there's no <target dev=''/>
>> either nor device alias (v12.0.0-43-g4009126f17). So just put
>> InstanceID there for now, which is this long path, with some
>> UUIDs, e.g.:
>>
>>
>> Microsoft:5C58E5F2-946E-490F-B81D-6E2A7328640D\C85554E0-2B3B-487C-A557-D230BFF5F9E6\
>>
>> But hey, at least it's unique.
>>
>> 1:
>> https://learn.microsoft.com/en-us/windows/win32/hyperv_v2/msvm-guestnetworkadapterconfiguration
>> Resolves: https://issues.redhat.com/browse/RHEL-145306
>> Signed-off-by: Michal Privoznik <[email protected]>
>> ---
>>
>> Admittedly, the 'virsh domifaddr' output is misaligned after this, but
>> I'll post a separate patch for that.
>
> Disclaimer: I have absolutely no idea about the hyperv specific bits.
>
>
>>
>> src/hyperv/hyperv_driver.c | 169 ++++++++++++++++++++++++++
>> src/hyperv/hyperv_wmi_generator.input | 12 ++
>> 2 files changed, 181 insertions(+)
>>
>> diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c
>> index fbc76544df..c25cb91c13 100644
>> --- a/src/hyperv/hyperv_driver.c
>> +++ b/src/hyperv/hyperv_driver.c
>> @@ -3654,6 +3654,174 @@ hypervDomainSendKey(virDomainPtr domain, unsigned
>> int codeset,
>> }
>>
>>
>> +static int
>> +hypervDomainInterfaceAddressesParseOne(hypervPrivate *priv,
>> +
>> Msvm_EthernetPortAllocationSettingData *net,
>> + virDomainInterfacePtr *oneIfaceRet)
>> +{
>> + g_autoptr(virDomainInterface) iface = NULL;
>> + g_autoptr(Msvm_SyntheticEthernetPortSettingData) sepsd = NULL;
>> + g_autoptr(Msvm_GuestNetworkAdapterConfiguration) aConfig = NULL;
>> + g_auto(virBuffer) query = VIR_BUFFER_INITIALIZER;
>> + virMacAddr macAddr = { 0 };
>> + char macAddrStr[VIR_MAC_STRING_BUFLEN] = { 0 };
>> +
>> + VIR_DEBUG("Parsing ethernet adapter '%s'", net->data->InstanceID);
>> +
>> + iface = g_new0(virDomainInterface, 1);
>> + iface->name = g_strdup(net->data->InstanceID);
>> +
>> + if (hypervDomainDefParseEthernetAdapterMAC(priv, net, &macAddr) < 0)
>> + return -1;
>> +
>> + iface->hwaddr = g_strdup(virMacAddrFormat(&macAddr, macAddrStr));
>> +
>> + virBufferAsprintf(&query,
>> + "ASSOCIATORS OF {%s} "
>> + "WHERE AssocClass=Msvm_SettingDataComponent "
>> + "ResultClass=Msvm_GuestNetworkAdapterConfiguration",
>> + net->data->Parent);
>
> 'query' is unused after this.
It's not.
>
>
>> +
>> + if (hypervGetWmiClass(Msvm_GuestNetworkAdapterConfiguration, &aConfig)
>> < 0)
This ^^ is actually a macro:
#define hypervGetWmiClass(type, class) \
hypervGetWmiClassList(priv, type ## _WmiInfo, &query, (hypervObject
**)class)
>> + return -1;
>> +
>> + if (aConfig) {
>> + size_t nAddr = aConfig->data->IPAddresses.count;
>> + size_t i;
>> +
>> + if (aConfig->data->Subnets.count != nAddr) {
>> + virReportError(VIR_ERR_INTERNAL_ERROR,
>> + _("the number of IP addresses (%1$zu) does not
>> match the number of subnets (%2$d)"),
>> + nAddr, aConfig->data->Subnets.count);
>> + return -1;
>> + }
>> +
>> + iface->addrs = g_new0(virDomainIPAddress, nAddr);
>> + iface->naddrs = nAddr;
>> + for (i = 0; i < nAddr; i++) {
>> + const char *ipAddrStr = ((const char **)
>> aConfig->data->IPAddresses.data)[i];
>> + const char *subnetAddrStr = ((const char **)
>> aConfig->data->Subnets.data)[i];
>> + virDomainIPAddressPtr ip = &iface->addrs[i];
>> + int family;
>> + int prefix;
>> +
>> + VIR_DEBUG("ipAddrStr='%s' subnetAddrStr='%s'",
>> + ipAddrStr, subnetAddrStr);
>> +
>> + ip->addr = g_strdup(ipAddrStr);
>> + family = virSocketAddrNumericFamily(ipAddrStr);
>> + if (family == AF_INET6) {
>> + ip->type = VIR_IP_ADDR_TYPE_IPV6;
>> + } else if (family == AF_INET) {
>> + ip->type = VIR_IP_ADDR_TYPE_IPV4;
>> + } else {
>> + virReportError(VIR_ERR_INTERNAL_ERROR,
>> + _("unknown IP address family of '%1$s'"),
>> + ipAddrStr);
>> + return -1;
>> + }
>> +
>> + prefix = virSocketAddrSubnetToPrefix(subnetAddrStr);
>> + if (prefix < 0) {
>> + virReportError(VIR_ERR_INTERNAL_ERROR,
>> + _("unexpected subnet mask '%1$s'"),
>> + subnetAddrStr);
>> + return -1;
>> + }
>> + ip->prefix = prefix;
>> + }
>> + }
>> +
>> + *oneIfaceRet = g_steal_pointer(&iface);
>> + return 0;
>> +}
>> +
>> +
>> +static ssize_t
>> +hypervDomainInterfaceAddressesParseList(hypervPrivate *priv,
>> +
>> Msvm_EthernetPortAllocationSettingData *nets,
>> + virDomainInterfacePtr **ifaces)
>> +{
>> + Msvm_EthernetPortAllocationSettingData *entry = nets;
>> + size_t nifaces = 0;
>> +
>> + while (entry) {
>> + virDomainInterfacePtr oneIface = NULL;
>> +
>> + if (hypervDomainInterfaceAddressesParseOne(priv, entry, &oneIface)
>> < 0)
>> + return -1;
>
> If this fails ....
>
>> +
>> + if (oneIface)
>> + VIR_APPEND_ELEMENT(*ifaces, nifaces, oneIface);
>
>
> ... after this was partially filled, the caller has no way of figuring
> out how many entries to free. This function should cleanup after itself
> internally.
>
Ah, okay. Will post v2.
>
>> +
>> + entry = entry->next;
>> + }
>> +
>> + return nifaces;
>> +}
>> +
>> +
Michal