[libvirt] [PATCH v10 0/4] Introduce API to query IP addresses for given domain

In v10 - Remove 'if' tests before access of hwaddr as it is always present - Switch remote protocol to use remote_nonnull_string for hwaddr as it can never be NUL - Misc typos Eric pointed out Nehal J Wani (4): domifaddr: Implement the public APIs domifaddr: Implement the remote protocol domifaddr: Implement the API for qemu domifaddr: Add virsh support daemon/remote.c | 124 ++++++++++++++++++++++++ include/libvirt/libvirt-domain.h | 32 ++++++ src/driver-hypervisor.h | 6 ++ src/libvirt-domain.c | 123 +++++++++++++++++++++++ src/libvirt_public.syms | 2 + src/qemu/qemu_agent.c | 204 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 4 + src/qemu/qemu_driver.c | 175 +++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 103 ++++++++++++++++++++ src/remote/remote_protocol.x | 37 ++++++- src/remote_protocol-structs | 25 +++++ tests/qemuagenttest.c | 188 ++++++++++++++++++++++++++++++++++++ tools/virsh-domain-monitor.c | 146 ++++++++++++++++++++++++++++ tools/virsh.pod | 16 +++ 14 files changed, 1184 insertions(+), 1 deletion(-) -- 2.1.0

From: Nehal J Wani <nehaljw.kkd1@gmail.com> Define helper function virDomainInterfaceFree, which allows the upper layer application to free the domain interface object conveniently. The API is going to provide multiple methods by flags, e.g. * Query guest agent * Parse DHCP lease file include/libvirt/libvirt-domain.h * Define virDomainInterfaceAddresses, virDomainInterfaceFree * Define structs virDomainInterface, virDomainIPAddress src/driver-hypervisor.h: * Define domainInterfaceAddresses src/libvirt-domain.c: * Implement virDomainInterfaceAddresses * Implement virDomainInterfaceFree src/libvirt_public.syms: * Export the new symbols Signed-off-by: Nehal J Wani <nehaljw.kkd1@gmail.com> --- include/libvirt/libvirt-domain.h | 32 ++++++++++ src/driver-hypervisor.h | 6 ++ src/libvirt-domain.c | 123 +++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 2 + 4 files changed, 163 insertions(+) diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 9487b80..8cc7fe8 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -3725,5 +3725,37 @@ typedef struct _virTypedParameter virMemoryParameter; */ typedef virMemoryParameter *virMemoryParameterPtr; +typedef enum { + VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE = 0, /* Parse DHCP lease file */ + VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT = 1, /* Query qemu guest agent */ + +# ifdef VIR_ENUM_SENTINELS + VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LAST +# endif +} virDomainInterfaceAddressesSource; + +typedef struct _virDomainInterfaceIPAddress virDomainIPAddress; +typedef virDomainIPAddress *virDomainIPAddressPtr; +struct _virDomainInterfaceIPAddress { + int type; /* virIPAddrType */ + char *addr; /* IP address */ + unsigned int prefix; /* IP address prefix */ +}; + +typedef struct _virDomainInterface virDomainInterface; +typedef virDomainInterface *virDomainInterfacePtr; +struct _virDomainInterface { + char *name; /* interface name */ + char *hwaddr; /* hardware address */ + unsigned int naddrs; /* number of items in @addrs */ + virDomainIPAddressPtr addrs; /* array of IP addresses */ +}; + +int virDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags); + +void virDomainInterfaceFree(virDomainInterfacePtr iface); #endif /* __VIR_LIBVIRT_DOMAIN_H__ */ diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index c2b4cd8..0e729c4 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -1178,6 +1178,11 @@ typedef int unsigned int cellCount, unsigned int flags); +typedef int +(*virDrvDomainInterfaceAddresses)(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags); typedef struct _virHypervisorDriver virHypervisorDriver; typedef virHypervisorDriver *virHypervisorDriverPtr; @@ -1404,6 +1409,7 @@ struct _virHypervisorDriver { virDrvConnectGetAllDomainStats connectGetAllDomainStats; virDrvNodeAllocPages nodeAllocPages; virDrvDomainGetFSInfo domainGetFSInfo; + virDrvDomainInterfaceAddresses domainInterfaceAddresses; }; diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 04545fd..cf36d30 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -11338,3 +11338,126 @@ virDomainFSInfoFree(virDomainFSInfoPtr info) VIR_FREE(info->devAlias[i]); VIR_FREE(info->devAlias); } + +/** + * virDomainInterfaceAddresses: + * @dom: domain object + * @ifaces: pointer to an array of pointers pointing to interface objects + * @source: one of the virDomainInterfaceAddressesSource constants + * @flags: currently unused, pass zero + * + * Return a pointer to the allocated array of pointers to interfaces + * present in given domain along with their IP and MAC addresses. Note that + * single interface can have multiple or even 0 IP addresses. + * + * This API dynamically allocates the virDomainInterfacePtr struct based on + * how many interfaces domain @dom has, usually there's 1:1 correlation. The + * count of the interfaces is returned as the return value. + * + * If @source is VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE, the DHCP lease + * file associated with any virtual networks will be examined to obtain + * the interface addresses. This only returns data for interfaces which + * are connected to virtual networks managed by libvirt. + * + * If @source is VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT, a configured + * guest agent is needed for successful return from this API. Moreover, if + * guest agent is used then the interface name is the one seen by guest OS. + * To match such interface with the one from @dom XML use MAC address or IP + * range. + * + * @ifaces->name and @ifaces->hwaddr are never NULL. + * + * The caller *must* free @ifaces when no longer needed. Usual use case + * looks like this: + * + * virDomainInterfacePtr *ifaces = NULL; + * int ifaces_count = 0; + * size_t i, j; + * virDomainPtr dom = ... obtain a domain here ...; + * + * if ((ifaces_count = virDomainInterfaceAddresses(dom, &ifaces, + * VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE)) < 0) + * goto cleanup; + * + * ... do something with returned values, for example: + * for (i = 0; i < ifaces_count; i++) { + * printf("name: %s", ifaces[i]->name); + * printf(" hwaddr: %s", ifaces[i]->hwaddr); + * + * for (j = 0; j < ifaces[i]->naddrs; j++) { + * virDomainIPAddressPtr ip_addr = ifaces[i]->addrs + j; + * printf("[addr: %s prefix: %d type: %d]", + * ip_addr->addr, ip_addr->prefix, ip_addr->type); + * } + * printf("\n"); + * } + * + * cleanup: + * if (ifaces) + * for (i = 0; i < ifaces_count; i++) + * virDomainInterfaceFree(ifaces[i]); + * free(ifaces); + * + * Returns the number of interfaces on success, -1 in case of error. + */ +int +virDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "ifaces=%p, source=%d, flags=%x", ifaces, source, flags); + + virResetLastError(); + + conn = dom->conn; + + if (ifaces) + *ifaces = NULL; + virCheckDomainReturn(dom, -1); + virCheckNonNullArgGoto(ifaces, error); + virCheckReadOnlyGoto(conn->flags, error); + + if (conn->driver->domainInterfaceAddresses) { + int ret; + ret = conn->driver->domainInterfaceAddresses(dom, ifaces, source, flags); + if (ret < 0) + goto error; + return ret; + } + + virReportError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + + error: + virDispatchError(dom->conn); + return -1; +} + + +/** + * virDomainInterfaceFree: + * @iface: an interface object + * + * Free the interface object. The data structure is + * freed and should not be used thereafter. If @iface + * is NULL, then this method has no effect. + */ +void +virDomainInterfaceFree(virDomainInterfacePtr iface) +{ + size_t i; + + if (!iface) + return; + + VIR_FREE(iface->name); + VIR_FREE(iface->hwaddr); + + for (i = 0; i < iface->naddrs; i++) + VIR_FREE(iface->addrs[i].addr); + VIR_FREE(iface->addrs); + + VIR_FREE(iface); +} diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 34852c1..64c455d 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -699,6 +699,8 @@ LIBVIRT_1.2.14 { global: virDomainIOThreadsInfoFree; virDomainGetIOThreadsInfo; + virDomainInterfaceAddresses; + virDomainInterfaceFree; } LIBVIRT_1.2.12; # .... define new API here using predicted next version number .... -- 2.1.0

On 03/11/2015 07:09 AM, Daniel P. Berrange wrote:
From: Nehal J Wani <nehaljw.kkd1@gmail.com>
Define helper function virDomainInterfaceFree, which allows the upper layer application to free the domain interface object conveniently.
The API is going to provide multiple methods by flags, e.g. * Query guest agent * Parse DHCP lease file
include/libvirt/libvirt-domain.h * Define virDomainInterfaceAddresses, virDomainInterfaceFree * Define structs virDomainInterface, virDomainIPAddress
src/driver-hypervisor.h: * Define domainInterfaceAddresses
src/libvirt-domain.c: * Implement virDomainInterfaceAddresses * Implement virDomainInterfaceFree
src/libvirt_public.syms: * Export the new symbols
Signed-off-by: Nehal J Wani <nehaljw.kkd1@gmail.com> --- include/libvirt/libvirt-domain.h | 32 ++++++++++ src/driver-hypervisor.h | 6 ++ src/libvirt-domain.c | 123 +++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 2 + 4 files changed, 163 insertions(+)
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index 9487b80..8cc7fe8 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -3725,5 +3725,37 @@ typedef struct _virTypedParameter virMemoryParameter; */ typedef virMemoryParameter *virMemoryParameterPtr;
+typedef enum { + VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE = 0, /* Parse DHCP lease file */ + VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT = 1, /* Query qemu guest agent */ + +# ifdef VIR_ENUM_SENTINELS + VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LAST +# endif +} virDomainInterfaceAddressesSource; + +typedef struct _virDomainInterfaceIPAddress virDomainIPAddress; +typedef virDomainIPAddress *virDomainIPAddressPtr; +struct _virDomainInterfaceIPAddress { + int type; /* virIPAddrType */ + char *addr; /* IP address */ + unsigned int prefix; /* IP address prefix */ +}; + +typedef struct _virDomainInterface virDomainInterface; +typedef virDomainInterface *virDomainInterfacePtr; +struct _virDomainInterface { + char *name; /* interface name */ + char *hwaddr; /* hardware address */ + unsigned int naddrs; /* number of items in @addrs */ + virDomainIPAddressPtr addrs; /* array of IP addresses */ +}; + +int virDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags); + +void virDomainInterfaceFree(virDomainInterfacePtr iface);
#endif /* __VIR_LIBVIRT_DOMAIN_H__ */ diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index c2b4cd8..0e729c4 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -1178,6 +1178,11 @@ typedef int unsigned int cellCount, unsigned int flags);
+typedef int +(*virDrvDomainInterfaceAddresses)(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags);
typedef struct _virHypervisorDriver virHypervisorDriver; typedef virHypervisorDriver *virHypervisorDriverPtr; @@ -1404,6 +1409,7 @@ struct _virHypervisorDriver { virDrvConnectGetAllDomainStats connectGetAllDomainStats; virDrvNodeAllocPages nodeAllocPages; virDrvDomainGetFSInfo domainGetFSInfo; + virDrvDomainInterfaceAddresses domainInterfaceAddresses; };
diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 04545fd..cf36d30 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -11338,3 +11338,126 @@ virDomainFSInfoFree(virDomainFSInfoPtr info) VIR_FREE(info->devAlias[i]); VIR_FREE(info->devAlias); } + +/** + * virDomainInterfaceAddresses: + * @dom: domain object + * @ifaces: pointer to an array of pointers pointing to interface objects + * @source: one of the virDomainInterfaceAddressesSource constants + * @flags: currently unused, pass zero + * + * Return a pointer to the allocated array of pointers to interfaces + * present in given domain along with their IP and MAC addresses. Note that + * single interface can have multiple or even 0 IP addresses. + * + * This API dynamically allocates the virDomainInterfacePtr struct based on + * how many interfaces domain @dom has, usually there's 1:1 correlation. The + * count of the interfaces is returned as the return value. + * + * If @source is VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE, the DHCP lease + * file associated with any virtual networks will be examined to obtain + * the interface addresses. This only returns data for interfaces which + * are connected to virtual networks managed by libvirt. + * + * If @source is VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT, a configured + * guest agent is needed for successful return from this API. Moreover, if + * guest agent is used then the interface name is the one seen by guest OS. + * To match such interface with the one from @dom XML use MAC address or IP + * range. + * + * @ifaces->name and @ifaces->hwaddr are never NULL. + * + * The caller *must* free @ifaces when no longer needed. Usual use case + * looks like this: + * + * virDomainInterfacePtr *ifaces = NULL; + * int ifaces_count = 0; + * size_t i, j; + * virDomainPtr dom = ... obtain a domain here ...; + * + * if ((ifaces_count = virDomainInterfaceAddresses(dom, &ifaces, + * VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE)) < 0) + * goto cleanup; + * + * ... do something with returned values, for example: + * for (i = 0; i < ifaces_count; i++) { + * printf("name: %s", ifaces[i]->name); + * printf(" hwaddr: %s", ifaces[i]->hwaddr); + * + * for (j = 0; j < ifaces[i]->naddrs; j++) { + * virDomainIPAddressPtr ip_addr = ifaces[i]->addrs + j; + * printf("[addr: %s prefix: %d type: %d]", + * ip_addr->addr, ip_addr->prefix, ip_addr->type); + * } + * printf("\n"); + * } + * + * cleanup: + * if (ifaces)
since ifaces_count could be negative... if (ifaces && ifaces_count > 0)
+ * for (i = 0; i < ifaces_count; i++) + * virDomainInterfaceFree(ifaces[i]); + * free(ifaces); + * + * Returns the number of interfaces on success, -1 in case of error. + */ +int +virDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "ifaces=%p, source=%d, flags=%x", ifaces, source, flags); + + virResetLastError(); + + conn = dom->conn;
If 'dom' is NULL, then there's a pointer deref (considering check below)
+ + if (ifaces) + *ifaces = NULL; + virCheckDomainReturn(dom, -1); + virCheckNonNullArgGoto(ifaces, error); + virCheckReadOnlyGoto(conn->flags, error);
I think I ran into this when I was adding the libvirt-perl code - since this is a get interface - does it really matter if it's readonly? Also, I'd move both conn = dom->conn and "if (ifaces) *ifaces = NULL;" to here
+ + if (conn->driver->domainInterfaceAddresses) { + int ret; + ret = conn->driver->domainInterfaceAddresses(dom, ifaces, source, flags); + if (ret < 0) + goto error; + return ret; + } + + virReportError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + + error: + virDispatchError(dom->conn); + return -1; +} + + +/** + * virDomainInterfaceFree: + * @iface: an interface object + * + * Free the interface object. The data structure is + * freed and should not be used thereafter. If @iface + * is NULL, then this method has no effect. + */ +void +virDomainInterfaceFree(virDomainInterfacePtr iface) +{ + size_t i; + + if (!iface) + return; + + VIR_FREE(iface->name); + VIR_FREE(iface->hwaddr); + + for (i = 0; i < iface->naddrs; i++) + VIR_FREE(iface->addrs[i].addr); + VIR_FREE(iface->addrs); + + VIR_FREE(iface); +} diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 34852c1..64c455d 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -699,6 +699,8 @@ LIBVIRT_1.2.14 { global: virDomainIOThreadsInfoFree; virDomainGetIOThreadsInfo; + virDomainInterfaceAddresses; + virDomainInterfaceFree; } LIBVIRT_1.2.12;
This will have some merge conflicts, but nothing unusual...
# .... define new API here using predicted next version number ....
ACK - with the cleanups John

