[PATCH 4/4] hyperv: Implement virDomainInterfaceAddresses()

Michal Privoznik via Devel posted 4 patches 2 days, 5 hours ago
[PATCH 4/4] hyperv: Implement virDomainInterfaceAddresses()
Posted by Michal Privoznik via Devel 2 days, 5 hours ago
From: Michal Privoznik <mprivozn@redhat.com>

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 <mprivozn@redhat.com>
---

Admittedly, the 'virsh domifaddr' output is misaligned after this, but
I'll post a separate patch for that.

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);
+
+    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 (oneIface)
+            VIR_APPEND_ELEMENT(*ifaces, nifaces, oneIface);
+
+        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) {
+        while (ifacesRetCount > 0) {
+            virDomainInterfaceFree(ifacesRet[--ifacesRetCount]);
+        }
+        VIR_FREE(ifacesRet);
+    }
+
+    return ret;
+}
+
+
 static virHypervisorDriver hypervHypervisorDriver = {
     .name = "Hyper-V",
     .connectOpen = hypervConnectOpen, /* 0.9.5 */
@@ -3717,6 +3885,7 @@ static virHypervisorDriver hypervHypervisorDriver = {
     .domainManagedSaveRemove = hypervDomainManagedSaveRemove, /* 0.9.5 */
     .domainSendKey = hypervDomainSendKey, /* 3.6.0 */
     .connectIsAlive = hypervConnectIsAlive, /* 0.9.8 */
+    .domainInterfaceAddresses = hypervDomainInterfaceAddresses, /* 12.1.0 */
 };
 
 
diff --git a/src/hyperv/hyperv_wmi_generator.input b/src/hyperv/hyperv_wmi_generator.input
index 0b342cbfa6..e6053267f8 100644
--- a/src/hyperv/hyperv_wmi_generator.input
+++ b/src/hyperv/hyperv_wmi_generator.input
@@ -866,6 +866,18 @@ class Msvm_SyntheticEthernetPortSettingData
 end
 
 
+class Msvm_GuestNetworkAdapterConfiguration
+    string  InstanceID
+    uint16  ProtocolIFType
+    boolean DHCPEnabled
+    string  IPAddresses[]
+    string  Subnets[]
+    string  DefaultGateways[]
+    string  DNSServers[]
+    uint16  IPAddressOrigins[]
+end
+
+
 class Msvm_EthernetPortAllocationSettingData
     string   InstanceID
     string   Caption
-- 
2.52.0