[libvirt] [PATCHv6 0/5] Introduce API to query IP addresses for given domain

This feature has been requested for a very long time. Since qemu guest agent gives us reliable results, now the wait is over. The RFC was first proposed by Michal Privoznik: http://www.redhat.com/archives/libvir-list/2012-February/msg00437.html A patch was submitted, using structs: https://www.redhat.com/archives/libvir-list/2012-June/msg00220.html Another patch was submitted, using XML: https://www.redhat.com/archives/libvir-list/2012-June/msg00904.html Neither of the patches were accepted, probably due to lack of extensibility and usability. Hence, we thought of using virTypedParameters for reporting list of interfaces along with their MAC address and IP addresses. The RFC can be found here: https://www.redhat.com/archives/libvir-list/2013-July/msg00084.html The idea of extensibility was rejected and rendered out of scope of libvirt. Hence, we were back to structs. This API is called virDomainInterfaceAddresses which returns a dynamically allocated array of virDomainInterface struct. The great disadvantage is once this gets released, it's written in stone and we cannot change or add an item into it. The API supports two methods: * Return information (list of all associated interfaces with MAC address and IP addresses) of all of the domain interfaces by default (if no interface name is provided) * Return information for the specified interface (if an interface name is provided) v6: * Inclusion of flags, readonly check for guest agent connection * Correction of memory leaks, other small nits. v5: * s/virDomainInterfacesAddresses/virDomainInterfaceAddresses. * Case for IP aliasing handled using virHashTable. * New test cases added, involving multiple and 0 IP addresse(s) per interface. * IP prefix changed from int to unsigned int. * Changes to practice libvirt habits. * https://www.redhat.com/archives/libvir-list/2013-September/msg00003.html v4: * Various style nits, indentation errors, memory leaks fixed. * https://www.redhat.com/archives/libvir-list/2013-August/msg01265.html v3: * Upper bounds to number of interfaces and addresses per interface introduced. * Change from array of structs to array of pointers * ifaces_count moved from function argument to return value * Changes in variable names * Test cases added for qemuAgentGetInterfaces. * https://www.redhat.com/archives/libvir-list/2013-August/msg01215.html v2: * Logical errors, memory leaks and few other errors fixed. * https://www.redhat.com/archives/libvir-list/2013-August/msg00631.html v1: * http://www.redhat.com/archives/libvir-list/2013-July/msg01553.html Nehal J Wani (5): domifaddr: Implement the public APIs domifaddr: Implement the remote protocol domifaddr: Implement the API for qemu domifaddr: Add virsh support domifaddr: Expose python binding daemon/remote.c | 130 ++++++++++++++++++++++++++ examples/python/Makefile.am | 2 +- examples/python/README | 1 + examples/python/domipaddrs.py | 56 +++++++++++ include/libvirt/libvirt.h.in | 38 ++++++++ python/generator.py | 3 + python/libvirt-override-api.xml | 8 +- python/libvirt-override.c | 102 ++++++++++++++++++++ src/driver.h | 6 ++ src/libvirt.c | 135 +++++++++++++++++++++++++++ src/libvirt_public.syms | 6 ++ src/qemu/qemu_agent.c | 201 ++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 3 + src/qemu/qemu_driver.c | 76 +++++++++++++++ src/remote/remote_driver.c | 99 ++++++++++++++++++++ src/remote/remote_protocol.x | 40 +++++++- src/remote_protocol-structs | 24 +++++ tests/qemuagenttest.c | 188 +++++++++++++++++++++++++++++++++++++ tools/virsh-domain-monitor.c | 131 ++++++++++++++++++++++++++ tools/virsh.pod | 18 ++++ 20 files changed, 1264 insertions(+), 3 deletions(-) create mode 100755 examples/python/domipaddrs.py -- 1.7.11.7

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 lease file of dnsmasq * DHCP snooping At this stage, it will only work with guest agent. Passing other flags will result in error: "Method hasn't been implemented yet" include/libvirt/libvirt.h.in: * Define virDomainInterfaceAddresses, virDomainInterfaceFree * Define structs virDomainInterface, virDomainIPAddress python/generator.py: * Skip the auto-generation for virDomainInterfaceAddresses and virDomainInterfaceFree src/driver.h: * Define domainInterfaceAddresses src/libvirt.c: * Implement virDomainInterfaceAddresses * Implement virDomainInterfaceFree src/libvirt_public.syms: * Export the new symbols --- include/libvirt/libvirt.h.in | 38 ++++++++++++ python/generator.py | 3 + src/driver.h | 6 ++ src/libvirt.c | 135 +++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 6 ++ 5 files changed, 188 insertions(+) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index a47e33c..0995080 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2044,6 +2044,44 @@ int virDomainGetInterfaceParameters (virDomainPtr dom, virTypedParameterPtr params, int *nparams, unsigned int flags); +typedef enum { + VIR_DOMAIN_INTERFACE_ADDRESSES_SNOOP = (1 << 0), /* Snoop traffic */ + VIR_DOMAIN_INTERFACE_ADDRESSES_LEASE = (1 << 1), /* Parse DHCP lease file */ + VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT = (1 << 2), /* Query qemu guest agent */ +} virDomainInterfaceAddressesFlags; + +typedef enum { + VIR_IP_ADDR_TYPE_IPV4 = 0, + VIR_IP_ADDR_TYPE_IPV6 = 1, + +#ifdef VIR_ENUM_SENTINELS + VIR_IP_ADDR_TYPE_LAST, +#endif +} virIPAddrType; + +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 flags); + +void virDomainInterfaceFree(virDomainInterfacePtr iface); + /* Management of domain block devices */ int virDomainBlockPeek (virDomainPtr dom, diff --git a/python/generator.py b/python/generator.py index fb321c6..50f779b 100755 --- a/python/generator.py +++ b/python/generator.py @@ -458,6 +458,7 @@ skip_impl = ( 'virNodeGetMemoryParameters', 'virNodeSetMemoryParameters', 'virNodeGetCPUMap', + 'virDomainInterfaceAddresses', 'virDomainMigrate3', 'virDomainMigrateToURI3', ) @@ -560,6 +561,8 @@ skip_function = ( "virTypedParamsGetString", "virTypedParamsGetUInt", "virTypedParamsGetULLong", + + "virDomainInterfaceFree", # Only useful in C ) lxc_skip_function = ( diff --git a/src/driver.h b/src/driver.h index be64333..210a910 100644 --- a/src/driver.h +++ b/src/driver.h @@ -518,6 +518,11 @@ typedef int unsigned int flags); typedef int +(*virDrvDomainInterfaceAddresses)(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int flags); + +typedef int (*virDrvDomainMemoryStats)(virDomainPtr domain, struct _virDomainMemoryStat *stats, unsigned int nr_stats, @@ -1238,6 +1243,7 @@ struct _virDriver { virDrvDomainInterfaceStats domainInterfaceStats; virDrvDomainSetInterfaceParameters domainSetInterfaceParameters; virDrvDomainGetInterfaceParameters domainGetInterfaceParameters; + virDrvDomainInterfaceAddresses domainInterfaceAddresses; virDrvDomainMemoryStats domainMemoryStats; virDrvDomainBlockPeek domainBlockPeek; virDrvDomainMemoryPeek domainMemoryPeek; diff --git a/src/libvirt.c b/src/libvirt.c index 07a3fd5..7a5759b 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -8643,6 +8643,116 @@ error: return -1; } + /** + * virDomainInterfaceAddresses: + * @dom: domain object + * @ifaces: pointer to an array of pointers pointing to interface objects + * @flags: bitwise-OR of virDomainInterfaceAddressesFlags + * + * Return a pointer to the allocated array of pointers pointing to interfaces + * present in given domain along with their IP and MAC addresses. Note that + * single interface can have multiple or even 0 IP address. + * + * 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. + * + * In case @flags includes VIR_DOMAIN_INTERFACE_ADDRESSES_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. + * + * Both the methods, pasrsing leases file and traffic snooping will return + * the interface name of the form "vnetN", which is different from what guest + * agent returns (like ethN or emN). And since the MAC address from guest agent + * might be different with what @dom XML specifies, we have no way to convert it + * into the names present in @dom config. Hence, it is not recommended to mix + * the flag ..._AGENT with ..._LEASE or ..._SNOOP as it may lead to ambiguous + * results because we cannot be sure if the name came from the agent or from + * the other method. + * + * If 0 is passed as @flags, libvirt will choose the best way, and won't + * include agent in it. + * + * @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, 0)) < 0) + * goto cleanup; + * + * ... do something with returned values, for example: + * for (i = 0; i < ifaces_count; i++) { + * printf("name: %s", ifaces[i]->name); + * if (ifaces[i]->hwaddr) + * 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 flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "ifaces=%p, flags=%x", ifaces, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(dom)) { + virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = dom->conn; + + virCheckNonNullArgGoto(ifaces, error); + + if ((flags & VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT) + && (conn->flags & VIR_CONNECT_RO)) { + virLibConnError(VIR_ERR_OPERATION_DENIED, "%s", + _("Cannot query guest agent in readonly mode")); + goto error; + } + + if (conn->driver->domainInterfaceAddresses) { + int ret; + ret = conn->driver->domainInterfaceAddresses(dom, ifaces, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(dom->conn); + return -1; +} + /** * virDomainMemoryStats: * @dom: pointer to the domain object @@ -21961,3 +22071,28 @@ 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. + */ +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 bbdf78a..35f3a3e 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -634,4 +634,10 @@ LIBVIRT_1.1.1 { virDomainSetMemoryStatsPeriod; } LIBVIRT_1.1.0; +LIBVIRT_1.1.3 { + global: + virDomainInterfaceAddresses; + virDomainInterfaceFree; +} LIBVIRT_1.1.1; + # .... define new API here using predicted next version number .... -- 1.7.11.7

On 06/09/13 23:18, Nehal J Wani wrote: The documentation for virDomainInterfaceAddresses is removed in this version.
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 lease file of dnsmasq * DHCP snooping
At this stage, it will only work with guest agent. Passing other flags will result in error: "Method hasn't been implemented yet"
Hm, not sure if I like this.
include/libvirt/libvirt.h.in: * Define virDomainInterfaceAddresses, virDomainInterfaceFree * Define structs virDomainInterface, virDomainIPAddress
python/generator.py: * Skip the auto-generation for virDomainInterfaceAddresses and virDomainInterfaceFree
src/driver.h: * Define domainInterfaceAddresses
src/libvirt.c: * Implement virDomainInterfaceAddresses * Implement virDomainInterfaceFree
src/libvirt_public.syms: * Export the new symbols
--- include/libvirt/libvirt.h.in | 38 ++++++++++++ python/generator.py | 3 + src/driver.h | 6 ++ src/libvirt.c | 135 +++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 6 ++ 5 files changed, 188 insertions(+)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index a47e33c..0995080 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2044,6 +2044,44 @@ int virDomainGetInterfaceParameters (virDomainPtr dom, virTypedParameterPtr params, int *nparams, unsigned int flags);
+typedef enum { + VIR_DOMAIN_INTERFACE_ADDRESSES_SNOOP = (1 << 0), /* Snoop traffic */ + VIR_DOMAIN_INTERFACE_ADDRESSES_LEASE = (1 << 1), /* Parse DHCP lease file */ + VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT = (1 << 2), /* Query qemu guest agent */
I don't see a good reason for expose the flags which are not supported yet. It will keep biting us until the implementations are done. Especially when you have documentations for them in virsh, API comments. (I see you exposed them as options in 4/5). It's the experience of a storage volume command "virsh vol-resize", which exposed the options "--shrink" and "--allocate" when the volResize API was introduced, but the implementations for them were done after a long time. And as said, it bugged us. Even if we can get the other 2 methods implemented very soon, I guess it won't be achieved in the same release of the API itself. Although what you did is a bit better than volResize (you have a more sensible error "Method has not been implemented yet"), I'm still not fan of exposing flags not supported yet.
+} virDomainInterfaceAddressesFlags; + +typedef enum { + VIR_IP_ADDR_TYPE_IPV4 = 0, + VIR_IP_ADDR_TYPE_IPV6 = 1, + +#ifdef VIR_ENUM_SENTINELS + VIR_IP_ADDR_TYPE_LAST, +#endif +} virIPAddrType; + +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 flags); + +void virDomainInterfaceFree(virDomainInterfacePtr iface); + /* Management of domain block devices */
int virDomainBlockPeek (virDomainPtr dom, diff --git a/python/generator.py b/python/generator.py index fb321c6..50f779b 100755 --- a/python/generator.py +++ b/python/generator.py @@ -458,6 +458,7 @@ skip_impl = ( 'virNodeGetMemoryParameters', 'virNodeSetMemoryParameters', 'virNodeGetCPUMap', + 'virDomainInterfaceAddresses', 'virDomainMigrate3', 'virDomainMigrateToURI3', ) @@ -560,6 +561,8 @@ skip_function = ( "virTypedParamsGetString", "virTypedParamsGetUInt", "virTypedParamsGetULLong", + + "virDomainInterfaceFree", # Only useful in C )
lxc_skip_function = ( diff --git a/src/driver.h b/src/driver.h index be64333..210a910 100644 --- a/src/driver.h +++ b/src/driver.h @@ -518,6 +518,11 @@ typedef int unsigned int flags);
typedef int +(*virDrvDomainInterfaceAddresses)(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int flags); + +typedef int (*virDrvDomainMemoryStats)(virDomainPtr domain, struct _virDomainMemoryStat *stats, unsigned int nr_stats, @@ -1238,6 +1243,7 @@ struct _virDriver { virDrvDomainInterfaceStats domainInterfaceStats; virDrvDomainSetInterfaceParameters domainSetInterfaceParameters; virDrvDomainGetInterfaceParameters domainGetInterfaceParameters; + virDrvDomainInterfaceAddresses domainInterfaceAddresses; virDrvDomainMemoryStats domainMemoryStats; virDrvDomainBlockPeek domainBlockPeek; virDrvDomainMemoryPeek domainMemoryPeek; diff --git a/src/libvirt.c b/src/libvirt.c index 07a3fd5..7a5759b 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -8643,6 +8643,116 @@ error: return -1; }
+ /** + * virDomainInterfaceAddresses: + * @dom: domain object + * @ifaces: pointer to an array of pointers pointing to interface objects + * @flags: bitwise-OR of virDomainInterfaceAddressesFlags + * + * Return a pointer to the allocated array of pointers pointing to interfaces + * present in given domain along with their IP and MAC addresses. Note that + * single interface can have multiple or even 0 IP address. + * + * 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. + * + * In case @flags includes VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT, a configured
[1], because the AGENT flag will be exclusive with the other two flags, you should s/includes/is/,
+ * guest agent is needed for successful return from this API. Moreover, if
s/successful return/successfully returning/,
+ * guest agent is used then the interface name is the one seen by guest OS.
I think "if guest is used" can be removed, since the "flags is _AGENT" already implied it.
+ * To match such interface with the one from @dom XML use MAC address or IP + * range.
s/XML use/XML, use/,
+ * + * Both the methods, pasrsing leases file and traffic snooping will return
s/pasrsing/parsing/,
+ * the interface name of the form "vnetN", which is different from what guest + * agent returns (like ethN or emN).
Guest OS could returns "vnetN", if it has a virtual network interface named like that, so may be just using "(the interface name seen by guest OS)" is better.
And since the MAC address from guest agent + * might be different with what @dom XML specifies, we have no way to convert it + * into the names present in @dom config. Hence, it is not recommended to mix
To be consistent, please use either "@dom XML" or "@dom config".
+ * the flag ..._AGENT with ..._LEASE or ..._SNOOP as it may lead to ambiguous
Not good to have strings like "...._AGENT" in documents.
+ * results because we cannot be sure if the name came from the agent or from + * the other method.
That says _AGENT flag is exclusive with the other two flags. See [1] But anyway, since I think we shouldn't expose the flags not implemented yet, above comments are not needed then.
+ * + * If 0 is passed as @flags, libvirt will choose the best way, and won't + * include agent in it.
I'm thinking if we don't need to be so complicated, by defaulting to one of _LEASE or _SNOOP explicitly. That says (conclusion of all of above), I'm wondering if define the enum like blow is better. typedef enum { VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT = (1 << 1), /* Query qemu guest agent */ } I.E. 0 and (1 << 0) is reserved for lease and snooping methods.
+ * + * @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, 0)) < 0) + * goto cleanup; + * + * ... do something with returned values, for example: + * for (i = 0; i < ifaces_count; i++) { + * printf("name: %s", ifaces[i]->name); + * if (ifaces[i]->hwaddr) + * 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 flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "ifaces=%p, flags=%x", ifaces, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(dom)) { + virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = dom->conn; + + virCheckNonNullArgGoto(ifaces, error); + + if ((flags & VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT) + && (conn->flags & VIR_CONNECT_RO)) { + virLibConnError(VIR_ERR_OPERATION_DENIED, "%s", + _("Cannot query guest agent in readonly mode"));
Follow what other APIs do: virLibDomainError(VIR_ERR_OPERATION_DENIED, __FUNCTION__); goto error; Osier

On 08/09/13 14:43, Osier Yang wrote:
On 06/09/13 23:18, Nehal J Wani wrote:
The documentation for virDomainInterfaceAddresses is removed in this version.
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 lease file of dnsmasq * DHCP snooping
At this stage, it will only work with guest agent. Passing other flags will result in error: "Method hasn't been implemented yet"
Hm, not sure if I like this.
include/libvirt/libvirt.h.in: * Define virDomainInterfaceAddresses, virDomainInterfaceFree * Define structs virDomainInterface, virDomainIPAddress
python/generator.py: * Skip the auto-generation for virDomainInterfaceAddresses and virDomainInterfaceFree
src/driver.h: * Define domainInterfaceAddresses
src/libvirt.c: * Implement virDomainInterfaceAddresses * Implement virDomainInterfaceFree
src/libvirt_public.syms: * Export the new symbols
--- include/libvirt/libvirt.h.in | 38 ++++++++++++ python/generator.py | 3 + src/driver.h | 6 ++ src/libvirt.c | 135 +++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 6 ++ 5 files changed, 188 insertions(+)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index a47e33c..0995080 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2044,6 +2044,44 @@ int virDomainGetInterfaceParameters (virDomainPtr dom, virTypedParameterPtr params, int *nparams, unsigned int flags); +typedef enum { + VIR_DOMAIN_INTERFACE_ADDRESSES_SNOOP = (1 << 0), /* Snoop traffic */ + VIR_DOMAIN_INTERFACE_ADDRESSES_LEASE = (1 << 1), /* Parse DHCP lease file */ + VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT = (1 << 2), /* Query qemu guest agent */
I don't see a good reason for expose the flags which are not supported yet. It will keep biting us until the implementations are done. Especially when you have documentations for them in virsh, API comments. (I see you exposed them as options in 4/5).
It's the experience of a storage volume command "virsh vol-resize", which exposed the options "--shrink" and "--allocate" when the volResize API was introduced, but the implementations for them were done after a long time. And as said, it bugged us.
Even if we can get the other 2 methods implemented very soon, I guess it won't be achieved in the same release of the API itself.
Although what you did is a bit better than volResize (you have a more sensible error "Method has not been implemented yet"), I'm still not fan of exposing flags not supported yet.
+} virDomainInterfaceAddressesFlags; + +typedef enum { + VIR_IP_ADDR_TYPE_IPV4 = 0, + VIR_IP_ADDR_TYPE_IPV6 = 1, + +#ifdef VIR_ENUM_SENTINELS + VIR_IP_ADDR_TYPE_LAST, +#endif +} virIPAddrType; + +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 flags); + +void virDomainInterfaceFree(virDomainInterfacePtr iface); + /* Management of domain block devices */ int virDomainBlockPeek (virDomainPtr dom, diff --git a/python/generator.py b/python/generator.py index fb321c6..50f779b 100755 --- a/python/generator.py +++ b/python/generator.py @@ -458,6 +458,7 @@ skip_impl = ( 'virNodeGetMemoryParameters', 'virNodeSetMemoryParameters', 'virNodeGetCPUMap', + 'virDomainInterfaceAddresses', 'virDomainMigrate3', 'virDomainMigrateToURI3', ) @@ -560,6 +561,8 @@ skip_function = ( "virTypedParamsGetString", "virTypedParamsGetUInt", "virTypedParamsGetULLong", + + "virDomainInterfaceFree", # Only useful in C ) lxc_skip_function = ( diff --git a/src/driver.h b/src/driver.h index be64333..210a910 100644 --- a/src/driver.h +++ b/src/driver.h @@ -518,6 +518,11 @@ typedef int unsigned int flags); typedef int +(*virDrvDomainInterfaceAddresses)(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int flags); + +typedef int (*virDrvDomainMemoryStats)(virDomainPtr domain, struct _virDomainMemoryStat *stats, unsigned int nr_stats, @@ -1238,6 +1243,7 @@ struct _virDriver { virDrvDomainInterfaceStats domainInterfaceStats; virDrvDomainSetInterfaceParameters domainSetInterfaceParameters; virDrvDomainGetInterfaceParameters domainGetInterfaceParameters; + virDrvDomainInterfaceAddresses domainInterfaceAddresses; virDrvDomainMemoryStats domainMemoryStats; virDrvDomainBlockPeek domainBlockPeek; virDrvDomainMemoryPeek domainMemoryPeek; diff --git a/src/libvirt.c b/src/libvirt.c index 07a3fd5..7a5759b 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -8643,6 +8643,116 @@ error: return -1; } + /** + * virDomainInterfaceAddresses: + * @dom: domain object + * @ifaces: pointer to an array of pointers pointing to interface objects + * @flags: bitwise-OR of virDomainInterfaceAddressesFlags + * + * Return a pointer to the allocated array of pointers pointing to interfaces + * present in given domain along with their IP and MAC addresses. Note that + * single interface can have multiple or even 0 IP address. + * + * 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. + * + * In case @flags includes VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT, a configured
[1], because the AGENT flag will be exclusive with the other two flags, you should s/includes/is/,
+ * guest agent is needed for successful return from this API. Moreover, if
s/successful return/successfully returning/,
+ * guest agent is used then the interface name is the one seen by guest OS.
I think "if guest is used" can be removed, since the "flags is _AGENT" already implied it.
+ * To match such interface with the one from @dom XML use MAC address or IP + * range.
s/XML use/XML, use/,
+ * + * Both the methods, pasrsing leases file and traffic snooping will return
s/pasrsing/parsing/,
+ * the interface name of the form "vnetN", which is different from what guest + * agent returns (like ethN or emN).
Guest OS could returns "vnetN", if it has a virtual network interface named like that, so may be just using "(the interface name seen by guest OS)" is better.
And since the MAC address from guest agent + * might be different with what @dom XML specifies, we have no way to convert it + * into the names present in @dom config. Hence, it is not recommended to mix
To be consistent, please use either "@dom XML" or "@dom config".
+ * the flag ..._AGENT with ..._LEASE or ..._SNOOP as it may lead to ambiguous
Not good to have strings like "...._AGENT" in documents.
+ * results because we cannot be sure if the name came from the agent or from + * the other method.
That says _AGENT flag is exclusive with the other two flags. See [1]
But anyway, since I think we shouldn't expose the flags not implemented yet, above comments are not needed then.
+ * + * If 0 is passed as @flags, libvirt will choose the best way, and won't + * include agent in it.
I'm thinking if we don't need to be so complicated, by defaulting to one of _LEASE or _SNOOP explicitly.
That says (conclusion of all of above), I'm wondering if define the enum like blow is better.
typedef enum { VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT = (1 << 1), /* Query qemu guest agent */
Btw, the API might work for other guest agent too, not only *qemu* guest agent. So s/qemu guest/guest/,
}
I.E. 0 and (1 << 0) is reserved for lease and snooping methods.
+ * + * @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, 0)) < 0) + * goto cleanup; + * + * ... do something with returned values, for example: + * for (i = 0; i < ifaces_count; i++) { + * printf("name: %s", ifaces[i]->name); + * if (ifaces[i]->hwaddr) + * 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 flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "ifaces=%p, flags=%x", ifaces, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(dom)) { + virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + virDispatchError(NULL); + return -1; + } + + conn = dom->conn; + + virCheckNonNullArgGoto(ifaces, error); + + if ((flags & VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT) + && (conn->flags & VIR_CONNECT_RO)) { + virLibConnError(VIR_ERR_OPERATION_DENIED, "%s", + _("Cannot query guest agent in readonly mode"));
Follow what other APIs do:
virLibDomainError(VIR_ERR_OPERATION_DENIED, __FUNCTION__); goto error;
Osier

[......]
+ * + * If 0 is passed as @flags, libvirt will choose the best way, and won't + * include agent in it.
I'm thinking if we don't need to be so complicated, by defaulting to one of _LEASE or _SNOOP explicitly.
That says (conclusion of all of above), I'm wondering if define the enum like blow is better.
typedef enum { VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT = (1 << 1), /* Query qemu guest agent */
And s/AGENT/GUEST_AGENT/, since "AGENT" could mean too much than "guest agent"

ping On Sun, Sep 8, 2013 at 2:42 PM, Osier Yang <jyang@redhat.com> wrote:
[......]
+ *
+ * If 0 is passed as @flags, libvirt will choose the best way, and won't + * include agent in it.
I'm thinking if we don't need to be so complicated, by defaulting to one of _LEASE or _SNOOP explicitly.
That says (conclusion of all of above), I'm wondering if define the enum like blow is better.
typedef enum { VIR_DOMAIN_INTERFACE_**ADDRESSES_AGENT = (1 << 1), /* Query qemu guest agent */
And s/AGENT/GUEST_AGENT/, since "AGENT" could mean too much than "guest agent"
-- Nehal J Wani UG3, BTech CS+MS(CL) IIIT-Hyderabad http://commandlinewani.blogspot.com

Eric, could you please share your views on the discussion? On Sun, Sep 8, 2013 at 2:42 PM, Osier Yang <jyang@redhat.com> wrote:
[......]
+ *
+ * If 0 is passed as @flags, libvirt will choose the best way, and won't + * include agent in it.
I'm thinking if we don't need to be so complicated, by defaulting to one of _LEASE or _SNOOP explicitly.
That says (conclusion of all of above), I'm wondering if define the enum like blow is better.
typedef enum { VIR_DOMAIN_INTERFACE_**ADDRESSES_AGENT = (1 << 1), /* Query qemu guest agent */
And s/AGENT/GUEST_AGENT/, since "AGENT" could mean too much than "guest agent"
-- Nehal J Wani UG3, BTech CS+MS(CL) IIIT-Hyderabad http://commandlinewani.blogspot.com

On Sun, Sep 08, 2013 at 02:43:09PM +0800, Osier Yang wrote:
On 06/09/13 23:18, Nehal J Wani wrote:
The documentation for virDomainInterfaceAddresses is removed in this version.
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 lease file of dnsmasq * DHCP snooping
At this stage, it will only work with guest agent. Passing other flags will result in error: "Method hasn't been implemented yet"
Hm, not sure if I like this.
include/libvirt/libvirt.h.in: * Define virDomainInterfaceAddresses, virDomainInterfaceFree * Define structs virDomainInterface, virDomainIPAddress
python/generator.py: * Skip the auto-generation for virDomainInterfaceAddresses and virDomainInterfaceFree
src/driver.h: * Define domainInterfaceAddresses
src/libvirt.c: * Implement virDomainInterfaceAddresses * Implement virDomainInterfaceFree
src/libvirt_public.syms: * Export the new symbols
--- include/libvirt/libvirt.h.in | 38 ++++++++++++ python/generator.py | 3 + src/driver.h | 6 ++ src/libvirt.c | 135 +++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 6 ++ 5 files changed, 188 insertions(+)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index a47e33c..0995080 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2044,6 +2044,44 @@ int virDomainGetInterfaceParameters (virDomainPtr dom, virTypedParameterPtr params, int *nparams, unsigned int flags); +typedef enum { + VIR_DOMAIN_INTERFACE_ADDRESSES_SNOOP = (1 << 0), /* Snoop traffic */ + VIR_DOMAIN_INTERFACE_ADDRESSES_LEASE = (1 << 1), /* Parse DHCP lease file */ + VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT = (1 << 2), /* Query qemu guest agent */
I don't see a good reason for expose the flags which are not supported yet. It will keep biting us until the implementations are done. Especially when you have documentations for them in virsh, API comments. (I see you exposed them as options in 4/5).
I tend to agree that we should only define flags once they are supported by at least one driver. Also if we put the virNetwork DHCP lease series in first, then we can be sure that this API will work when no flags are set too. 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 :|

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 DDoS 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 --- daemon/remote.c | 130 +++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 99 ++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 40 ++++++++++++- src/remote_protocol-structs | 24 ++++++++ 4 files changed, 292 insertions(+), 1 deletion(-) diff --git a/daemon/remote.c b/daemon/remote.c index 2aff7c1..4dff602 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -5137,7 +5137,137 @@ cleanup: return rv; } +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 (iface->hwaddr) { + char **hwaddr_p = NULL; + if (VIR_ALLOC(hwaddr_p) < 0) + goto cleanup; + if (VIR_STRDUP(*hwaddr_p, iface->hwaddr) < 0) { + VIR_FREE(hwaddr_p); + goto cleanup; + } + + iface_ret->hwaddr = hwaddr_p; + } + + 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(iface_ret); + } + 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->flags)) < 0) + goto cleanup; + + if (remoteSerializeDomainInterface(ifaces, ifaces_count, ret) < 0) + goto cleanup; + + rv = 0; + +cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + + virDomainFree(dom); + + if (ifaces) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces[i]); + VIR_FREE(ifaces); + } + + return rv; +} /*----- Helpers. -----*/ diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 62e77a5..db94c07 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -6599,6 +6599,104 @@ done: return rv; } +static int +remoteDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + 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.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++) { + if (VIR_ALLOC(ifaces_ret[i]) < 0) + goto cleanup; + + virDomainInterfacePtr iface = ifaces_ret[i]; + remote_domain_interface *iface_ret = &(ret.ifaces.ifaces_val[i]); + + if (VIR_STRDUP(iface->name, iface_ret->name) < 0) + goto cleanup; + + if (iface_ret->hwaddr && + 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; +} + static void remoteDomainEventQueue(struct private_data *priv, virDomainEventPtr event) { @@ -6933,6 +7031,7 @@ static virDriver remote_driver = { .domainMigratePerform3Params = remoteDomainMigratePerform3Params, /* 1.1.0 */ .domainMigrateFinish3Params = remoteDomainMigrateFinish3Params, /* 1.1.0 */ .domainMigrateConfirm3Params = remoteDomainMigrateConfirm3Params, /* 1.1.0 */ + .domainInterfaceAddresses = remoteDomainInterfaceAddresses, /* 1.1.3 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index a1c23da..cc074b2 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -235,6 +235,16 @@ const REMOTE_DOMAIN_JOB_STATS_MAX = 16; /* UUID. VIR_UUID_BUFLEN definition comes from libvirt.h */ typedef opaque remote_uuid[VIR_UUID_BUFLEN]; +/* + * 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; + /* A domain which may not be NULL. */ struct remote_nonnull_domain { remote_nonnull_string name; @@ -2835,6 +2845,27 @@ struct remote_domain_event_device_removed_msg { remote_nonnull_string devAlias; }; +struct remote_domain_ip_addr { + int type; + remote_nonnull_string addr; + unsigned int prefix; +}; + +struct remote_domain_interface { + remote_nonnull_string name; + remote_string hwaddr; + remote_domain_ip_addr addrs<REMOTE_DOMAIN_IP_ADDR_MAX>; +}; + +struct remote_domain_interface_addresses_args { + remote_nonnull_domain dom; + 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. */ @@ -4998,5 +5029,12 @@ enum remote_procedure { * @generate: both * @acl: none */ - REMOTE_PROC_DOMAIN_EVENT_DEVICE_REMOVED = 311 + REMOTE_PROC_DOMAIN_EVENT_DEVICE_REMOVED = 311, + + /** + * @generate: none + * @acl: domain:read + */ + REMOTE_PROC_DOMAIN_INTERFACE_ADDRESSES = 312 + }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 4e27aae..bde73fd 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -2316,6 +2316,29 @@ struct remote_domain_event_device_removed_msg { remote_nonnull_domain dom; remote_nonnull_string devAlias; }; +struct remote_domain_ip_addr { + int type; + remote_nonnull_string addr; + u_int prefix; +}; +struct remote_domain_interface { + remote_nonnull_string name; + remote_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 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, @@ -2628,4 +2651,5 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_CREATE_XML_WITH_FILES = 309, REMOTE_PROC_DOMAIN_CREATE_WITH_FILES = 310, REMOTE_PROC_DOMAIN_EVENT_DEVICE_REMOVED = 311, + REMOTE_PROC_DOMAIN_INTERFACE_ADDRESSES = 312, }; -- 1.7.11.7

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 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) --- src/qemu/qemu_agent.c | 202 +++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 3 + src/qemu/qemu_driver.c | 74 ++++++++++++++++++ tests/qemuagenttest.c | 188 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 467 insertions(+) diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c index 2cd0ccc..76f8e7c 100644 --- a/src/qemu/qemu_agent.c +++ b/src/qemu/qemu_agent.c @@ -1320,6 +1320,208 @@ cleanup: } /* + * 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, 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++) { + if (VIR_EXPAND_N(iface->addrs, addrs_count, 1) < 0) + goto error; + + virJSONValuePtr ip_addr_obj = virJSONValueArrayGet(ip_addr_arr, j); + virDomainIPAddressPtr ip_addr = &iface->addrs[addrs_count - 1]; + const char *type, *addr; + + /* 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; +} + +/* * qemuAgentFSThaw: * @mon: Agent * diff --git a/src/qemu/qemu_agent.h b/src/qemu/qemu_agent.h index 5fbacdb..58b56fd 100644 --- a/src/qemu/qemu_agent.h +++ b/src/qemu/qemu_agent.h @@ -76,6 +76,9 @@ int qemuAgentFSThaw(qemuAgentPtr mon); int qemuAgentSuspend(qemuAgentPtr mon, unsigned int target); +int qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr **ifaces); + int qemuAgentArbitraryCommand(qemuAgentPtr mon, const char *cmd, char **result, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index e37fe33..9d44c86 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -15882,6 +15882,79 @@ qemuNodeSuspendForDuration(virConnectPtr conn, return nodeSuspendForDuration(target, duration, flags); } +static int +qemuDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int flags) +{ + virQEMUDriverPtr driver = dom->conn->privateData; + qemuDomainObjPrivatePtr priv = NULL; + virDomainObjPtr vm = NULL; + int ret = -1; + + /* We won't allow agent interaction in default behavior */ + if (flags == 0) + flags = VIR_DOMAIN_INTERFACE_ADDRESSES_SNOOP | + VIR_DOMAIN_INTERFACE_ADDRESSES_LEASE; + + virCheckFlags(VIR_DOMAIN_INTERFACE_ADDRESSES_SNOOP | + VIR_DOMAIN_INTERFACE_ADDRESSES_LEASE | + VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT, -1); + + if (!(vm = qemuDomObjFromDomain(dom))) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + priv = vm->privateData; + + if (virDomainInterfaceAddressesEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (flags & VIR_DOMAIN_INTERFACE_ADDRESSES_SNOOP) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Method has not been implemented yet")); + } + + if (flags & VIR_DOMAIN_INTERFACE_ADDRESSES_LEASE) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Method has not been implemented yet")); + } + + if (flags & VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT) { + if (priv->agentError) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("QEMU guest agent is not " + "available due to an error")); + goto cleanup; + } + + if (!priv->agent) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("QEMU guest agent is not configured")); + goto cleanup; + } + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0) + goto cleanup; + + qemuDomainObjEnterAgent(vm); + ret = qemuAgentGetInterfaces(priv->agent, ifaces); + qemuDomainObjExitAgent(vm); + + if (qemuDomainObjEndJob(driver, vm) == 0) + vm = NULL; + } + +cleanup: + if (vm) + virObjectUnlock(vm); + return ret; +} static virDriver qemuDriver = { .no = VIR_DRV_QEMU, @@ -15977,6 +16050,7 @@ static virDriver qemuDriver = { .domainMemoryPeek = qemuDomainMemoryPeek, /* 0.4.4 */ .domainGetBlockInfo = qemuDomainGetBlockInfo, /* 0.8.1 */ .nodeGetCPUStats = qemuNodeGetCPUStats, /* 0.9.3 */ + .domainInterfaceAddresses = qemuDomainInterfaceAddresses, /* 1.1.3 */ .nodeGetMemoryStats = qemuNodeGetMemoryStats, /* 0.9.3 */ .nodeGetCellsFreeMemory = qemuNodeGetCellsFreeMemory, /* 0.4.4 */ .nodeGetFreeMemory = qemuNodeGetFreeMemory, /* 0.4.4 */ diff --git a/tests/qemuagenttest.c b/tests/qemuagenttest.c index 4e27981..ddca48c 100644 --- a/tests/qemuagenttest.c +++ b/tests/qemuagenttest.c @@ -576,6 +576,193 @@ cleanup: 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) @@ -605,6 +792,7 @@ mymain(void) DO_TEST(Shutdown); DO_TEST(CPU); DO_TEST(ArbitraryCommand); + DO_TEST(GetInterfaces); DO_TEST(Timeout); /* Timeout should always be called last */ -- 1.7.11.7

Use virDomainInterfaceAddresses in virsh tools/virsh-domain-monitor.c * Introduce new command : domifaddr Usage: domifaddr <domain> [interface] [--full] [--snoop] [--lease] [--agent] Example outputs: virsh # domifaddr f18 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 f18 eth1 --agent 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 f18 eth0 --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 --- tools/virsh-domain-monitor.c | 131 +++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 18 ++++++ 2 files changed, 149 insertions(+) diff --git a/tools/virsh-domain-monitor.c b/tools/virsh-domain-monitor.c index b29b82a..3690776 100644 --- a/tools/virsh-domain-monitor.c +++ b/tools/virsh-domain-monitor.c @@ -1871,6 +1871,131 @@ cleanup: } #undef FILTER +/* "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[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"interface", VSH_OT_DATA, VSH_OFLAG_NONE, N_("network interface name")}, + {"full", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("display full fields")}, + {"snoop", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("snoop network traffic")}, + {"lease", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("parse dhcp lease file")}, + {"agent", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("query qemu guest agent")}, + {NULL, 0, 0, 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; + unsigned int flags = 0; + bool ret = false; + bool full = vshCommandOptBool(cmd, "full"); + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptString(cmd, "interface", &interface) < 0) { + goto cleanup; + } + + if (vshCommandOptBool(cmd, "snoop")) + flags |= VIR_DOMAIN_INTERFACE_ADDRESSES_SNOOP; + + if (vshCommandOptBool(cmd, "lease")) + flags |= VIR_DOMAIN_INTERFACE_ADDRESSES_LEASE; + + if (vshCommandOptBool(cmd, "agent")) + flags |= VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT; + + if ((ifaces_count = virDomainInterfaceAddresses(dom, &ifaces, flags)) < 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) + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces[i]); + VIR_FREE(ifaces); + + virDomainFree(dom); + return ret; +} + const vshCmdDef domMonitoringCmds[] = { {.name = "domblkerror", .handler = cmdDomBlkError, @@ -1944,5 +2069,11 @@ const vshCmdDef domMonitoringCmds[] = { .info = info_list, .flags = 0 }, + {.name = "domifaddr", + .handler = cmdDomIfAddr, + .opts = opts_domifaddr, + .info = info_domifaddr, + .flags = 0 + }, {.name = NULL} }; diff --git a/tools/virsh.pod b/tools/virsh.pod index 0ae5178..3d3bdc7 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -636,6 +636,24 @@ 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<--snoop>]] [I<--lease>] [I<--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. If I<--snoop> is +specified, network traffic snooping is used, if I<--lease> is specified, +dhcp file is parsed and if I<--agent> is specified, qemu guest agent is +queried. It is not recommended to mix I<--agent> with I<--snoop> or +I<--lease> as it may return ambiguous results. + =item B<domifstat> I<domain> I<interface-device> Get network interface stats for a running domain. -- 1.7.11.7

Expose virDomainInterfacesAddresses to python binding examples/python/Makefile.am: * Add new file domipaddrs.py examples/python/README: * Add documentation for the python example python/libvirt-override-api.xml: * Add new symbol for virDomainInterfacesAddresses python/libvirt-override.c: * Hand written python api Example: $ ./run ./examples/python/domipaddrs.py qemu:///system f18 Interface MAC address Protocol Address lo 00:00:00:00:00:00 ipv4 127.0.0.1/8 lo 00:00:00:00:00:00 ipv6 ::1/128 eth3 52:54:00:20:70:3d ipv4 192.168.105.240/16 eth3 52:54:00:20:70:3d ipv6 fe80::5054:ff:fe20:703d/64 eth2 52:54:00:36:2a:e5 N/A N/A eth1 52:54:00:b1:70:19 ipv4 192.168.105.201/16 eth1 52:54:00:b1:70:19 ipv4 192.168.201.195/16 eth1 52:54:00:b1:70:19 ipv6 fe80::5054:ff:feb1:7019/64 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 --- examples/python/Makefile.am | 2 +- examples/python/README | 1 + examples/python/domipaddrs.py | 57 ++++++++++++++++++++++ python/libvirt-override-api.xml | 8 +++- python/libvirt-override.c | 102 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 2 deletions(-) create mode 100755 examples/python/domipaddrs.py diff --git a/examples/python/Makefile.am b/examples/python/Makefile.am index 7823c20..b77154f 100644 --- a/examples/python/Makefile.am +++ b/examples/python/Makefile.am @@ -18,4 +18,4 @@ EXTRA_DIST= \ README \ consolecallback.py \ topology.py \ - dominfo.py domrestore.py domsave.py domstart.py esxlist.py + dominfo.py domrestore.py domsave.py domstart.py esxlist.py domipaddrs.py diff --git a/examples/python/README b/examples/python/README index f4db76c..1285d52 100644 --- a/examples/python/README +++ b/examples/python/README @@ -10,6 +10,7 @@ domsave.py - save all running domU's into a directory domrestore.py - restore domU's from their saved files in a directory esxlist.py - list active domains of an VMware ESX host and print some info. also demonstrates how to use the libvirt.openAuth() method +domipaddrs.py - print domain interfaces along with their MAC and IP addresses The XML files in this directory are examples of the XML format that libvirt expects, and will have to be adapted for your setup. They are only needed diff --git a/examples/python/domipaddrs.py b/examples/python/domipaddrs.py new file mode 100755 index 0000000..4430f59 --- /dev/null +++ b/examples/python/domipaddrs.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# domipaddrds - print domain interfaces along with their MAC and IP addresses + +import libvirt +import sys + +def usage(): + print "Usage: %s [URI] DOMAIN" % sys.argv[0] + print " Print domain interfaces along with their MAC and IP addresses" + +uri = None +name = None +args = len(sys.argv) + +if args == 2: + name = sys.argv[1] +elif args == 3: + uri = sys.argv[1] + name = sys.argv[2] +else: + usage() + sys.exit(2) + +conn = libvirt.open(uri) +if conn == None: + print "Unable to open connection to libvirt" + sys.exit(1) + +try: + dom = conn.lookupByName(name) +except libvirt.libvirtError: + print "Domain %s not found" % name + sys.exit(0) + +ifaces = dom.interfaceAddresses(libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_AGENT); +if (ifaces == None): + print "Failed to get domain interfaces" + sys.exit(0) + +print " {0:10} {1:20} {2:12} {3}".format("Interface", "MAC address", "Protocol", "Address") + +def toIPAddrType(addrType): + if addrType == libvirt.VIR_IP_ADDR_TYPE_IPV4: + return "ipv4" + elif addrType == libvirt.VIR_IP_ADDR_TYPE_IPV6: + return "ipv6" + +for (name, val) in ifaces.iteritems(): + if val['ip_addrs']: + for addr in val['ip_addrs']: + print " {0:10} {1:19}".format(name, val['hwaddr']), + print " {0:12} {1}/{2} ".format(toIPAddrType(addr['type']), addr['addr'], addr['prefix']), + print + else: + print " {0:10} {1:19}".format(name, val['hwaddr']), + print " {0:12} {1}".format("N/A", "N/A"), + print diff --git a/python/libvirt-override-api.xml b/python/libvirt-override-api.xml index 9a88215..60491de 100644 --- a/python/libvirt-override-api.xml +++ b/python/libvirt-override-api.xml @@ -602,5 +602,11 @@ <arg name='conn' type='virConnectPtr' info='pointer to the hypervisor connection'/> <arg name='flags' type='int' info='unused, pass 0'/> </function> - </symbols> + <function name='virDomainInterfaceAddresses' file='python'> + <info>returns a dictionary of domain interfaces along with their MAC and IP addresses</info> + <arg name='dom' type='virDomainPtr' info='pointer to the domain'/> + <arg name='flags' type='unsigned int' info='extra flags; not used yet, so callers should always pass 0'/> + <return type='virDomainInterfacePtr' info="dictionary of domain interfaces along with their MAC and IP addresses"/> + </function> +</symbols> </api> diff --git a/python/libvirt-override.c b/python/libvirt-override.c index d16b9a2..f288587 100644 --- a/python/libvirt-override.c +++ b/python/libvirt-override.c @@ -4761,6 +4761,107 @@ cleanup: return py_retval; } + +static PyObject * +libvirt_virDomainInterfaceAddresses(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) +{ + PyObject *py_retval = VIR_PY_NONE; + virDomainPtr domain; + PyObject *pyobj_domain; + unsigned int flags; + virDomainInterfacePtr *ifaces = NULL; + int ifaces_count = 0; + size_t i, j; + int ret; + bool full_free = true; + + if (!PyArg_ParseTuple(args, (char *) "Oi:virDomainInterfacePtr", + &pyobj_domain, &flags)) + return NULL; + + domain = (virDomainPtr) PyvirDomain_Get(pyobj_domain); + + LIBVIRT_BEGIN_ALLOW_THREADS; + ifaces_count = virDomainInterfaceAddresses(domain, &ifaces, flags); + ret = ifaces_count; + LIBVIRT_END_ALLOW_THREADS; + if (ret < 0) + goto cleanup; + + if (!(py_retval = PyDict_New())) + goto no_memory; + + for (i = 0; i < ifaces_count; i++) { + virDomainInterfacePtr iface = ifaces[i]; + PyObject *py_ip_addrs = NULL; + PyObject *py_iface = NULL; + + if (!(py_iface = PyDict_New())) + goto no_memory; + + if (iface->naddrs) { + if (!(py_ip_addrs = PyList_New(iface->naddrs))) { + Py_DECREF(py_iface); + goto no_memory; + } + } else { + py_ip_addrs = VIR_PY_NONE; + } + + for (j = 0; j < iface->naddrs; j++) { + virDomainIPAddressPtr ip_addr = &(iface->addrs[j]); + PyObject *py_addr = PyDict_New(); + int type = ip_addr->type; + + if (!py_addr) { + Py_DECREF(py_iface); + Py_DECREF(py_ip_addrs); + goto no_memory; + } + + PyDict_SetItem(py_addr, libvirt_constcharPtrWrap("addr"), + PyString_FromString(ip_addr->addr)); + PyDict_SetItem(py_addr, libvirt_constcharPtrWrap("prefix"), + libvirt_intWrap(ip_addr->prefix)); + PyDict_SetItem(py_addr, libvirt_constcharPtrWrap("type"), + libvirt_intWrap(type)); + + PyList_SetItem(py_ip_addrs, j, py_addr); + } + + PyDict_SetItem(py_iface, libvirt_constcharPtrWrap("ip_addrs"), + py_ip_addrs); + PyDict_SetItem(py_iface, libvirt_constcharPtrWrap("hwaddr"), + libvirt_charPtrWrap(iface->hwaddr)); + + PyDict_SetItem(py_retval, libvirt_charPtrWrap(iface->name), + py_iface); + } + + full_free = false; + +cleanup: + if (ifaces) { + for (i = 0; full_free && i < ifaces_count; i++) { + /* + * We don't want to free values we've just shared with python variables unless + * there was an error and hence we are returning PY_NONE or equivalent + */ + virDomainInterfaceFree(ifaces[i]); + } + } + VIR_FREE(ifaces); + + return py_retval; + +no_memory: + Py_XDECREF(py_retval); + py_retval = PyErr_NoMemory(); + goto cleanup; +} + + /******************************************* * Helper functions to avoid importing modules * for every callback @@ -7284,6 +7385,7 @@ static PyMethodDef libvirtMethods[] = { {(char *) "virDomainBlockPeek", libvirt_virDomainBlockPeek, METH_VARARGS, NULL}, {(char *) "virDomainMemoryPeek", libvirt_virDomainMemoryPeek, METH_VARARGS, NULL}, {(char *) "virDomainGetDiskErrors", libvirt_virDomainGetDiskErrors, METH_VARARGS, NULL}, + {(char *) "virDomainInterfaceAddresses", libvirt_virDomainInterfaceAddresses, METH_VARARGS, NULL}, {(char *) "virNodeGetMemoryParameters", libvirt_virNodeGetMemoryParameters, METH_VARARGS, NULL}, {(char *) "virNodeSetMemoryParameters", libvirt_virNodeSetMemoryParameters, METH_VARARGS, NULL}, {(char *) "virNodeGetCPUMap", libvirt_virNodeGetCPUMap, METH_VARARGS, NULL}, -- 1.7.11.7
participants (3)
-
Daniel P. Berrange
-
Nehal J Wani
-
Osier Yang