On Mon, Mar 16, 2015 at 08:42:18AM -0400, John Ferlan wrote:
On 03/11/2015 07:09 AM, Daniel P. Berrange wrote:
From: Nehal J Wani <nehaljw.kkd1@gmail.com>
Define helper function virDomainInterfaceFree, which allows the upper layer application to free the domain interface object conveniently.
The API is going to provide multiple methods by flags, e.g. * Query guest agent * Parse DHCP lease file
include/libvirt/libvirt-domain.h * Define virDomainInterfaceAddresses, virDomainInterfaceFree * Define structs virDomainInterface, virDomainIPAddress
src/driver-hypervisor.h: * Define domainInterfaceAddresses
src/libvirt-domain.c: * Implement virDomainInterfaceAddresses * Implement virDomainInterfaceFree
src/libvirt_public.syms: * Export the new symbols
Signed-off-by: Nehal J Wani <nehaljw.kkd1@gmail.com> --- include/libvirt/libvirt-domain.h | 32 ++++++++++ src/driver-hypervisor.h | 6 ++ src/libvirt-domain.c | 123 +++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 2 + 4 files changed, 163 insertions(+)
+ * for (i = 0; i < ifaces_count; i++) + * virDomainInterfaceFree(ifaces[i]); + * free(ifaces); + * + * Returns the number of interfaces on success, -1 in case of error. + */ +int +virDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "ifaces=%p, source=%d, flags=%x", ifaces, source, flags); + + virResetLastError(); + + conn = dom->conn;
If 'dom' is NULL, then there's a pointer deref (considering check below)
+ + if (ifaces) + *ifaces = NULL; + virCheckDomainReturn(dom, -1); + virCheckNonNullArgGoto(ifaces, error); + virCheckReadOnlyGoto(conn->flags, error);
I think I ran into this when I was adding the libvirt-perl code - since this is a get interface - does it really matter if it's readonly?
In this case it matters because we need to block access to the QEMU guest agent.
Also, I'd move both conn = dom->conn and "if (ifaces) *ifaces = NULL;" to here
I'll just delete the 'conn' var - it is fairly pointless and just causes problems. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

From: Nehal J Wani <nehaljw.kkd1@gmail.com> daemon/remote.c * Define remoteSerializeDomainInterface, remoteDispatchDomainInterfaceAddresses src/remote/remote_driver.c * Define remoteDomainInterfaceAddresses src/remote/remote_protocol.x * New RPC procedure: REMOTE_PROC_DOMAIN_INTERFACE_ADDRESSES * Define structs remote_domain_ip_addr, remote_domain_interface, remote_domain_interfaces_addresse_args, remote_domain_interface_addresses_ret * Introduce upper bounds (to handle DoS attacks): REMOTE_DOMAIN_INTERFACE_MAX = 2048 REMOTE_DOMAIN_IP_ADDR_MAX = 2048 Restrictions on the maximum number of aliases per interface were removed after kernel v2.0, and theoretically, at present, there are no upper limits on number of interfaces per virtual machine and on the number of IP addresses per interface. src/remote_protocol-structs * New structs added Signed-off-by: Nehal J Wani <nehaljw.kkd1@gmail.com> --- daemon/remote.c | 124 +++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 103 +++++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 37 ++++++++++++- src/remote_protocol-structs | 25 +++++++++ 4 files changed, 288 insertions(+), 1 deletion(-) diff --git a/daemon/remote.c b/daemon/remote.c index ff64eeb..f2b970a 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -6510,6 +6510,130 @@ remoteDispatchDomainGetFSInfo(virNetServerPtr server ATTRIBUTE_UNUSED, } +static int +remoteSerializeDomainInterface(virDomainInterfacePtr *ifaces, + unsigned int ifaces_count, + remote_domain_interface_addresses_ret *ret) +{ + size_t i, j; + + if (ifaces_count > REMOTE_DOMAIN_INTERFACE_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Number of interfaces, %d exceeds the max limit: %d"), + ifaces_count, REMOTE_DOMAIN_INTERFACE_MAX); + return -1; + } + + if (VIR_ALLOC_N(ret->ifaces.ifaces_val, ifaces_count) < 0) + return -1; + + ret->ifaces.ifaces_len = ifaces_count; + + for (i = 0; i < ifaces_count; i++) { + virDomainInterfacePtr iface = ifaces[i]; + remote_domain_interface *iface_ret = &(ret->ifaces.ifaces_val[i]); + + if ((VIR_STRDUP(iface_ret->name, iface->name)) < 0) + goto cleanup; + + if ((VIR_STRDUP(iface_ret->hwaddr, iface->hwaddr)) < 0) + goto cleanup; + + if (iface->naddrs > REMOTE_DOMAIN_IP_ADDR_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Number of interfaces, %d exceeds the max limit: %d"), + iface->naddrs, REMOTE_DOMAIN_IP_ADDR_MAX); + goto cleanup; + } + + if (VIR_ALLOC_N(iface_ret->addrs.addrs_val, + iface->naddrs) < 0) + goto cleanup; + + iface_ret->addrs.addrs_len = iface->naddrs; + + for (j = 0; j < iface->naddrs; j++) { + virDomainIPAddressPtr ip_addr = &(iface->addrs[j]); + remote_domain_ip_addr *ip_addr_ret = + &(iface_ret->addrs.addrs_val[j]); + + if (VIR_STRDUP(ip_addr_ret->addr, ip_addr->addr) < 0) + goto cleanup; + + ip_addr_ret->prefix = ip_addr->prefix; + ip_addr_ret->type = ip_addr->type; + } + } + + return 0; + + cleanup: + if (ret->ifaces.ifaces_val) { + for (i = 0; i < ifaces_count; i++) { + remote_domain_interface *iface_ret = &(ret->ifaces.ifaces_val[i]); + VIR_FREE(iface_ret->name); + VIR_FREE(iface_ret->hwaddr); + for (j = 0; j < iface_ret->addrs.addrs_len; j++) { + remote_domain_ip_addr *ip_addr = + &(iface_ret->addrs.addrs_val[j]); + VIR_FREE(ip_addr->addr); + } + } + VIR_FREE(ret->ifaces.ifaces_val); + } + + return -1; +} + + +static int +remoteDispatchDomainInterfaceAddresses(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + remote_domain_interface_addresses_args *args, + remote_domain_interface_addresses_ret *ret) +{ + size_t i; + int rv = -1; + virDomainPtr dom = NULL; + virDomainInterfacePtr *ifaces = NULL; + int ifaces_count = 0; + struct daemonClientPrivate *priv = + virNetServerClientGetPrivateData(client); + + if (!priv->conn) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + if (!(dom = get_nonnull_domain(priv->conn, args->dom))) + goto cleanup; + + if ((ifaces_count = virDomainInterfaceAddresses(dom, &ifaces, args->source, args->flags)) < 0) + goto cleanup; + + if (remoteSerializeDomainInterface(ifaces, ifaces_count, ret) < 0) + goto cleanup; + + rv = 0; + + cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + + virObjectUnref(dom); + + if (ifaces && ifaces_count > 0) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces[i]); + VIR_FREE(ifaces); + } + + return rv; +} + + /*----- Helpers. -----*/ /* get_nonnull_domain and get_nonnull_network turn an on-wire diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 42dab9d..b261d9c 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -7909,6 +7909,108 @@ remoteDomainGetFSInfo(virDomainPtr dom, } +static int +remoteDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags) +{ + int rv = -1; + size_t i, j; + + virDomainInterfacePtr *ifaces_ret = NULL; + remote_domain_interface_addresses_args args; + remote_domain_interface_addresses_ret ret; + + struct private_data *priv = dom->conn->privateData; + + args.source = source; + args.flags = flags; + make_nonnull_domain(&args.dom, dom); + + remoteDriverLock(priv); + + memset(&ret, 0, sizeof(ret)); + + if (call(dom->conn, priv, 0, REMOTE_PROC_DOMAIN_INTERFACE_ADDRESSES, + (xdrproc_t)xdr_remote_domain_interface_addresses_args, + (char *)&args, + (xdrproc_t)xdr_remote_domain_interface_addresses_ret, + (char *)&ret) == -1) { + goto done; + } + + if (ret.ifaces.ifaces_len > REMOTE_DOMAIN_INTERFACE_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Number of interfaces, %d exceeds the max limit: %d"), + ret.ifaces.ifaces_len, REMOTE_DOMAIN_INTERFACE_MAX); + goto cleanup; + } + + if (ret.ifaces.ifaces_len && + VIR_ALLOC_N(ifaces_ret, ret.ifaces.ifaces_len) < 0) + goto cleanup; + + for (i = 0; i < ret.ifaces.ifaces_len; i++) { + virDomainInterfacePtr iface; + remote_domain_interface *iface_ret = &(ret.ifaces.ifaces_val[i]); + + if (VIR_ALLOC(ifaces_ret[i]) < 0) + goto cleanup; + + iface = ifaces_ret[i]; + + if (VIR_STRDUP(iface->name, iface_ret->name) < 0) + goto cleanup; + + if (VIR_STRDUP(iface->hwaddr, iface_ret->hwaddr) < 0) + goto cleanup; + + if (iface_ret->addrs.addrs_len > REMOTE_DOMAIN_IP_ADDR_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Number of interfaces, %d exceeds the max limit: %d"), + iface_ret->addrs.addrs_len, REMOTE_DOMAIN_IP_ADDR_MAX); + goto cleanup; + } + + iface->naddrs = iface_ret->addrs.addrs_len; + + if (iface->naddrs) { + if (VIR_ALLOC_N(iface->addrs, iface->naddrs) < 0) + goto cleanup; + + for (j = 0; j < iface->naddrs; j++) { + virDomainIPAddressPtr ip_addr = &(iface->addrs[j]); + remote_domain_ip_addr *ip_addr_ret = + &(iface_ret->addrs.addrs_val[j]); + + if (VIR_STRDUP(ip_addr->addr, ip_addr_ret->addr) < 0) + goto cleanup; + + ip_addr->prefix = ip_addr_ret->prefix; + ip_addr->type = ip_addr_ret->type; + } + } + } + *ifaces = ifaces_ret; + ifaces_ret = NULL; + + rv = ret.ifaces.ifaces_len; + + cleanup: + if (ifaces_ret) { + for (i = 0; i < ret.ifaces.ifaces_len; i++) + virDomainInterfaceFree(ifaces_ret[i]); + VIR_FREE(ifaces_ret); + } + xdr_free((xdrproc_t)xdr_remote_domain_interface_addresses_ret, + (char *) &ret); + done: + remoteDriverUnlock(priv); + return rv; +} + + /* get_nonnull_domain and get_nonnull_network turn an on-wire * (name, uuid) pair into virDomainPtr or virNetworkPtr object. * These can return NULL if underlying memory allocations fail, @@ -8253,6 +8355,7 @@ static virHypervisorDriver hypervisor_driver = { .connectGetAllDomainStats = remoteConnectGetAllDomainStats, /* 1.2.8 */ .nodeAllocPages = remoteNodeAllocPages, /* 1.2.9 */ .domainGetFSInfo = remoteDomainGetFSInfo, /* 1.2.11 */ + .domainInterfaceAddresses = remoteDomainInterfaceAddresses, /* 1.2.14 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 4ea535e..b511b00 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -259,6 +259,12 @@ const REMOTE_DOMAIN_FSINFO_MAX = 256; /* Upper limit on number of disks per mountpoint in fsinfo */ const REMOTE_DOMAIN_FSINFO_DISKS_MAX = 256; +/* Upper limit on number of interfaces per domain */ +const REMOTE_DOMAIN_INTERFACE_MAX = 2048; + +/* Upper limit on number of IP addresses per interface */ +const REMOTE_DOMAIN_IP_ADDR_MAX = 2048; + /* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */ typedef opaque remote_uuid[VIR_UUID_BUFLEN]; @@ -3170,6 +3176,29 @@ struct remote_domain_get_fsinfo_ret { unsigned int ret; }; +struct remote_domain_ip_addr { + int type; + remote_nonnull_string addr; + unsigned int prefix; +}; + +struct remote_domain_interface { + remote_nonnull_string name; + remote_nonnull_string hwaddr; + remote_domain_ip_addr addrs<REMOTE_DOMAIN_IP_ADDR_MAX>; +}; + +struct remote_domain_interface_addresses_args { + remote_nonnull_domain dom; + unsigned int source; + unsigned int flags; +}; + +struct remote_domain_interface_addresses_ret { + remote_domain_interface ifaces<REMOTE_DOMAIN_INTERFACE_MAX>; +}; + + /*----- Protocol. -----*/ /* Define the program number, protocol version and procedure numbers here. */ @@ -5593,5 +5622,11 @@ enum remote_procedure { * @generate: none * @acl: domain:read */ - REMOTE_PROC_DOMAIN_GET_IOTHREADS_INFO = 351 + REMOTE_PROC_DOMAIN_GET_IOTHREADS_INFO = 351, + + /** + * @generate: none + * @acl: domain:read + */ + REMOTE_PROC_DOMAIN_INTERFACE_ADDRESSES = 352 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 907bcd4..632576b 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -2630,6 +2630,30 @@ struct remote_domain_get_fsinfo_ret { } info; u_int ret; }; +struct remote_domain_ip_addr { + int type; + remote_nonnull_string addr; + u_int prefix; +}; +struct remote_domain_interface { + remote_nonnull_string name; + remote_nonnull_string hwaddr; + struct { + u_int addrs_len; + remote_domain_ip_addr * addrs_val; + } addrs; +}; +struct remote_domain_interface_addresses_args { + remote_nonnull_domain dom; + u_int source; + u_int flags; +}; +struct remote_domain_interface_addresses_ret { + struct { + u_int ifaces_len; + remote_domain_interface * ifaces_val; + } ifaces; +}; enum remote_procedure { REMOTE_PROC_CONNECT_OPEN = 1, REMOTE_PROC_CONNECT_CLOSE = 2, @@ -2982,4 +3006,5 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_GET_FSINFO = 349, REMOTE_PROC_DOMAIN_DEFINE_XML_FLAGS = 350, REMOTE_PROC_DOMAIN_GET_IOTHREADS_INFO = 351, + REMOTE_PROC_DOMAIN_INTERFACE_ADDRESSES = 352, }; -- 2.1.0

