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.


> +
> +    if (hypervGetWmiClass(Msvm_GuestNetworkAdapterConfiguration, &aConfig) < 
> 0)
> +        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.


> +
> +        entry = entry->next;
> +    }
> +
> +    return nifaces;
> +}
> +
> +
> +static int
> +hypervDomainInterfaceAddresses(virDomainPtr dom,
> +                               virDomainInterfacePtr **ifaces,
> +                               unsigned int source,
> +                               unsigned int flags)
> +{
> +    hypervPrivate *priv = NULL;
> +    char uuid_string[VIR_UUID_STRING_BUFLEN];
> +    g_autoptr(Msvm_ComputerSystem) computerSystem = NULL;
> +    g_autoptr(Msvm_VirtualSystemSettingData) virtualSystemSettingData = NULL;
> +    g_autoptr(Msvm_EthernetPortAllocationSettingData) nets = NULL;
> +    virDomainInterfacePtr *ifacesRet = NULL;
> +    ssize_t ifacesRetCount = 0;
> +    int ret = -1;
> +
> +    virCheckFlags(0, -1);
> +
> +    if (source != VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT) {
> +        virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED,
> +                       _("Unknown IP address data source %1$d"),
> +                       source);
> +        return -1;
> +    }
> +
> +    if (hypervMsvmComputerSystemFromDomain(dom, &computerSystem) < 0)
> +        return -1;
> +
> +    priv = dom->conn->privateData;
> +    virUUIDFormat(dom->uuid, uuid_string);
> +
> +    if (hypervGetMsvmVirtualSystemSettingDataFromUUID(priv,
> +                                                      uuid_string,
> +                                                      
> &virtualSystemSettingData) < 0) {
> +        return -1;
> +    }
> +
> +    if (hypervGetEthernetPortAllocationSD(priv,
> +                                          
> virtualSystemSettingData->data->InstanceID,
> +                                          &nets) < 0) {
> +        return -1;
> +    }
> +
> +    ifacesRetCount = hypervDomainInterfaceAddressesParseList(priv, nets, 
> &ifacesRet);
> +    if (ifacesRetCount < 0)
> +        goto cleanup;
> +
> +    *ifaces = g_steal_pointer(&ifacesRet);
> +    ret = ifacesRetCount;
> +
> + cleanup:
> +    if (ret < 0) {

The only case when 'ret' is <0 here is ...

> +        while (ifacesRetCount > 0) {

... when this is also -1

> +            virDomainInterfaceFree(ifacesRet[--ifacesRetCount]);
> +        }

So all of this is dead code, and also actually due to flawed error
reporting in 'hypervDomainInterfaceAddressesParseList' leaks the array
if it was partially filled.

> +        VIR_FREE(ifacesRet);
> +    }
> +
> +    return ret;
> +}
> +
> +
>  static virHypervisorDriver hypervHypervisorDriver = {
>      .name = "Hyper-V",
>      .connectOpen = hypervConnectOpen, /* 0.9.5 */

I'm wondering what's the point of 'query' above, if it's a leftover from
debugging then I'm okay with that.

With the cleanup of partially filled data fixed:

Reviewed-by: Peter Krempa <[email protected]>

Reply via email to