On 03/11/2015 07:09 AM, Daniel P. Berrange wrote:
From: Nehal J Wani <nehaljw.kkd1@gmail.com>
daemon/remote.c * Define remoteSerializeDomainInterface, remoteDispatchDomainInterfaceAddresses
src/remote/remote_driver.c * Define remoteDomainInterfaceAddresses
src/remote/remote_protocol.x * New RPC procedure: REMOTE_PROC_DOMAIN_INTERFACE_ADDRESSES * Define structs remote_domain_ip_addr, remote_domain_interface, remote_domain_interfaces_addresse_args, remote_domain_interface_addresses_ret * Introduce upper bounds (to handle DoS attacks): REMOTE_DOMAIN_INTERFACE_MAX = 2048 REMOTE_DOMAIN_IP_ADDR_MAX = 2048 Restrictions on the maximum number of aliases per interface were removed after kernel v2.0, and theoretically, at present, there are no upper limits on number of interfaces per virtual machine and on the number of IP addresses per interface.
src/remote_protocol-structs * New structs added
Signed-off-by: Nehal J Wani <nehaljw.kkd1@gmail.com> --- daemon/remote.c | 124 +++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 103 +++++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 37 ++++++++++++- src/remote_protocol-structs | 25 +++++++++ 4 files changed, 288 insertions(+), 1 deletion(-)
diff --git a/daemon/remote.c b/daemon/remote.c index ff64eeb..f2b970a 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -6510,6 +6510,130 @@ remoteDispatchDomainGetFSInfo(virNetServerPtr server ATTRIBUTE_UNUSED, }
+static int +remoteSerializeDomainInterface(virDomainInterfacePtr *ifaces, + unsigned int ifaces_count, + remote_domain_interface_addresses_ret *ret) +{ + size_t i, j; + + if (ifaces_count > REMOTE_DOMAIN_INTERFACE_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Number of interfaces, %d exceeds the max limit: %d"), + ifaces_count, REMOTE_DOMAIN_INTERFACE_MAX); + return -1; + } + + if (VIR_ALLOC_N(ret->ifaces.ifaces_val, ifaces_count) < 0) + return -1; + + ret->ifaces.ifaces_len = ifaces_count; + + for (i = 0; i < ifaces_count; i++) { + virDomainInterfacePtr iface = ifaces[i]; + remote_domain_interface *iface_ret = &(ret->ifaces.ifaces_val[i]); + + if ((VIR_STRDUP(iface_ret->name, iface->name)) < 0) + goto cleanup; + + if ((VIR_STRDUP(iface_ret->hwaddr, iface->hwaddr)) < 0) + goto cleanup; + + if (iface->naddrs > REMOTE_DOMAIN_IP_ADDR_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Number of interfaces, %d exceeds the max limit: %d"), + iface->naddrs, REMOTE_DOMAIN_IP_ADDR_MAX); + goto cleanup; + } + + if (VIR_ALLOC_N(iface_ret->addrs.addrs_val, + iface->naddrs) < 0) + goto cleanup; + + iface_ret->addrs.addrs_len = iface->naddrs; + + for (j = 0; j < iface->naddrs; j++) { + virDomainIPAddressPtr ip_addr = &(iface->addrs[j]); + remote_domain_ip_addr *ip_addr_ret = + &(iface_ret->addrs.addrs_val[j]); + + if (VIR_STRDUP(ip_addr_ret->addr, ip_addr->addr) < 0) + goto cleanup; + + ip_addr_ret->prefix = ip_addr->prefix; + ip_addr_ret->type = ip_addr->type; + } + } + + return 0; + + cleanup: + if (ret->ifaces.ifaces_val) { + for (i = 0; i < ifaces_count; i++) { + remote_domain_interface *iface_ret = &(ret->ifaces.ifaces_val[i]); + VIR_FREE(iface_ret->name); + VIR_FREE(iface_ret->hwaddr); + for (j = 0; j < iface_ret->addrs.addrs_len; j++) { + remote_domain_ip_addr *ip_addr = + &(iface_ret->addrs.addrs_val[j]); + VIR_FREE(ip_addr->addr); + } + } + VIR_FREE(ret->ifaces.ifaces_val); + } + + return -1; +} + + +static int +remoteDispatchDomainInterfaceAddresses(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + remote_domain_interface_addresses_args *args, + remote_domain_interface_addresses_ret *ret) +{ + size_t i; + int rv = -1; + virDomainPtr dom = NULL; + virDomainInterfacePtr *ifaces = NULL; + int ifaces_count = 0; + struct daemonClientPrivate *priv = + virNetServerClientGetPrivateData(client); + + if (!priv->conn) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + if (!(dom = get_nonnull_domain(priv->conn, args->dom))) + goto cleanup; + + if ((ifaces_count = virDomainInterfaceAddresses(dom, &ifaces, args->source, args->flags)) < 0) + goto cleanup; + + if (remoteSerializeDomainInterface(ifaces, ifaces_count, ret) < 0) + goto cleanup; + + rv = 0; + + cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + + virObjectUnref(dom); + + if (ifaces && ifaces_count > 0) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces[i]); + VIR_FREE(ifaces); + }
Based on something else seen.... The VIR_FREE(ifaces) should be outside the if {}
+ + return rv; +} + + /*----- Helpers. -----*/
/* get_nonnull_domain and get_nonnull_network turn an on-wire diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 42dab9d..b261d9c 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -7909,6 +7909,108 @@ remoteDomainGetFSInfo(virDomainPtr dom, }
+static int +remoteDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags) +{ + int rv = -1; + size_t i, j; + + virDomainInterfacePtr *ifaces_ret = NULL; + remote_domain_interface_addresses_args args; + remote_domain_interface_addresses_ret ret; + + struct private_data *priv = dom->conn->privateData; + + args.source = source; + args.flags = flags; + make_nonnull_domain(&args.dom, dom); + + remoteDriverLock(priv); + + memset(&ret, 0, sizeof(ret)); + + if (call(dom->conn, priv, 0, REMOTE_PROC_DOMAIN_INTERFACE_ADDRESSES, + (xdrproc_t)xdr_remote_domain_interface_addresses_args, + (char *)&args, + (xdrproc_t)xdr_remote_domain_interface_addresses_ret, + (char *)&ret) == -1) { + goto done; + } + + if (ret.ifaces.ifaces_len > REMOTE_DOMAIN_INTERFACE_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Number of interfaces, %d exceeds the max limit: %d"), + ret.ifaces.ifaces_len, REMOTE_DOMAIN_INTERFACE_MAX); + goto cleanup; + } + + if (ret.ifaces.ifaces_len && + VIR_ALLOC_N(ifaces_ret, ret.ifaces.ifaces_len) < 0) + goto cleanup; + + for (i = 0; i < ret.ifaces.ifaces_len; i++) { + virDomainInterfacePtr iface; + remote_domain_interface *iface_ret = &(ret.ifaces.ifaces_val[i]); + + if (VIR_ALLOC(ifaces_ret[i]) < 0) + goto cleanup; + + iface = ifaces_ret[i]; + + if (VIR_STRDUP(iface->name, iface_ret->name) < 0) + goto cleanup; + + if (VIR_STRDUP(iface->hwaddr, iface_ret->hwaddr) < 0) + goto cleanup; + + if (iface_ret->addrs.addrs_len > REMOTE_DOMAIN_IP_ADDR_MAX) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Number of interfaces, %d exceeds the max limit: %d"), + iface_ret->addrs.addrs_len, REMOTE_DOMAIN_IP_ADDR_MAX); + goto cleanup; + } + + iface->naddrs = iface_ret->addrs.addrs_len; + + if (iface->naddrs) { + if (VIR_ALLOC_N(iface->addrs, iface->naddrs) < 0) + goto cleanup; + + for (j = 0; j < iface->naddrs; j++) { + virDomainIPAddressPtr ip_addr = &(iface->addrs[j]); + remote_domain_ip_addr *ip_addr_ret = + &(iface_ret->addrs.addrs_val[j]); + + if (VIR_STRDUP(ip_addr->addr, ip_addr_ret->addr) < 0) + goto cleanup; + + ip_addr->prefix = ip_addr_ret->prefix; + ip_addr->type = ip_addr_ret->type; + } + } + } + *ifaces = ifaces_ret; + ifaces_ret = NULL; + + rv = ret.ifaces.ifaces_len; + + cleanup: + if (ifaces_ret) { + for (i = 0; i < ret.ifaces.ifaces_len; i++) + virDomainInterfaceFree(ifaces_ret[i]); + VIR_FREE(ifaces_ret); + } + xdr_free((xdrproc_t)xdr_remote_domain_interface_addresses_ret, + (char *) &ret); + done: + remoteDriverUnlock(priv); + return rv; +} + + /* get_nonnull_domain and get_nonnull_network turn an on-wire * (name, uuid) pair into virDomainPtr or virNetworkPtr object. * These can return NULL if underlying memory allocations fail, @@ -8253,6 +8355,7 @@ static virHypervisorDriver hypervisor_driver = { .connectGetAllDomainStats = remoteConnectGetAllDomainStats, /* 1.2.8 */ .nodeAllocPages = remoteNodeAllocPages, /* 1.2.9 */ .domainGetFSInfo = remoteDomainGetFSInfo, /* 1.2.11 */ + .domainInterfaceAddresses = remoteDomainInterfaceAddresses, /* 1.2.14 */ };
static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 4ea535e..b511b00 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -259,6 +259,12 @@ const REMOTE_DOMAIN_FSINFO_MAX = 256; /* Upper limit on number of disks per mountpoint in fsinfo */ const REMOTE_DOMAIN_FSINFO_DISKS_MAX = 256;
+/* Upper limit on number of interfaces per domain */ +const REMOTE_DOMAIN_INTERFACE_MAX = 2048; + +/* Upper limit on number of IP addresses per interface */ +const REMOTE_DOMAIN_IP_ADDR_MAX = 2048; + /* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */ typedef opaque remote_uuid[VIR_UUID_BUFLEN];
@@ -3170,6 +3176,29 @@ struct remote_domain_get_fsinfo_ret { unsigned int ret; };
+struct remote_domain_ip_addr { + int type; + remote_nonnull_string addr; + unsigned int prefix; +}; + +struct remote_domain_interface { + remote_nonnull_string name; + remote_nonnull_string hwaddr; + remote_domain_ip_addr addrs<REMOTE_DOMAIN_IP_ADDR_MAX>; +}; + +struct remote_domain_interface_addresses_args { + remote_nonnull_domain dom; + unsigned int source; + unsigned int flags; +}; + +struct remote_domain_interface_addresses_ret { + remote_domain_interface ifaces<REMOTE_DOMAIN_INTERFACE_MAX>; +}; + + /*----- Protocol. -----*/
/* Define the program number, protocol version and procedure numbers here. */ @@ -5593,5 +5622,11 @@ enum remote_procedure { * @generate: none * @acl: domain:read */ - REMOTE_PROC_DOMAIN_GET_IOTHREADS_INFO = 351 + REMOTE_PROC_DOMAIN_GET_IOTHREADS_INFO = 351, + + /** + * @generate: none + * @acl: domain:read + */ + REMOTE_PROC_DOMAIN_INTERFACE_ADDRESSES = 352 };
Merge conflict here.
diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 907bcd4..632576b 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -2630,6 +2630,30 @@ struct remote_domain_get_fsinfo_ret { } info; u_int ret; }; +struct remote_domain_ip_addr { + int type; + remote_nonnull_string addr; + u_int prefix; +}; +struct remote_domain_interface { + remote_nonnull_string name; + remote_nonnull_string hwaddr; + struct { + u_int addrs_len; + remote_domain_ip_addr * addrs_val; + } addrs; +}; +struct remote_domain_interface_addresses_args { + remote_nonnull_domain dom; + u_int source; + u_int flags; +}; +struct remote_domain_interface_addresses_ret { + struct { + u_int ifaces_len; + remote_domain_interface * ifaces_val; + } ifaces; +}; enum remote_procedure { REMOTE_PROC_CONNECT_OPEN = 1, REMOTE_PROC_CONNECT_CLOSE = 2, @@ -2982,4 +3006,5 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_GET_FSINFO = 349, REMOTE_PROC_DOMAIN_DEFINE_XML_FLAGS = 350, REMOTE_PROC_DOMAIN_GET_IOTHREADS_INFO = 351, + REMOTE_PROC_DOMAIN_INTERFACE_ADDRESSES = 352,
Another merge conflict here.
};
ACK - with the cleanups John

From: Nehal J Wani <nehaljw.kkd1@gmail.com> By querying the qemu guest agent with the QMP command "guest-network-get-interfaces" and converting the received JSON output to structured objects. Although "ifconfig" is deprecated, IP aliases created by "ifconfig" are supported by this API. The legacy syntax of an IP alias is: "<ifname>:<alias-name>". Since we want all aliases to be clubbed under parent interface, simply stripping ":<alias-name>" suffices. Note that IP aliases formed by "ip" aren't visible to "ifconfig", and aliases created by "ip" do not have any specific name. But we are lucky, as qemu guest agent detects aliases created by both. src/qemu/qemu_agent.h: * Define qemuAgentGetInterfaces src/qemu/qemu_agent.c: * Implement qemuAgentGetInterface src/qemu/qemu_driver.c: * New function qemuGetDHCPInterfaces * New function qemuDomainInterfaceAddresses src/remote_protocol-sructs: * Define new structs tests/qemuagenttest.c: * Add new test: testQemuAgentGetInterfaces Test cases for IP aliases, 0 or multiple ipv4/ipv6 address(es) Signed-off-by: Nehal J Wani <nehaljw.kkd1@gmail.com> --- src/qemu/qemu_agent.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 4 + src/qemu/qemu_driver.c | 175 ++++++++++++++++++++++++++++++++++++++++++ tests/qemuagenttest.c | 188 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 571 insertions(+) diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c index 5fcc40f..8155006 100644 --- a/src/qemu/qemu_agent.c +++ b/src/qemu/qemu_agent.c @@ -1953,3 +1953,207 @@ qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info, virJSONValueFree(reply); return ret; } + +/* + * qemuAgentGetInterfaces: + * @mon: Agent monitor + * @ifaces: pointer to an array of pointers pointing to interface objects + * + * Issue guest-network-get-interfaces to guest agent, which returns a + * list of interfaces of a running domain along with their IP and MAC + * addresses. + * + * Returns: number of interfaces on success, -1 on error. + */ +int +qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr **ifaces) +{ + int ret = -1; + size_t i, j; + int size = -1; + virJSONValuePtr cmd = NULL; + virJSONValuePtr reply = NULL; + virJSONValuePtr ret_array = NULL; + size_t ifaces_count = 0; + size_t addrs_count = 0; + virDomainInterfacePtr *ifaces_ret = NULL; + virHashTablePtr ifaces_store = NULL; + char **ifname = NULL; + + /* Hash table to handle the interface alias */ + if (!(ifaces_store = virHashCreate(ifaces_count, NULL))) { + virHashFree(ifaces_store); + return -1; + } + + if (!(cmd = qemuAgentMakeCommand("guest-network-get-interfaces", NULL))) + goto cleanup; + + if (qemuAgentCommand(mon, cmd, &reply, false, VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0 || + qemuAgentCheckError(cmd, reply) < 0) { + goto cleanup; + } + + if (!(ret_array = virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'return' field")); + goto cleanup; + } + + if ((size = virJSONValueArraySize(ret_array)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't return an array of interfaces")); + goto cleanup; + } + + for (i = 0; i < size; i++) { + virJSONValuePtr tmp_iface = virJSONValueArrayGet(ret_array, i); + virJSONValuePtr ip_addr_arr = NULL; + const char *hwaddr, *ifname_s, *name = NULL; + int ip_addr_arr_size; + virDomainInterfacePtr iface = NULL; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!tmp_iface) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("something has went really wrong")); + goto error; + } + + /* interface name is required to be presented */ + name = virJSONValueObjectGetString(tmp_iface, "name"); + if (!name) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'name' field")); + goto error; + } + + /* Handle interface alias (<ifname>:<alias>) */ + ifname = virStringSplit(name, ":", 2); + ifname_s = ifname[0]; + + iface = virHashLookup(ifaces_store, ifname_s); + + /* If the hash table doesn't contain this iface, add it */ + if (!iface) { + if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0) + goto error; + + if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + if (virHashAddEntry(ifaces_store, ifname_s, + ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + iface = ifaces_ret[ifaces_count - 1]; + iface->naddrs = 0; + + if (VIR_STRDUP(iface->name, ifname_s) < 0) + goto error; + + hwaddr = virJSONValueObjectGetString(tmp_iface, "hardware-address"); + if (!hwaddr) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide" + " 'hardware-address' field")); + goto error; + } + + if (VIR_STRDUP(iface->hwaddr, hwaddr) < 0) + goto error; + } + + /* Has to be freed for each interface. */ + virStringFreeList(ifname); + + /* as well as IP address which - moreover - + * can be presented multiple times */ + ip_addr_arr = virJSONValueObjectGet(tmp_iface, "ip-addresses"); + if (!ip_addr_arr) + continue; + + + if ((ip_addr_arr_size = virJSONValueArraySize(ip_addr_arr)) < 0) + /* Mmm, empty 'ip-address'? */ + goto error; + + /* If current iface already exists, continue with the count */ + addrs_count = iface->naddrs; + + for (j = 0; j < ip_addr_arr_size; j++) { + const char *type, *addr; + virJSONValuePtr ip_addr_obj = virJSONValueArrayGet(ip_addr_arr, j); + virDomainIPAddressPtr ip_addr; + + if (VIR_EXPAND_N(iface->addrs, addrs_count, 1) < 0) + goto error; + + ip_addr = &iface->addrs[addrs_count - 1]; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!ip_addr_obj) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("something has went really wrong")); + goto error; + } + + type = virJSONValueObjectGetString(ip_addr_obj, "ip-address-type"); + if (!type) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("qemu agent didn't provide 'ip-address-type'" + " field for interface '%s'"), name); + goto error; + } else if (STREQ(type, "ipv4")) { + ip_addr->type = VIR_IP_ADDR_TYPE_IPV4; + } else if (STREQ(type, "ipv6")) { + ip_addr->type = VIR_IP_ADDR_TYPE_IPV6; + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown ip address type '%s'"), + type); + goto error; + } + + addr = virJSONValueObjectGetString(ip_addr_obj, "ip-address"); + if (!addr) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("qemu agent didn't provide 'ip-address'" + " field for interface '%s'"), name); + goto error; + } + if (VIR_STRDUP(ip_addr->addr, addr) < 0) + goto error; + + if (virJSONValueObjectGetNumberUint(ip_addr_obj, "prefix", + &ip_addr->prefix) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed 'prefix' field")); + goto error; + } + } + + iface->naddrs = addrs_count; + } + + *ifaces = ifaces_ret; + ifaces_ret = NULL; + ret = ifaces_count; + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + virHashFree(ifaces_store); + return ret; + + error: + if (ifaces_ret) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces_ret[i]); + } + VIR_FREE(ifaces_ret); + virStringFreeList(ifname); + + goto cleanup; +} diff --git a/src/qemu/qemu_agent.h b/src/qemu/qemu_agent.h index c983828..1cd5749 100644 --- a/src/qemu/qemu_agent.h +++ b/src/qemu/qemu_agent.h @@ -108,4 +108,8 @@ int qemuAgentSetTime(qemuAgentPtr mon, long long seconds, unsigned int nseconds, bool sync); + +int qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr **ifaces); + #endif /* __QEMU_AGENT_H__ */ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index ceceafa..5f21c22 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -171,6 +171,9 @@ static int qemuOpenFileAs(uid_t fallback_uid, gid_t fallback_gid, const char *path, int oflags, bool *needUnlink, bool *bypassSecurityDriver); +static int qemuGetDHCPInterfaces(virDomainPtr dom, + virDomainObjPtr vm, + virDomainInterfacePtr **ifaces); virQEMUDriverPtr qemu_driver = NULL; @@ -19246,6 +19249,177 @@ qemuDomainGetFSInfo(virDomainPtr dom, return ret; } +static int +qemuDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags) +{ + virQEMUDriverPtr driver = dom->conn->privateData; + qemuDomainObjPrivatePtr priv = NULL; + virDomainObjPtr vm = NULL; + int ret = -1; + + virCheckFlags(0, -1); + + if (!(vm = qemuDomObjFromDomain(dom))) + goto cleanup; + + priv = vm->privateData; + + if (virDomainInterfaceAddressesEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + switch (source) { + case VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE: + ret = qemuGetDHCPInterfaces(dom, vm, ifaces); + break; + + case VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT: + if (priv->agentError) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("QEMU guest agent is not " + "available due to an error")); + goto cleanup; + } + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto endjob; + } + + if (!priv->agent) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("QEMU guest agent is not configured")); + goto endjob; + } + + qemuDomainObjEnterAgent(vm); + ret = qemuAgentGetInterfaces(priv->agent, ifaces); + qemuDomainObjExitAgent(vm); + + endjob: + qemuDomainObjEndJob(driver, vm); + + break; + + default: + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("Unknown IP address data source %d"), + source); + break; + } + + cleanup: + if (vm) + virObjectUnlock(vm); + return ret; +} + +static int +qemuGetDHCPInterfaces(virDomainPtr dom, + virDomainObjPtr vm, + virDomainInterfacePtr **ifaces) +{ + int rv = -1; + int n_leases = 0; + size_t i, j; + size_t ifaces_count = 0; + virNetworkPtr network; + char macaddr[VIR_MAC_STRING_BUFLEN]; + virDomainInterfacePtr iface = NULL; + virNetworkDHCPLeasePtr *leases = NULL; + virDomainInterfacePtr *ifaces_ret = NULL; + + if (!dom->conn->networkDriver || + !dom->conn->networkDriver->networkGetDHCPLeases) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Network driver does not support DHCP lease query")); + return -1; + } + + for (i = 0; i < vm->def->nnets; i++) { + if (vm->def->nets[i]->type != VIR_DOMAIN_NET_TYPE_NETWORK) + continue; + + virMacAddrFormat(&(vm->def->nets[i]->mac), macaddr); + network = virNetworkLookupByName(dom->conn, + vm->def->nets[i]->data.network.name); + + if ((n_leases = virNetworkGetDHCPLeases(network, macaddr, + &leases, 0)) < 0) + goto error; + + if (n_leases) { + if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0) + goto error; + + if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + iface = ifaces_ret[ifaces_count - 1]; + /* Assuming each lease corresponds to a separate IP */ + iface->naddrs = n_leases; + + if (VIR_ALLOC_N(iface->addrs, iface->naddrs) < 0) + goto error; + + if (VIR_STRDUP(iface->name, vm->def->nets[i]->ifname) < 0) + goto cleanup; + + if (VIR_STRDUP(iface->hwaddr, macaddr) < 0) + goto cleanup; + } + + for (j = 0; j < n_leases; j++) { + virNetworkDHCPLeasePtr lease = leases[j]; + virDomainIPAddressPtr ip_addr = &iface->addrs[j]; + + if (VIR_STRDUP(ip_addr->addr, lease->ipaddr) < 0) + goto cleanup; + + ip_addr->type = lease->type; + ip_addr->prefix = lease->prefix; + } + + for (j = 0; j < n_leases; j++) + virNetworkDHCPLeaseFree(leases[j]); + + VIR_FREE(leases); + } + + *ifaces = ifaces_ret; + ifaces_ret = NULL; + rv = ifaces_count; + + cleanup: + if (leases) { + for (i = 0; i < n_leases; i++) + virNetworkDHCPLeaseFree(leases[i]); + } + VIR_FREE(leases); + + return rv; + + error: + if (ifaces_ret) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces_ret[i]); + } + VIR_FREE(ifaces_ret); + + goto cleanup; +} static virHypervisorDriver qemuHypervisorDriver = { .name = QEMU_DRIVER_NAME, @@ -19449,6 +19623,7 @@ static virHypervisorDriver qemuHypervisorDriver = { .connectGetAllDomainStats = qemuConnectGetAllDomainStats, /* 1.2.8 */ .nodeAllocPages = qemuNodeAllocPages, /* 1.2.9 */ .domainGetFSInfo = qemuDomainGetFSInfo, /* 1.2.11 */ + .domainInterfaceAddresses = qemuDomainInterfaceAddresses, /* 1.2.14 */ }; diff --git a/tests/qemuagenttest.c b/tests/qemuagenttest.c index 54a45df..d8d1e41 100644 --- a/tests/qemuagenttest.c +++ b/tests/qemuagenttest.c @@ -723,6 +723,193 @@ testQemuAgentTimeout(const void *data) return ret; } +static const char testQemuAgentGetInterfacesResponse[] = + "{\"return\": " + " [" + " {\"name\":\"eth2\"," + " \"hardware-address\":\"52:54:00:36:2a:e5\"" + " }," + " {\"name\":\"eth1:0\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.10.91\"," + " \"prefix\":24" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::fc54:ff:fefe:4c4f\"," + " \"prefix\":64" + " }" + " ]," + " \"hardware-address\":\"52:54:00:d3:39:ee\"" + " }," + " {\"name\":\"eth0\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::5054:ff:fe89:ad35\"," + " \"prefix\":64" + " }," + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.102.142\"," + " \"prefix\":24" + " }," + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.234.152\"," + " \"prefix\":16" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::5054:ff:fec3:68bb\"," + " \"prefix\":64" + " }" + " ]," + " \"hardware-address\":\"52:54:00:89:ad:35\"" + " }," + " {\"name\":\"eth1\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.103.83\"," + " \"prefix\":32" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::5054:ff:fed3:39ee\"," + " \"prefix\":64" + " }" + " ]," + " \"hardware-address\":\"52:54:00:d3:39:ee\"" + " }," + " {\"name\":\"lo\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"127.0.0.1\"," + " \"prefix\":8" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"::1\"," + " \"prefix\":128" + " }" + " ]," + " \"hardware-address\":\"00:00:00:00:00:00\"" + " }" + " ]" + "}"; + +static int +testQemuAgentGetInterfaces(const void *data) +{ + virDomainXMLOptionPtr xmlopt = (virDomainXMLOptionPtr)data; + qemuMonitorTestPtr test = qemuMonitorTestNewAgent(xmlopt); + size_t i; + int ret = -1; + int ifaces_count = 0; + virDomainInterfacePtr *ifaces = NULL; + + if (!test) + return -1; + + if (qemuMonitorTestAddAgentSyncResponse(test) < 0) + goto cleanup; + + if (qemuMonitorTestAddItem(test, "guest-network-get-interfaces", + testQemuAgentGetInterfacesResponse) < 0) + goto cleanup; + + if ((ifaces_count = qemuAgentGetInterfaces(qemuMonitorTestGetAgent(test), + &ifaces)) < 0) + goto cleanup; + + if (ifaces_count != 4) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "expected 4 interfaces, got %d", ret); + goto cleanup; + } + + if (STRNEQ(ifaces[0]->name, "eth2") || + STRNEQ(ifaces[1]->name, "eth1") || + STRNEQ(ifaces[2]->name, "eth0") || + STRNEQ(ifaces[3]->name, "lo")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for interface names"); + goto cleanup; + } + + if (STRNEQ(ifaces[0]->hwaddr, "52:54:00:36:2a:e5") || + STRNEQ(ifaces[1]->hwaddr, "52:54:00:d3:39:ee") || + STRNEQ(ifaces[2]->hwaddr, "52:54:00:89:ad:35") || + STRNEQ(ifaces[3]->hwaddr, "00:00:00:00:00:00")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for MAC addresses"); + goto cleanup; + } + + if (ifaces[0]->naddrs != 0 || + ifaces[1]->naddrs != 4 || + ifaces[2]->naddrs != 4 || + ifaces[3]->naddrs != 2) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for number of IP addresses"); + goto cleanup; + } + + if (ifaces[1]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[1]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[1]->addrs[2].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[1]->addrs[3].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[2]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[2]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[2]->addrs[2].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[2]->addrs[3].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[3]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[3]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV6) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for IP address types"); + goto cleanup; + } + + if (ifaces[1]->addrs[0].prefix != 24 || + ifaces[1]->addrs[1].prefix != 64 || + ifaces[1]->addrs[2].prefix != 32 || + ifaces[1]->addrs[3].prefix != 64 || + ifaces[2]->addrs[0].prefix != 64 || + ifaces[2]->addrs[1].prefix != 24 || + ifaces[2]->addrs[2].prefix != 16 || + ifaces[2]->addrs[3].prefix != 64 || + ifaces[3]->addrs[0].prefix != 8 || + ifaces[3]->addrs[1].prefix != 128) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for IP address prefix"); + goto cleanup; + } + + if (STRNEQ(ifaces[1]->addrs[0].addr, "192.168.10.91") || + STRNEQ(ifaces[1]->addrs[1].addr, "fe80::fc54:ff:fefe:4c4f") || + STRNEQ(ifaces[1]->addrs[2].addr, "192.168.103.83") || + STRNEQ(ifaces[1]->addrs[3].addr, "fe80::5054:ff:fed3:39ee") || + STRNEQ(ifaces[2]->addrs[0].addr, "fe80::5054:ff:fe89:ad35") || + STRNEQ(ifaces[2]->addrs[1].addr, "192.168.102.142") || + STRNEQ(ifaces[2]->addrs[2].addr, "192.168.234.152") || + STRNEQ(ifaces[2]->addrs[3].addr, "fe80::5054:ff:fec3:68bb") || + STRNEQ(ifaces[3]->addrs[0].addr, "127.0.0.1") || + STRNEQ(ifaces[3]->addrs[1].addr, "::1")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for IP address values"); + goto cleanup; + } + + ret = 0; + + cleanup: + qemuMonitorTestFree(test); + if (ifaces) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces[i]); + } + VIR_FREE(ifaces); + + return ret; +} static int mymain(void) @@ -753,6 +940,7 @@ mymain(void) DO_TEST(Shutdown); DO_TEST(CPU); DO_TEST(ArbitraryCommand); + DO_TEST(GetInterfaces); DO_TEST(Timeout); /* Timeout should always be called last */ -- 2.1.0

On 03/11/2015 07:09 AM, Daniel P. Berrange wrote:
From: Nehal J Wani <nehaljw.kkd1@gmail.com>
By querying the qemu guest agent with the QMP command "guest-network-get-interfaces" and converting the received JSON output to structured objects.
Although "ifconfig" is deprecated, IP aliases created by "ifconfig" are supported by this API. The legacy syntax of an IP alias is: "<ifname>:<alias-name>". Since we want all aliases to be clubbed under parent interface, simply stripping ":<alias-name>" suffices. Note that IP aliases formed by "ip" aren't visible to "ifconfig", and aliases created by "ip" do not have any specific name. But we are lucky, as qemu guest agent detects aliases created by both.
src/qemu/qemu_agent.h: * Define qemuAgentGetInterfaces
src/qemu/qemu_agent.c: * Implement qemuAgentGetInterface
src/qemu/qemu_driver.c: * New function qemuGetDHCPInterfaces * New function qemuDomainInterfaceAddresses
src/remote_protocol-sructs: * Define new structs
tests/qemuagenttest.c: * Add new test: testQemuAgentGetInterfaces Test cases for IP aliases, 0 or multiple ipv4/ipv6 address(es)
Signed-off-by: Nehal J Wani <nehaljw.kkd1@gmail.com> --- src/qemu/qemu_agent.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 4 + src/qemu/qemu_driver.c | 175 ++++++++++++++++++++++++++++++++++++++++++ tests/qemuagenttest.c | 188 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 571 insertions(+)
ACK - there's a NIT and a comment that follows, I'll let you decide how to handle. John
diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c index 5fcc40f..8155006 100644 --- a/src/qemu/qemu_agent.c +++ b/src/qemu/qemu_agent.c @@ -1953,3 +1953,207 @@ qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info, virJSONValueFree(reply); return ret; } + +/* + * qemuAgentGetInterfaces: + * @mon: Agent monitor + * @ifaces: pointer to an array of pointers pointing to interface objects + * + * Issue guest-network-get-interfaces to guest agent, which returns a + * list of interfaces of a running domain along with their IP and MAC + * addresses. + * + * Returns: number of interfaces on success, -1 on error. + */ +int +qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr **ifaces) +{ + int ret = -1; + size_t i, j; + int size = -1; + virJSONValuePtr cmd = NULL; + virJSONValuePtr reply = NULL; + virJSONValuePtr ret_array = NULL; + size_t ifaces_count = 0; + size_t addrs_count = 0; + virDomainInterfacePtr *ifaces_ret = NULL; + virHashTablePtr ifaces_store = NULL; + char **ifname = NULL; + + /* Hash table to handle the interface alias */ + if (!(ifaces_store = virHashCreate(ifaces_count, NULL))) { + virHashFree(ifaces_store); + return -1; + } + + if (!(cmd = qemuAgentMakeCommand("guest-network-get-interfaces", NULL))) + goto cleanup; + + if (qemuAgentCommand(mon, cmd, &reply, false, VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0 || + qemuAgentCheckError(cmd, reply) < 0) { + goto cleanup; + } + + if (!(ret_array = virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'return' field")); + goto cleanup; + } + + if ((size = virJSONValueArraySize(ret_array)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't return an array of interfaces")); + goto cleanup; + } + + for (i = 0; i < size; i++) { + virJSONValuePtr tmp_iface = virJSONValueArrayGet(ret_array, i); + virJSONValuePtr ip_addr_arr = NULL; + const char *hwaddr, *ifname_s, *name = NULL; + int ip_addr_arr_size; + virDomainInterfacePtr iface = NULL; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!tmp_iface) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("something has went really wrong")); + goto error; + } + + /* interface name is required to be presented */ + name = virJSONValueObjectGetString(tmp_iface, "name"); + if (!name) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'name' field")); + goto error; + } + + /* Handle interface alias (<ifname>:<alias>) */ + ifname = virStringSplit(name, ":", 2); + ifname_s = ifname[0]; + + iface = virHashLookup(ifaces_store, ifname_s); + + /* If the hash table doesn't contain this iface, add it */ + if (!iface) { + if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0) + goto error; + + if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + if (virHashAddEntry(ifaces_store, ifname_s, + ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + iface = ifaces_ret[ifaces_count - 1]; + iface->naddrs = 0; + + if (VIR_STRDUP(iface->name, ifname_s) < 0) + goto error; + + hwaddr = virJSONValueObjectGetString(tmp_iface, "hardware-address"); + if (!hwaddr) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide" + " 'hardware-address' field")); + goto error; + } + + if (VIR_STRDUP(iface->hwaddr, hwaddr) < 0) + goto error; + } + + /* Has to be freed for each interface. */ + virStringFreeList(ifname); + + /* as well as IP address which - moreover - + * can be presented multiple times */ + ip_addr_arr = virJSONValueObjectGet(tmp_iface, "ip-addresses"); + if (!ip_addr_arr) + continue; +
NIT: Extra line here
+ + if ((ip_addr_arr_size = virJSONValueArraySize(ip_addr_arr)) < 0) + /* Mmm, empty 'ip-address'? */ + goto error; + + /* If current iface already exists, continue with the count */ + addrs_count = iface->naddrs; + + for (j = 0; j < ip_addr_arr_size; j++) { + const char *type, *addr; + virJSONValuePtr ip_addr_obj = virJSONValueArrayGet(ip_addr_arr, j); + virDomainIPAddressPtr ip_addr; + + if (VIR_EXPAND_N(iface->addrs, addrs_count, 1) < 0) + goto error; + + ip_addr = &iface->addrs[addrs_count - 1]; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!ip_addr_obj) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("something has went really wrong"));
Cannot remember if this text caught my attention the first time, but it is humorous...
+ goto error; + } + + type = virJSONValueObjectGetString(ip_addr_obj, "ip-address-type"); + if (!type) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("qemu agent didn't provide 'ip-address-type'" + " field for interface '%s'"), name); + goto error; + } else if (STREQ(type, "ipv4")) { + ip_addr->type = VIR_IP_ADDR_TYPE_IPV4; + } else if (STREQ(type, "ipv6")) { + ip_addr->type = VIR_IP_ADDR_TYPE_IPV6; + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown ip address type '%s'"), + type); + goto error; + } + + addr = virJSONValueObjectGetString(ip_addr_obj, "ip-address"); + if (!addr) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("qemu agent didn't provide 'ip-address'" + " field for interface '%s'"), name); + goto error; + } + if (VIR_STRDUP(ip_addr->addr, addr) < 0) + goto error; + + if (virJSONValueObjectGetNumberUint(ip_addr_obj, "prefix", + &ip_addr->prefix) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed 'prefix' field")); + goto error; + } + } + + iface->naddrs = addrs_count; + } + + *ifaces = ifaces_ret; + ifaces_ret = NULL; + ret = ifaces_count; + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + virHashFree(ifaces_store); + return ret; + + error: + if (ifaces_ret) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces_ret[i]); + } + VIR_FREE(ifaces_ret); + virStringFreeList(ifname); + + goto cleanup; +} diff --git a/src/qemu/qemu_agent.h b/src/qemu/qemu_agent.h index c983828..1cd5749 100644 --- a/src/qemu/qemu_agent.h +++ b/src/qemu/qemu_agent.h @@ -108,4 +108,8 @@ int qemuAgentSetTime(qemuAgentPtr mon, long long seconds, unsigned int nseconds, bool sync); + +int qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr **ifaces); + #endif /* __QEMU_AGENT_H__ */ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index ceceafa..5f21c22 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -171,6 +171,9 @@ static int qemuOpenFileAs(uid_t fallback_uid, gid_t fallback_gid, const char *path, int oflags, bool *needUnlink, bool *bypassSecurityDriver);
+static int qemuGetDHCPInterfaces(virDomainPtr dom, + virDomainObjPtr vm, + virDomainInterfacePtr **ifaces);
virQEMUDriverPtr qemu_driver = NULL;
@@ -19246,6 +19249,177 @@ qemuDomainGetFSInfo(virDomainPtr dom, return ret; }
+static int +qemuDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags) +{ + virQEMUDriverPtr driver = dom->conn->privateData; + qemuDomainObjPrivatePtr priv = NULL; + virDomainObjPtr vm = NULL; + int ret = -1; + + virCheckFlags(0, -1); + + if (!(vm = qemuDomObjFromDomain(dom))) + goto cleanup; + + priv = vm->privateData; + + if (virDomainInterfaceAddressesEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + switch (source) { + case VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE: + ret = qemuGetDHCPInterfaces(dom, vm, ifaces); + break; + + case VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT: + if (priv->agentError) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("QEMU guest agent is not " + "available due to an error")); + goto cleanup; + } + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto endjob; + } + + if (!priv->agent) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("QEMU guest agent is not configured")); + goto endjob; + } + + qemuDomainObjEnterAgent(vm); + ret = qemuAgentGetInterfaces(priv->agent, ifaces); + qemuDomainObjExitAgent(vm); + + endjob: + qemuDomainObjEndJob(driver, vm); + + break; + + default: + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("Unknown IP address data source %d"), + source); + break; + } + + cleanup: + if (vm) + virObjectUnlock(vm); + return ret; +} + +static int +qemuGetDHCPInterfaces(virDomainPtr dom, + virDomainObjPtr vm, + virDomainInterfacePtr **ifaces) +{ + int rv = -1; + int n_leases = 0; + size_t i, j; + size_t ifaces_count = 0; + virNetworkPtr network; + char macaddr[VIR_MAC_STRING_BUFLEN]; + virDomainInterfacePtr iface = NULL; + virNetworkDHCPLeasePtr *leases = NULL; + virDomainInterfacePtr *ifaces_ret = NULL; + + if (!dom->conn->networkDriver || + !dom->conn->networkDriver->networkGetDHCPLeases) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Network driver does not support DHCP lease query")); + return -1; + } + + for (i = 0; i < vm->def->nnets; i++) { + if (vm->def->nets[i]->type != VIR_DOMAIN_NET_TYPE_NETWORK) + continue; + + virMacAddrFormat(&(vm->def->nets[i]->mac), macaddr); + network = virNetworkLookupByName(dom->conn, + vm->def->nets[i]->data.network.name); + + if ((n_leases = virNetworkGetDHCPLeases(network, macaddr, + &leases, 0)) < 0) + goto error; + + if (n_leases) { + if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0) + goto error; + + if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + iface = ifaces_ret[ifaces_count - 1]; + /* Assuming each lease corresponds to a separate IP */ + iface->naddrs = n_leases; + + if (VIR_ALLOC_N(iface->addrs, iface->naddrs) < 0) + goto error; + + if (VIR_STRDUP(iface->name, vm->def->nets[i]->ifname) < 0) + goto cleanup; + + if (VIR_STRDUP(iface->hwaddr, macaddr) < 0) + goto cleanup; + } + + for (j = 0; j < n_leases; j++) { + virNetworkDHCPLeasePtr lease = leases[j]; + virDomainIPAddressPtr ip_addr = &iface->addrs[j]; + + if (VIR_STRDUP(ip_addr->addr, lease->ipaddr) < 0) + goto cleanup; + + ip_addr->type = lease->type; + ip_addr->prefix = lease->prefix; + } + + for (j = 0; j < n_leases; j++) + virNetworkDHCPLeaseFree(leases[j]); + + VIR_FREE(leases); + } + + *ifaces = ifaces_ret; + ifaces_ret = NULL; + rv = ifaces_count; + + cleanup: + if (leases) { + for (i = 0; i < n_leases; i++) + virNetworkDHCPLeaseFree(leases[i]); + } + VIR_FREE(leases); + + return rv; + + error: + if (ifaces_ret) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces_ret[i]); + } + VIR_FREE(ifaces_ret); + + goto cleanup; +}
static virHypervisorDriver qemuHypervisorDriver = { .name = QEMU_DRIVER_NAME, @@ -19449,6 +19623,7 @@ static virHypervisorDriver qemuHypervisorDriver = { .connectGetAllDomainStats = qemuConnectGetAllDomainStats, /* 1.2.8 */ .nodeAllocPages = qemuNodeAllocPages, /* 1.2.9 */ .domainGetFSInfo = qemuDomainGetFSInfo, /* 1.2.11 */ + .domainInterfaceAddresses = qemuDomainInterfaceAddresses, /* 1.2.14 */ };
diff --git a/tests/qemuagenttest.c b/tests/qemuagenttest.c index 54a45df..d8d1e41 100644 --- a/tests/qemuagenttest.c +++ b/tests/qemuagenttest.c @@ -723,6 +723,193 @@ testQemuAgentTimeout(const void *data) return ret; }
+static const char testQemuAgentGetInterfacesResponse[] = + "{\"return\": " + " [" + " {\"name\":\"eth2\"," + " \"hardware-address\":\"52:54:00:36:2a:e5\"" + " }," + " {\"name\":\"eth1:0\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.10.91\"," + " \"prefix\":24" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::fc54:ff:fefe:4c4f\"," + " \"prefix\":64" + " }" + " ]," + " \"hardware-address\":\"52:54:00:d3:39:ee\"" + " }," + " {\"name\":\"eth0\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::5054:ff:fe89:ad35\"," + " \"prefix\":64" + " }," + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.102.142\"," + " \"prefix\":24" + " }," + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.234.152\"," + " \"prefix\":16" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::5054:ff:fec3:68bb\"," + " \"prefix\":64" + " }" + " ]," + " \"hardware-address\":\"52:54:00:89:ad:35\"" + " }," + " {\"name\":\"eth1\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.103.83\"," + " \"prefix\":32" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::5054:ff:fed3:39ee\"," + " \"prefix\":64" + " }" + " ]," + " \"hardware-address\":\"52:54:00:d3:39:ee\"" + " }," + " {\"name\":\"lo\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"127.0.0.1\"," + " \"prefix\":8" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"::1\"," + " \"prefix\":128" + " }" + " ]," + " \"hardware-address\":\"00:00:00:00:00:00\"" + " }" + " ]" + "}"; + +static int +testQemuAgentGetInterfaces(const void *data) +{ + virDomainXMLOptionPtr xmlopt = (virDomainXMLOptionPtr)data; + qemuMonitorTestPtr test = qemuMonitorTestNewAgent(xmlopt); + size_t i; + int ret = -1; + int ifaces_count = 0; + virDomainInterfacePtr *ifaces = NULL; + + if (!test) + return -1; + + if (qemuMonitorTestAddAgentSyncResponse(test) < 0) + goto cleanup; + + if (qemuMonitorTestAddItem(test, "guest-network-get-interfaces", + testQemuAgentGetInterfacesResponse) < 0) + goto cleanup; + + if ((ifaces_count = qemuAgentGetInterfaces(qemuMonitorTestGetAgent(test), + &ifaces)) < 0) + goto cleanup; + + if (ifaces_count != 4) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "expected 4 interfaces, got %d", ret); + goto cleanup; + } + + if (STRNEQ(ifaces[0]->name, "eth2") || + STRNEQ(ifaces[1]->name, "eth1") || + STRNEQ(ifaces[2]->name, "eth0") || + STRNEQ(ifaces[3]->name, "lo")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for interface names"); + goto cleanup; + } + + if (STRNEQ(ifaces[0]->hwaddr, "52:54:00:36:2a:e5") || + STRNEQ(ifaces[1]->hwaddr, "52:54:00:d3:39:ee") || + STRNEQ(ifaces[2]->hwaddr, "52:54:00:89:ad:35") || + STRNEQ(ifaces[3]->hwaddr, "00:00:00:00:00:00")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for MAC addresses"); + goto cleanup; + } + + if (ifaces[0]->naddrs != 0 || + ifaces[1]->naddrs != 4 || + ifaces[2]->naddrs != 4 || + ifaces[3]->naddrs != 2) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for number of IP addresses"); + goto cleanup; + } + + if (ifaces[1]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[1]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[1]->addrs[2].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[1]->addrs[3].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[2]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[2]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[2]->addrs[2].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[2]->addrs[3].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[3]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[3]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV6) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for IP address types"); + goto cleanup; + } + + if (ifaces[1]->addrs[0].prefix != 24 || + ifaces[1]->addrs[1].prefix != 64 || + ifaces[1]->addrs[2].prefix != 32 || + ifaces[1]->addrs[3].prefix != 64 || + ifaces[2]->addrs[0].prefix != 64 || + ifaces[2]->addrs[1].prefix != 24 || + ifaces[2]->addrs[2].prefix != 16 || + ifaces[2]->addrs[3].prefix != 64 || + ifaces[3]->addrs[0].prefix != 8 || + ifaces[3]->addrs[1].prefix != 128) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for IP address prefix"); + goto cleanup; + } + + if (STRNEQ(ifaces[1]->addrs[0].addr, "192.168.10.91") || + STRNEQ(ifaces[1]->addrs[1].addr, "fe80::fc54:ff:fefe:4c4f") || + STRNEQ(ifaces[1]->addrs[2].addr, "192.168.103.83") || + STRNEQ(ifaces[1]->addrs[3].addr, "fe80::5054:ff:fed3:39ee") || + STRNEQ(ifaces[2]->addrs[0].addr, "fe80::5054:ff:fe89:ad35") || + STRNEQ(ifaces[2]->addrs[1].addr, "192.168.102.142") || + STRNEQ(ifaces[2]->addrs[2].addr, "192.168.234.152") || + STRNEQ(ifaces[2]->addrs[3].addr, "fe80::5054:ff:fec3:68bb") || + STRNEQ(ifaces[3]->addrs[0].addr, "127.0.0.1") || + STRNEQ(ifaces[3]->addrs[1].addr, "::1")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for IP address values"); + goto cleanup; + } + + ret = 0; + + cleanup: + qemuMonitorTestFree(test); + if (ifaces) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces[i]); + } + VIR_FREE(ifaces); + + return ret; +}
static int mymain(void) @@ -753,6 +940,7 @@ mymain(void) DO_TEST(Shutdown); DO_TEST(CPU); DO_TEST(ArbitraryCommand); + DO_TEST(GetInterfaces);
DO_TEST(Timeout); /* Timeout should always be called last */

On Mon, Mar 16, 2015 at 08:42:49AM -0400, John Ferlan wrote:
On 03/11/2015 07:09 AM, Daniel P. Berrange wrote:
From: Nehal J Wani <nehaljw.kkd1@gmail.com>
By querying the qemu guest agent with the QMP command "guest-network-get-interfaces" and converting the received JSON output to structured objects.
Although "ifconfig" is deprecated, IP aliases created by "ifconfig" are supported by this API. The legacy syntax of an IP alias is: "<ifname>:<alias-name>". Since we want all aliases to be clubbed under parent interface, simply stripping ":<alias-name>" suffices. Note that IP aliases formed by "ip" aren't visible to "ifconfig", and aliases created by "ip" do not have any specific name. But we are lucky, as qemu guest agent detects aliases created by both.
src/qemu/qemu_agent.h: * Define qemuAgentGetInterfaces
src/qemu/qemu_agent.c: * Implement qemuAgentGetInterface
src/qemu/qemu_driver.c: * New function qemuGetDHCPInterfaces * New function qemuDomainInterfaceAddresses
src/remote_protocol-sructs: * Define new structs
tests/qemuagenttest.c: * Add new test: testQemuAgentGetInterfaces Test cases for IP aliases, 0 or multiple ipv4/ipv6 address(es)
Signed-off-by: Nehal J Wani <nehaljw.kkd1@gmail.com> --- src/qemu/qemu_agent.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 4 + src/qemu/qemu_driver.c | 175 ++++++++++++++++++++++++++++++++++++++++++ tests/qemuagenttest.c | 188 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 571 insertions(+)
ACK - there's a NIT and a comment that follows, I'll let you decide how to handle.
John
diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c index 5fcc40f..8155006 100644 --- a/src/qemu/qemu_agent.c +++ b/src/qemu/qemu_agent.c @@ -1953,3 +1953,207 @@ qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info, virJSONValueFree(reply); return ret; } + +/* + * qemuAgentGetInterfaces: + * @mon: Agent monitor + * @ifaces: pointer to an array of pointers pointing to interface objects + * + * Issue guest-network-get-interfaces to guest agent, which returns a + * list of interfaces of a running domain along with their IP and MAC + * addresses. + * + * Returns: number of interfaces on success, -1 on error. + */ +int +qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr **ifaces) +{ + int ret = -1; + size_t i, j; + int size = -1; + virJSONValuePtr cmd = NULL; + virJSONValuePtr reply = NULL; + virJSONValuePtr ret_array = NULL; + size_t ifaces_count = 0; + size_t addrs_count = 0; + virDomainInterfacePtr *ifaces_ret = NULL; + virHashTablePtr ifaces_store = NULL; + char **ifname = NULL; + + /* Hash table to handle the interface alias */ + if (!(ifaces_store = virHashCreate(ifaces_count, NULL))) { + virHashFree(ifaces_store); + return -1; + } + + if (!(cmd = qemuAgentMakeCommand("guest-network-get-interfaces", NULL))) + goto cleanup; + + if (qemuAgentCommand(mon, cmd, &reply, false, VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0 || + qemuAgentCheckError(cmd, reply) < 0) { + goto cleanup; + } + + if (!(ret_array = virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'return' field")); + goto cleanup; + } + + if ((size = virJSONValueArraySize(ret_array)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't return an array of interfaces")); + goto cleanup; + } + + for (i = 0; i < size; i++) { + virJSONValuePtr tmp_iface = virJSONValueArrayGet(ret_array, i); + virJSONValuePtr ip_addr_arr = NULL; + const char *hwaddr, *ifname_s, *name = NULL; + int ip_addr_arr_size; + virDomainInterfacePtr iface = NULL; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!tmp_iface) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("something has went really wrong")); + goto error; + } + + /* interface name is required to be presented */ + name = virJSONValueObjectGetString(tmp_iface, "name"); + if (!name) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'name' field")); + goto error; + } + + /* Handle interface alias (<ifname>:<alias>) */ + ifname = virStringSplit(name, ":", 2); + ifname_s = ifname[0]; + + iface = virHashLookup(ifaces_store, ifname_s); + + /* If the hash table doesn't contain this iface, add it */ + if (!iface) { + if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0) + goto error; + + if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + if (virHashAddEntry(ifaces_store, ifname_s, + ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + iface = ifaces_ret[ifaces_count - 1]; + iface->naddrs = 0; + + if (VIR_STRDUP(iface->name, ifname_s) < 0) + goto error; + + hwaddr = virJSONValueObjectGetString(tmp_iface, "hardware-address"); + if (!hwaddr) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide" + " 'hardware-address' field")); + goto error; + } + + if (VIR_STRDUP(iface->hwaddr, hwaddr) < 0) + goto error; + } + + /* Has to be freed for each interface. */ + virStringFreeList(ifname); + + /* as well as IP address which - moreover - + * can be presented multiple times */ + ip_addr_arr = virJSONValueObjectGet(tmp_iface, "ip-addresses"); + if (!ip_addr_arr) + continue; +
NIT: Extra line here
+ + if ((ip_addr_arr_size = virJSONValueArraySize(ip_addr_arr)) < 0) + /* Mmm, empty 'ip-address'? */ + goto error; + + /* If current iface already exists, continue with the count */ + addrs_count = iface->naddrs; + + for (j = 0; j < ip_addr_arr_size; j++) { + const char *type, *addr; + virJSONValuePtr ip_addr_obj = virJSONValueArrayGet(ip_addr_arr, j); + virDomainIPAddressPtr ip_addr; + + if (VIR_EXPAND_N(iface->addrs, addrs_count, 1) < 0) + goto error; + + ip_addr = &iface->addrs[addrs_count - 1]; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!ip_addr_obj) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("something has went really wrong"));
Cannot remember if this text caught my attention the first time, but it is humorous...
I'll put a more sensible error message in there. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

From: Nehal J Wani <nehaljw.kkd1@gmail.com> tools/virsh-domain-monitor.c * Introduce new command : domifaddr Usage: domifaddr <domain> [interface] [--full] [--source lease|agent] Example outputs: virsh # domifaddr f20 Name MAC address Protocol Address ------------------------------------------------------------------------------- lo 00:00:00:00:00:00 ipv4 127.0.0.1/8 - - ipv6 ::1/128 eth0 52:54:00:2e:45:ce ipv4 10.1.33.188/24 - - ipv6 2001:db8:0:f101::2/64 - - ipv6 fe80::5054:ff:fe2e:45ce/64 eth1 52:54:00:b1:70:19 ipv4 192.168.105.201/16 - - ipv4 192.168.201.195/16 - - ipv6 fe80::5054:ff:feb1:7019/64 eth2 52:54:00:36:2a:e5 N/A N/A eth3 52:54:00:20:70:3d ipv4 192.168.105.240/16 - - ipv6 fe80::5054:ff:fe20:703d/64 virsh # domifaddr f20 eth1 --source lease Name MAC address Protocol Address ------------------------------------------------------------------------------- eth1 52:54:00:b1:70:19 ipv4 192.168.105.201/16 - - ipv4 192.168.201.195/16 - - ipv6 fe80::5054:ff:feb1:7019/64 virsh # domifaddr f20 eth0 --source agent --full Name MAC address Protocol Address ------------------------------------------------------------------------------- eth0 52:54:00:2e:45:ce ipv4 10.1.33.188/24 eth0 52:54:00:2e:45:ce ipv6 2001:db8:0:f101::2/64 eth0 52:54:00:2e:45:ce ipv6 fe80::5054:ff:fe2e:45ce/64 tools/virsh.pod * Document new command Signed-off-by: Nehal J Wani <nehaljw.kkd1@gmail.com> --- tools/virsh-domain-monitor.c | 146 +++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 16 +++++ 2 files changed, 162 insertions(+) diff --git a/tools/virsh-domain-monitor.c b/tools/virsh-domain-monitor.c index 464ac11..92ce152 100644 --- a/tools/virsh-domain-monitor.c +++ b/tools/virsh-domain-monitor.c @@ -2197,6 +2197,146 @@ cmdDomstats(vshControl *ctl, const vshCmd *cmd) return ret; } +/* "domifaddr" command + */ +static const vshCmdInfo info_domifaddr[] = { + {"help", N_("Get network interfaces' addresses for a running domain")}, + {"desc", N_("Get network interfaces' addresses for a running domain")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_domifaddr[] = { + {.name = "domain", + .type = VSH_OT_DATA, + .flags = VSH_OFLAG_REQ, + .help = N_("domain name, id or uuid")}, + {.name = "interface", + .type = VSH_OT_STRING, + .flags = VSH_OFLAG_NONE, + .help = N_("network interface name")}, + {.name = "full", + .type = VSH_OT_BOOL, + .flags = VSH_OFLAG_NONE, + .help = N_("display full fields")}, + {.name = "source", + .type = VSH_OT_STRING, + .flags = VSH_OFLAG_NONE, + .help = N_("address source: 'lease' or 'agent'")}, + {.name = NULL} +}; + +static bool +cmdDomIfAddr(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + const char *interface = NULL; + virDomainInterfacePtr *ifaces = NULL; + size_t i, j; + int ifaces_count = 0; + bool ret = false; + bool full = vshCommandOptBool(cmd, "full"); + const char *sourcestr = NULL; + int source = VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE; + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptString(cmd, "interface", &interface) < 0) + goto cleanup; + if (vshCommandOptString(cmd, "source", &sourcestr) < 0) + goto cleanup; + + if (sourcestr) { + if (STREQ(sourcestr, "lease")) { + source = VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE; + } else if (STREQ(sourcestr, "agent")) { + source = VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT; + } else { + vshError(ctl, _("Unknown data source '%s'"), sourcestr); + goto cleanup; + } + } + + if ((ifaces_count = virDomainInterfaceAddresses(dom, &ifaces, source, 0)) < 0) { + vshError(ctl, _("Failed to query for interfaces addresses")); + goto cleanup; + } + + vshPrintExtra(ctl, " %-10s %-20s %-8s %s\n%s%s\n", _("Name"), + _("MAC address"), _("Protocol"), _("Address"), + _("-------------------------------------------------"), + _("------------------------------")); + + for (i = 0; i < ifaces_count; i++) { + virDomainInterfacePtr iface = ifaces[i]; + const char *hwaddr = ""; + const char *ip_addr_str = NULL; + const char *type = NULL; + + if (interface && STRNEQ(interface, iface->name)) + continue; + + hwaddr = iface->hwaddr; + + /* When the interface has no IP address */ + if (!iface->naddrs) { + vshPrintExtra(ctl, " %-10s %-17s %-12s %s\n", + iface->name, hwaddr, "N/A", "N/A"); + continue; + } + + for (j = 0; j < iface->naddrs; j++) { + virBuffer buf = VIR_BUFFER_INITIALIZER; + + switch (iface->addrs[j].type) { + case VIR_IP_ADDR_TYPE_IPV4: + type = "ipv4"; + break; + case VIR_IP_ADDR_TYPE_IPV6: + type = "ipv6"; + break; + } + + virBufferAsprintf(&buf, "%-12s %s/%d", + type, iface->addrs[j].addr, + iface->addrs[j].prefix); + + if (virBufferError(&buf)) { + virBufferFreeAndReset(&buf); + virReportOOMError(); + goto cleanup; + } + + ip_addr_str = virBufferContentAndReset(&buf); + + if (!ip_addr_str) + ip_addr_str = ""; + + /* Don't repeat interface name */ + if (full || !j) + vshPrintExtra(ctl, " %-10s %-17s %s\n", + iface->name, hwaddr, ip_addr_str); + else + vshPrintExtra(ctl, " %-10s %-17s %s\n", + "-", "-", ip_addr_str); + + virBufferFreeAndReset(&buf); + } + } + + ret = true; + + cleanup: + if (ifaces && ifaces_count > 0) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces[i]); + } + VIR_FREE(ifaces); + + virDomainFree(dom); + return ret; +} + const vshCmdDef domMonitoringCmds[] = { {.name = "domblkerror", .handler = cmdDomBlkError, @@ -2234,6 +2374,12 @@ const vshCmdDef domMonitoringCmds[] = { .info = info_domif_getlink, .flags = 0 }, + {.name = "domifaddr", + .handler = cmdDomIfAddr, + .opts = opts_domifaddr, + .info = info_domifaddr, + .flags = 0 + }, {.name = "domiflist", .handler = cmdDomiflist, .opts = opts_domiflist, diff --git a/tools/virsh.pod b/tools/virsh.pod index e65378e..4190485 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -730,6 +730,22 @@ B<Explanation of fields> (fields appear in the following order): flush_total_times - total time flush operations took (ns) <-- other fields provided by hypervisor --> + +=item B<domifaddr> I<domain> [I<interface>] [I<--full>] + [I<--source lease|agent>] + +Get a list of interfaces of a running domain along with their IP and MAC +addresses, or limited output just for one interface if I<interface> is +specified. Note that I<interface> can be driver dependent, it can be the name +within guest OS or the name you would see in domain XML. Moreover, the whole +command may require a guest agent to be configured for the queried domain under +some drivers, notably qemu. If I<--full> is specified, the interface name is +always displayed when the interface has multiple addresses or alias, otherwise +it only displays the interface name for the first address, and "-" for the +others. The I<--source> argument specifies what data source to use for the +addresses, currently one of 'lease' to read DHCP leases, or 'agent' to query +the guest OS via an agent. + =item B<domifstat> I<domain> I<interface-device> Get network interface stats for a running domain. -- 2.1.0

On 03/11/2015 07:09 AM, Daniel P. Berrange wrote:
From: Nehal J Wani <nehaljw.kkd1@gmail.com>
tools/virsh-domain-monitor.c * Introduce new command : domifaddr Usage: domifaddr <domain> [interface] [--full] [--source lease|agent]
Example outputs: virsh # domifaddr f20 Name MAC address Protocol Address ------------------------------------------------------------------------------- lo 00:00:00:00:00:00 ipv4 127.0.0.1/8 - - ipv6 ::1/128 eth0 52:54:00:2e:45:ce ipv4 10.1.33.188/24 - - ipv6 2001:db8:0:f101::2/64 - - ipv6 fe80::5054:ff:fe2e:45ce/64 eth1 52:54:00:b1:70:19 ipv4 192.168.105.201/16 - - ipv4 192.168.201.195/16 - - ipv6 fe80::5054:ff:feb1:7019/64 eth2 52:54:00:36:2a:e5 N/A N/A eth3 52:54:00:20:70:3d ipv4 192.168.105.240/16 - - ipv6 fe80::5054:ff:fe20:703d/64
Does the above list now change with the default --source of lease? Since the subsequent example lists --source agent, I suspect the above may not be "correct" for eth0.
virsh # domifaddr f20 eth1 --source lease Name MAC address Protocol Address ------------------------------------------------------------------------------- eth1 52:54:00:b1:70:19 ipv4 192.168.105.201/16 - - ipv4 192.168.201.195/16 - - ipv6 fe80::5054:ff:feb1:7019/64
virsh # domifaddr f20 eth0 --source agent --full Name MAC address Protocol Address ------------------------------------------------------------------------------- eth0 52:54:00:2e:45:ce ipv4 10.1.33.188/24 eth0 52:54:00:2e:45:ce ipv6 2001:db8:0:f101::2/64 eth0 52:54:00:2e:45:ce ipv6 fe80::5054:ff:fe2e:45ce/64
tools/virsh.pod * Document new command
Signed-off-by: Nehal J Wani <nehaljw.kkd1@gmail.com> --- tools/virsh-domain-monitor.c | 146 +++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 16 +++++ 2 files changed, 162 insertions(+)
diff --git a/tools/virsh-domain-monitor.c b/tools/virsh-domain-monitor.c index 464ac11..92ce152 100644 --- a/tools/virsh-domain-monitor.c +++ b/tools/virsh-domain-monitor.c @@ -2197,6 +2197,146 @@ cmdDomstats(vshControl *ctl, const vshCmd *cmd) return ret; }
+/* "domifaddr" command + */ +static const vshCmdInfo info_domifaddr[] = { + {"help", N_("Get network interfaces' addresses for a running domain")}, + {"desc", N_("Get network interfaces' addresses for a running domain")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_domifaddr[] = { + {.name = "domain", + .type = VSH_OT_DATA, + .flags = VSH_OFLAG_REQ, + .help = N_("domain name, id or uuid")}, + {.name = "interface", + .type = VSH_OT_STRING, + .flags = VSH_OFLAG_NONE, + .help = N_("network interface name")}, + {.name = "full", + .type = VSH_OT_BOOL, + .flags = VSH_OFLAG_NONE, + .help = N_("display full fields")}, + {.name = "source", + .type = VSH_OT_STRING, + .flags = VSH_OFLAG_NONE, + .help = N_("address source: 'lease' or 'agent'")}, + {.name = NULL} +}; + +static bool +cmdDomIfAddr(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + const char *interface = NULL; + virDomainInterfacePtr *ifaces = NULL; + size_t i, j; + int ifaces_count = 0; + bool ret = false; + bool full = vshCommandOptBool(cmd, "full"); + const char *sourcestr = NULL; + int source = VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE; + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptString(cmd, "interface", &interface) < 0) + goto cleanup; + if (vshCommandOptString(cmd, "source", &sourcestr) < 0) + goto cleanup; + + if (sourcestr) { + if (STREQ(sourcestr, "lease")) { + source = VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE; + } else if (STREQ(sourcestr, "agent")) { + source = VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT; + } else { + vshError(ctl, _("Unknown data source '%s'"), sourcestr); + goto cleanup; + } + } + + if ((ifaces_count = virDomainInterfaceAddresses(dom, &ifaces, source, 0)) < 0) { + vshError(ctl, _("Failed to query for interfaces addresses")); + goto cleanup; + } + + vshPrintExtra(ctl, " %-10s %-20s %-8s %s\n%s%s\n", _("Name"), + _("MAC address"), _("Protocol"), _("Address"), + _("-------------------------------------------------"), + _("------------------------------")); + + for (i = 0; i < ifaces_count; i++) { + virDomainInterfacePtr iface = ifaces[i]; + const char *hwaddr = ""; + const char *ip_addr_str = NULL; + const char *type = NULL; + + if (interface && STRNEQ(interface, iface->name)) + continue; + + hwaddr = iface->hwaddr; + + /* When the interface has no IP address */ + if (!iface->naddrs) { + vshPrintExtra(ctl, " %-10s %-17s %-12s %s\n", + iface->name, hwaddr, "N/A", "N/A"); + continue; + } + + for (j = 0; j < iface->naddrs; j++) { + virBuffer buf = VIR_BUFFER_INITIALIZER; + + switch (iface->addrs[j].type) { + case VIR_IP_ADDR_TYPE_IPV4: + type = "ipv4"; + break; + case VIR_IP_ADDR_TYPE_IPV6: + type = "ipv6"; + break; + } + + virBufferAsprintf(&buf, "%-12s %s/%d", + type, iface->addrs[j].addr, + iface->addrs[j].prefix); + + if (virBufferError(&buf)) { + virBufferFreeAndReset(&buf); + virReportOOMError(); + goto cleanup; + } + + ip_addr_str = virBufferContentAndReset(&buf); + + if (!ip_addr_str) + ip_addr_str = ""; + + /* Don't repeat interface name */ + if (full || !j) + vshPrintExtra(ctl, " %-10s %-17s %s\n", + iface->name, hwaddr, ip_addr_str); + else + vshPrintExtra(ctl, " %-10s %-17s %s\n", + "-", "-", ip_addr_str); + + virBufferFreeAndReset(&buf); + } + } + + ret = true; + + cleanup: + if (ifaces && ifaces_count > 0) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces[i]); + } + VIR_FREE(ifaces); + + virDomainFree(dom); + return ret; +} + const vshCmdDef domMonitoringCmds[] = { {.name = "domblkerror", .handler = cmdDomBlkError, @@ -2234,6 +2374,12 @@ const vshCmdDef domMonitoringCmds[] = { .info = info_domif_getlink, .flags = 0 }, + {.name = "domifaddr", + .handler = cmdDomIfAddr, + .opts = opts_domifaddr, + .info = info_domifaddr, + .flags = 0 + }, {.name = "domiflist", .handler = cmdDomiflist, .opts = opts_domiflist, diff --git a/tools/virsh.pod b/tools/virsh.pod index e65378e..4190485 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -730,6 +730,22 @@ B<Explanation of fields> (fields appear in the following order): flush_total_times - total time flush operations took (ns) <-- other fields provided by hypervisor -->
+ +=item B<domifaddr> I<domain> [I<interface>] [I<--full>] + [I<--source lease|agent>] + +Get a list of interfaces of a running domain along with their IP and MAC +addresses, or limited output just for one interface if I<interface> is +specified. Note that I<interface> can be driver dependent, it can be the name +within guest OS or the name you would see in domain XML. Moreover, the whole +command may require a guest agent to be configured for the queried domain under +some drivers, notably qemu. If I<--full> is specified, the interface name is +always displayed when the interface has multiple addresses or alias, otherwise +it only displays the interface name for the first address, and "-" for the +others. The I<--source> argument specifies what data source to use for the +addresses, currently one of 'lease' to read DHCP leases, or 'agent' to query +the guest OS via an agent. +
If unspecified the default is 'lease'.
=item B<domifstat> I<domain> I<interface-device>
Get network interface stats for a running domain.
ACK with the cleanups John

On Mon, Mar 16, 2015 at 08:43:32AM -0400, John Ferlan wrote:
On 03/11/2015 07:09 AM, Daniel P. Berrange wrote:
From: Nehal J Wani <nehaljw.kkd1@gmail.com>
tools/virsh-domain-monitor.c * Introduce new command : domifaddr Usage: domifaddr <domain> [interface] [--full] [--source lease|agent]
Example outputs: virsh # domifaddr f20 Name MAC address Protocol Address ------------------------------------------------------------------------------- lo 00:00:00:00:00:00 ipv4 127.0.0.1/8 - - ipv6 ::1/128 eth0 52:54:00:2e:45:ce ipv4 10.1.33.188/24 - - ipv6 2001:db8:0:f101::2/64 - - ipv6 fe80::5054:ff:fe2e:45ce/64 eth1 52:54:00:b1:70:19 ipv4 192.168.105.201/16 - - ipv4 192.168.201.195/16 - - ipv6 fe80::5054:ff:feb1:7019/64 eth2 52:54:00:36:2a:e5 N/A N/A eth3 52:54:00:20:70:3d ipv4 192.168.105.240/16 - - ipv6 fe80::5054:ff:fe20:703d/64
Does the above list now change with the default --source of lease?
Yeah, it should be using vnet0, vnet1, etc instead of eth0, eth1, etc at the very least. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Wed, Mar 11, 2015 at 4:39 PM, Daniel P. Berrange <berrange@redhat.com> wrote:
In v10
- Remove 'if' tests before access of hwaddr as it is always present - Switch remote protocol to use remote_nonnull_string for hwaddr as it can never be NUL - Misc typos Eric pointed out
Nehal J Wani (4): domifaddr: Implement the public APIs domifaddr: Implement the remote protocol domifaddr: Implement the API for qemu domifaddr: Add virsh support
daemon/remote.c | 124 ++++++++++++++++++++++++ include/libvirt/libvirt-domain.h | 32 ++++++ src/driver-hypervisor.h | 6 ++ src/libvirt-domain.c | 123 +++++++++++++++++++++++ src/libvirt_public.syms | 2 + src/qemu/qemu_agent.c | 204 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 4 + src/qemu/qemu_driver.c | 175 +++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 103 ++++++++++++++++++++ src/remote/remote_protocol.x | 37 ++++++- src/remote_protocol-structs | 25 +++++ tests/qemuagenttest.c | 188 ++++++++++++++++++++++++++++++++++++ tools/virsh-domain-monitor.c | 146 ++++++++++++++++++++++++++++ tools/virsh.pod | 16 +++ 14 files changed, 1184 insertions(+), 1 deletion(-)
-- 2.1.0
Thank You, Daniel, for v9 and v10. I am quite elated to see this pushed :) The quest started in June 2012 (https://www.redhat.com/archives/libvir-list/2012-June/msg00904.html), resumed in July 2013 (http://www.redhat.com/archives/libvir-list/2013-July/msg01553.html) and was granted entry in March 2015 :) Quite a long journey! -- Nehal J Wani
participants (3)
-
Daniel P. Berrange
-
John Ferlan
-
Nehal J Wani