[libvirt] [PATCH 0/7] Introduce API for dumping domain IP addresses

This feature has been requested for a very long time. However, we had to wait for guest agent to obtain reliable results as user might create totally different structure of interfaces than seen from outside (e.g. bonding, virtual interfaces, etc.). That's the main reason why sniffing for domain traffic can return bogus results. Fortunately, qemu guest agent implement requested part for a while so nothing holds us back anymore. To make matters worse, guest OS can assign whatever name to an interface and changing MAC inside guest isn't propagated to the host which in the end see original one. Therefore, finding correlation between interface within guest and the host side end is left as exercise for mgmt applications. This API is called virDomainInterfacesAddresses (okay, maybe too many plurals) and returns a dynamically allocated array of virDomainInterface struct. The great disadvantage once this gets released, it's written in stone and we cannot change or add an item into it. Therefore we might add a padding into it - something like reserved for future use. On the other hand, everything important is already there - what else we will want to add? :) There are basically two approaches: 1) two APIs: one for list interfaces names, the other for querying addresses on singe interface 2) one API that returns everything I've chosen the latter as it's race free (query for interface that has gone meanwhile). Michal Privoznik (7): Introduce virDomainInterfacesAddresses API virsh: Expose virDomainInterfacesAddresses qemu_agent: Implement 'guest-network-get-interfaces' command handling qemu: Implement virDomainInterfacesAddresses remote: Implement virDomainInterfacesAddresses python: Expose virDomainInterfacesAddresses python: create example for dumping domain IP addresses daemon/remote.c | 122 ++++++++++++++++++++++++++++++ examples/python/Makefile.am | 2 +- examples/python/README | 1 + examples/python/domipaddrs.py | 50 ++++++++++++ include/libvirt/libvirt.h.in | 32 ++++++++ python/generator.py | 1 + python/libvirt-override-api.xml | 6 ++ python/libvirt-override.c | 116 ++++++++++++++++++++++++++++ src/driver.h | 6 ++ src/libvirt.c | 101 +++++++++++++++++++++++++ src/libvirt_public.syms | 5 + src/qemu/qemu_agent.c | 158 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 4 + src/qemu/qemu_driver.c | 76 +++++++++++++++++++ src/remote/remote_driver.c | 95 +++++++++++++++++++++++ src/remote/remote_protocol.x | 25 ++++++- tools/virsh.c | 92 +++++++++++++++++++++++ tools/virsh.pod | 10 +++ 18 files changed, 900 insertions(+), 2 deletions(-) create mode 100644 examples/python/domipaddrs.py -- 1.7.8.5

This API returns dynamically allocated array of IP addresses for all domain interfaces. --- include/libvirt/libvirt.h.in | 32 +++++++++++++ python/generator.py | 1 + src/driver.h | 6 +++ src/libvirt.c | 101 ++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 5 ++ 5 files changed, 145 insertions(+), 0 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index da3ce29..604e598 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -1659,6 +1659,38 @@ int virDomainGetInterfaceParameters (virDomainPtr dom, virTypedParameterPtr params, int *nparams, unsigned int flags); +typedef enum { + VIR_IP_ADDR_TYPE_IPV4, + VIR_IP_ADDR_TYPE_IPV6, + +#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 */ + int prefix; /* IP address prefix */ +}; + +typedef struct _virDomainInterface virDomainInterface; +typedef virDomainInterface *virDomainInterfacePtr; +struct _virDomainInterface { + char *name; /* interface name */ + char *hwaddr; /* hardware address */ + unsigned int ip_addrs_count; /* number of items in @ip_addr */ + virDomainIPAddressPtr ip_addrs; /* array of IP addresses */ +}; + +int virDomainInterfacesAddresses (virDomainPtr dom, + virDomainInterfacePtr *ifaces, + unsigned int *ifaces_count, + unsigned int flags); + /* Management of domain block devices */ int virDomainBlockPeek (virDomainPtr dom, diff --git a/python/generator.py b/python/generator.py index 9530867..747d1a8 100755 --- a/python/generator.py +++ b/python/generator.py @@ -425,6 +425,7 @@ skip_impl = ( 'virDomainGetInterfaceParameters', 'virDomainGetCPUStats', 'virDomainGetDiskErrors', + 'virDomainInterfacesAddresses', ) qemu_skip_impl = ( diff --git a/src/driver.h b/src/driver.h index aa7a377..336ad31 100644 --- a/src/driver.h +++ b/src/driver.h @@ -394,6 +394,11 @@ typedef int const char *device, virTypedParameterPtr params, int *nparams, unsigned int flags); +typedef int + (*virDrvDomainInterfacesAddresses) (virDomainPtr dom, + virDomainInterfacePtr *ifaces, + unsigned int *ifaces_count, + unsigned int flags); typedef int (*virDrvDomainMemoryStats) @@ -943,6 +948,7 @@ struct _virDriver { virDrvDomainInterfaceStats domainInterfaceStats; virDrvDomainSetInterfaceParameters domainSetInterfaceParameters; virDrvDomainGetInterfaceParameters domainGetInterfaceParameters; + virDrvDomainInterfacesAddresses domainInterfacesAddresses; virDrvDomainMemoryStats domainMemoryStats; virDrvDomainBlockPeek domainBlockPeek; virDrvDomainMemoryPeek domainMemoryPeek; diff --git a/src/libvirt.c b/src/libvirt.c index 6386ed4..2e30a14 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -7347,6 +7347,107 @@ error: } /** + * virDomainInterfacesAddresses: + * @dom: domain object + * @ifaces: array of @dom interfaces + * @ifaces_count: number of items in @ifaces + * @flags: extra flags; not used yet, so callers should always pass 0 + * + * Return an array of interfaces presented in given domain + * among with their IP and HW addresses. Note, that single + * interface can have multiple or even none IP address. + * + * This API dynamically allocates the virDomainInterfacePtr + * struct based on how much interfaces domain @dom has, + * usually there's 1:1 correlation. The count of elements + * allocated is stored in @ifaces_count. + * + * Note that for some hypervisors 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 HW address or IP range. + * + * @ifaces->name is never NULL, @ifaces->hwaddr might be NULL, + * + * The caller *must* free @ifaces when no longer needed. + * Usual use case looks like this: + * + * virDomainInterfacePtr ifaces = NULL; + * unsigned int ifaces_count = 0; + * unsigned int i, j; + * virDomainPtr dom = ... obtain a domain here ...; + * + * if (virDomainInterfacesAddresses(dom, &ifaces, &ifaces_count, 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].ip_addrs_count; j++) { + * virDomainIPAddressPtr ip_addr = ifaces[i].ip_addrs + j; + * printf("[addr: %s prefix: %d type: %d]", + * ip_addr->addr, ip_addr->prefix, ip_addr->type); + * } + * printf("\n"); + * } + * + * cleanup: + * for (i = 0; i < ifaces_count; i++) { + * free(ifaces[i].name); + * free(ifaces[i].hwaddr); + * for (j = 0; j < ifaces[i].ip_addrs_count; j++) + * free(ifaces[i].ip_addrs[j].addr); + * free(ifaces[i].ip_addrs); + * } + * free(ifaces); + * + * Returns 0 on success, + * -1 in case of error + */ +int +virDomainInterfacesAddresses (virDomainPtr dom, + virDomainInterfacePtr *ifaces, + unsigned int *ifaces_count, + unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "ifaces=%p, ifaces_count=%p, flags=%x", + ifaces, ifaces_count, flags); + + virResetLastError(); + + if (!VIR_IS_CONNECTED_DOMAIN(dom)) { + virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__); + goto error; + } + + conn = dom->conn; + + virCheckNonNullArgGoto(ifaces, error); + virCheckNonNullArgGoto(ifaces_count, error); + + if (conn->driver->domainInterfacesAddresses) { + int ret; + ret = conn->driver->domainInterfacesAddresses(dom, ifaces, + ifaces_count, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(dom ? dom->conn : NULL); + return -1; +} + +/** * virDomainMemoryStats: * @dom: pointer to the domain object * @stats: nr_stats-sized array of stat structures (returned) diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 46c13fb..54b094e 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -534,4 +534,9 @@ LIBVIRT_0.9.11 { virDomainPMWakeup; } LIBVIRT_0.9.10; +LIBVIRT_0.9.13 { + global: + virDomainInterfacesAddresses; +} LIBVIRT_0.9.11; + # .... define new API here using predicted next version number .... -- 1.7.8.5

On 06/08/2012 02:04 AM, Michal Privoznik wrote:
This API returns dynamically allocated array of IP addresses for all domain interfaces. --- include/libvirt/libvirt.h.in | 32 +++++++++++++ python/generator.py | 1 + src/driver.h | 6 +++ src/libvirt.c | 101 ++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 5 ++ 5 files changed, 145 insertions(+), 0 deletions(-)
I still think XML may be the way to go for extensibility reasons. But if we stick with structs...
/** + * virDomainInterfacesAddresses: + * @dom: domain object + * @ifaces: array of @dom interfaces + * @ifaces_count: number of items in @ifaces
Lose the ifaces_count parameter, and instead,
+ * + * Returns 0 on success,
Return the number of ifaces allocated on return, as well as also guaranteeing an extra NULL pointer on the end of the array not counted in the return value.
+LIBVIRT_0.9.13 { + global: + virDomainInterfacesAddresses; +} LIBVIRT_0.9.11;
This will need rebasing, but shouldn't be too hard to figure out. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

--- tools/virsh.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 10 ++++++ 2 files changed, 102 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index abcfbff..9782534 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1621,6 +1621,97 @@ cleanup: } #undef DOMBLKSTAT_LEGACY_PRINT +/* "domifaddr" command + */ +static const vshCmdInfo info_domifaddr[] = { + {"help", N_("get network interfaces addresses for a 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")}, + {NULL, 0, 0, NULL} +}; + +static bool +cmdDomIfAddr(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + const char *device = NULL; + virDomainInterfacePtr ifaces = NULL; + unsigned int i, j, ifaces_count = 0; + unsigned int flags = 0; + bool ret = false; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptString(cmd, "interface", &device) < 0) { + goto cleanup; + } + + if (virDomainInterfacesAddresses(dom, &ifaces, &ifaces_count, flags) < 0) { + vshError(ctl, _("Failed to query for interfaces addresses")); + goto cleanup; + } + + vshPrintExtra(ctl, " %-10s %-17s %s\n%s\n", + _("Name"), _("HW address"), _("IP address"), + "---------------------------------------------------"); + + for (i = 0; i < ifaces_count; i++) { + virDomainInterfacePtr iface = &(ifaces[i]); + virBuffer buf = VIR_BUFFER_INITIALIZER; + const char *hwaddr = ""; + const char *ip_addr_str = NULL; + + if (device && STRNEQ(device, iface->name)) + continue; + + if (iface->hwaddr) + hwaddr = iface->hwaddr; + + for (j = 0; j < iface->ip_addrs_count; j++) { + if (j) + virBufferAddChar(&buf, ' '); + virBufferAsprintf(&buf, "%s/%d", + iface->ip_addrs[j].addr, + iface->ip_addrs[j].prefix); + } + + ip_addr_str = virBufferContentAndReset(&buf); + + if (!ip_addr_str) + ip_addr_str = ""; + + vshPrintExtra(ctl, " %-10s %-17s %s\n", + iface->name, hwaddr, ip_addr_str); + + virBufferFreeAndReset(&buf); + } + + ret = true; + +cleanup: + for (i = 0; i < ifaces_count; i++) { + VIR_FREE(ifaces[i].name); + VIR_FREE(ifaces[i].hwaddr); + for (j = 0; j < ifaces[i].ip_addrs_count; j++) + VIR_FREE(ifaces[i].ip_addrs[j].addr); + VIR_FREE(ifaces[i].ip_addrs); + } + VIR_FREE(ifaces); + + if (dom) + virDomainFree(dom); + return ret; +} + /* "domifstat" command */ static const vshCmdInfo info_domifstat[] = { @@ -17468,6 +17559,7 @@ static const vshCmdDef domMonitoringCmds[] = { {"domblkstat", cmdDomblkstat, opts_domblkstat, info_domblkstat, 0}, {"domcontrol", cmdDomControl, opts_domcontrol, info_domcontrol, 0}, {"domif-getlink", cmdDomIfGetLink, opts_domif_getlink, info_domif_getlink, 0}, + {"domifaddr", cmdDomIfAddr, opts_domifaddr, info_domifaddr, 0}, {"domiflist", cmdDomiflist, opts_domiflist, info_domiflist, 0}, {"domifstat", cmdDomIfstat, opts_domifstat, info_domifstat, 0}, {"dominfo", cmdDominfo, opts_dominfo, info_dominfo, 0}, diff --git a/tools/virsh.pod b/tools/virsh.pod index ef71717..07386ad 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -562,6 +562,16 @@ B<Explanation of fields> (fields appear in the folowing order): flush_total_times - total time flush operations took (ns) <-- other fields provided by hypervisor --> +=item B<domifaddr> I<domain> [I<interface-device>] + +Get a list of interfaces of domain among with their IP and hardware +addresses, or if I<interface-device> is specified limit output just +for that one interface. Note, that interface name can be driver +dependent meaning it can be 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. + =item B<domifstat> I<domain> I<interface-device> Get network interface stats for a running domain. -- 1.7.8.5

On 06/08/2012 04:04 AM, Michal Privoznik wrote:
--- tools/virsh.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 10 ++++++ 2 files changed, 102 insertions(+), 0 deletions(-)
diff --git a/tools/virsh.c b/tools/virsh.c
+ +cleanup: + for (i = 0; i< ifaces_count; i++) { + VIR_FREE(ifaces[i].name); + VIR_FREE(ifaces[i].hwaddr); + for (j = 0; j< ifaces[i].ip_addrs_count; j++) + VIR_FREE(ifaces[i].ip_addrs[j].addr); + VIR_FREE(ifaces[i].ip_addrs); + } + VIR_FREE(ifaces);
I just skimmed over the code and I saw this block a couple of times. I think it could be worth to provide a function for this in src/util/virdomaininterface.c. Stefan

This command returns an array of all guest interfaces among with their IP and HW addresses. --- src/qemu/qemu_agent.c | 158 +++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 4 + 2 files changed, 162 insertions(+), 0 deletions(-) diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c index bc4ceff..1a434c1 100644 --- a/src/qemu/qemu_agent.c +++ b/src/qemu/qemu_agent.c @@ -1370,3 +1370,161 @@ qemuAgentSuspend(qemuAgentPtr mon, virJSONValueFree(reply); return ret; } + +int +qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr *ifaces, + unsigned int *ifaces_count) +{ + int ret = -1; + int i, j, size = -1; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + virJSONValuePtr ret_array = NULL; + + cmd = qemuAgentMakeCommand("guest-network-get-interfaces", NULL); + + if (!cmd) + return -1; + + if (qemuAgentCommand(mon, cmd, &reply) < 0 || + qemuAgentCheckError(cmd, reply) < 0) + goto cleanup; + + if (!(ret_array = virJSONValueObjectGet(reply, "return"))) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'return' field")); + goto cleanup; + } + + if ((size = virJSONValueArraySize(ret_array)) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't return an array of interfaces")); + goto cleanup; + } + + *ifaces_count = (unsigned int) size; + + if (VIR_ALLOC_N(*ifaces, size) < 0) { + virReportOOMError(); + goto cleanup; + } + + for (i = 0; i < size; i++) { + virJSONValuePtr tmp_iface = virJSONValueArrayGet(ret_array, i); + virJSONValuePtr ip_addr_arr = NULL; + const char *name, *hwaddr; + int ip_addr_arr_size; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!tmp_iface) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("something has went really wrong")); + goto cleanup; + } + + /* interface name is required to be presented */ + name = virJSONValueObjectGetString(tmp_iface, "name"); + if (!name) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'name' field")); + goto cleanup; + } + + if (!((*ifaces)[i].name = strdup(name))) { + virReportOOMError(); + goto cleanup; + } + + /* hwaddr might be omitted */ + hwaddr = virJSONValueObjectGetString(tmp_iface, "hardware-address"); + if (hwaddr && !((*ifaces)[i].hwaddr = strdup(hwaddr))) { + virReportOOMError(); + goto cleanup; + } + + /* 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'? */ + continue; + } + + (*ifaces)[i].ip_addrs_count = (unsigned int) ip_addr_arr_size; + + if (VIR_ALLOC_N((*ifaces)[i].ip_addrs, ip_addr_arr_size) < 0) { + virReportOOMError(); + goto cleanup; + } + + for (j = 0; j < ip_addr_arr_size; j++) { + virJSONValuePtr ip_addr_obj = virJSONValueArrayGet(ip_addr_arr, j); + virDomainIPAddressPtr ip_addr = &(*ifaces)[i].ip_addrs[j]; + const char *type, *addr; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!ip_addr_obj) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("something has went really wrong")); + goto cleanup; + } + + type = virJSONValueObjectGetString(ip_addr_obj, "ip-address-type"); + if (!type) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("qemu agent didn't provide 'ip-address-type'" + " field for interface '%s'"), name); + goto cleanup; + } 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 { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown ip address type '%s'"), + type); + goto cleanup; + } + + addr = virJSONValueObjectGetString(ip_addr_obj, "ip-address"); + if (!addr) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, + _("qemu agent didn't provide 'ip-address'" + " field for interface '%s'"), name); + goto cleanup; + } + if (!(ip_addr->addr = strdup(addr))) { + virReportOOMError(); + goto cleanup; + } + + if (virJSONValueObjectGetNumberInt(ip_addr_obj, "prefix", + &ip_addr->prefix) < 0) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed 'prefix' field")); + goto cleanup; + } + } + } + + ret = 0; + +cleanup: + if (ret < 0 && *ifaces && size> 0) { + for (i = 0; i < size; i++) { + VIR_FREE((*ifaces)[i].name); + VIR_FREE((*ifaces)[i].hwaddr); + for (j = 0; j < (*ifaces)[i].ip_addrs_count; j++) + VIR_FREE((*ifaces)[i].ip_addrs[j].addr); + VIR_FREE((*ifaces)[i].ip_addrs); + } + VIR_FREE(*ifaces); + } + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} diff --git a/src/qemu/qemu_agent.h b/src/qemu/qemu_agent.h index 98c23b0..55d0920 100644 --- a/src/qemu/qemu_agent.h +++ b/src/qemu/qemu_agent.h @@ -71,4 +71,8 @@ int qemuAgentFSThaw(qemuAgentPtr mon); int qemuAgentSuspend(qemuAgentPtr mon, unsigned int target); + +int qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr *ifaces, + unsigned int *ifaces_count); #endif /* __QEMU_AGENT_H__ */ -- 1.7.8.5

--- src/qemu/qemu_driver.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 76 insertions(+), 0 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index d3f74d2..e662fbd 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -12946,6 +12946,81 @@ cleanup: return ret; } +static int +qemuDomainInterfacesAddresses(virDomainPtr dom, + virDomainInterfacePtr *ifaces, + unsigned int *ifaces_count, + unsigned int flags) +{ + struct qemud_driver *driver = dom->conn->privateData; + qemuDomainObjPrivatePtr priv = NULL; + virDomainObjPtr vm = NULL; + unsigned long long ret = -1; + + virCheckFlags(0, -1); + + if (!ifaces || !ifaces_count) { + qemuReportError(VIR_ERR_INVALID_ARG, "%s", + _("ifaces and ifaces_count cannot be NULL")); + return -1; + } + + qemuDriverLock(driver); + vm = virDomainFindByUUID(&driver->domains, dom->uuid); + qemuDriverUnlock(driver); + + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + virUUIDFormat(dom->uuid, uuidstr); + qemuReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + priv = vm->privateData; + + if (priv->agentError) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("QEMU guest agent is not " + "available due to an error")); + goto cleanup; + } + + if (!priv->agent) { + qemuReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("QEMU guest agent is not configured")); + goto cleanup; + } + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto endjob; + } + + qemuDomainObjEnterAgent(driver, vm); + ret = qemuAgentGetInterfaces(priv->agent, ifaces, ifaces_count); + qemuDomainObjExitAgent(driver, vm); + +endjob: + if (qemuDomainObjEndJob(driver, vm) == 0) + vm = NULL; + +cleanup: + if (vm) + virDomainObjUnlock(vm); + return ret; +} + static virDriver qemuDriver = { .no = VIR_DRV_QEMU, .name = QEMU_DRIVER_NAME, @@ -13106,6 +13181,7 @@ static virDriver qemuDriver = { .domainPMSuspendForDuration = qemuDomainPMSuspendForDuration, /* 0.9.11 */ .domainPMWakeup = qemuDomainPMWakeup, /* 0.9.11 */ .domainGetCPUStats = qemuDomainGetCPUStats, /* 0.9.11 */ + .domainInterfacesAddresses = qemuDomainInterfacesAddresses, /* 0.9.13 */ }; -- 1.7.8.5

--- daemon/remote.c | 122 ++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 95 ++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 25 ++++++++- 3 files changed, 241 insertions(+), 1 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index a02c09b..029b695 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -3907,3 +3907,125 @@ no_memory: virReportOOMError(); return -1; } + +static int +remoteSerializeDomainInterfacePtr(virDomainInterfacePtr ifaces, + unsigned int ifaces_count, + remote_domain_interfaces_addresses_ret *ret) +{ + int i, j; + + if (VIR_ALLOC_N(ret->ifaces.ifaces_val, ifaces_count) < 0) + goto no_memory; + + 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 (!(iface_ret->name = strdup(iface->name))) + goto no_memory; + + if (iface->hwaddr) { + char **hwaddr_p = NULL; + if (VIR_ALLOC(hwaddr_p) < 0) + goto no_memory; + *hwaddr_p = strdup(iface->hwaddr); + if (!hwaddr_p) + goto no_memory; + + iface_ret->hwaddr = hwaddr_p; + } + + if (VIR_ALLOC_N(iface_ret->ip_addrs.ip_addrs_val, + iface->ip_addrs_count) < 0) + goto no_memory; + + iface_ret->ip_addrs.ip_addrs_len = iface->ip_addrs_count; + + for (j = 0; j < iface->ip_addrs_count; j++) { + virDomainIPAddressPtr ip_addr = &(iface->ip_addrs[j]); + remote_domain_ip_addr *ip_addr_ret = + &(iface_ret->ip_addrs.ip_addrs_val[j]); + + if (!(ip_addr_ret->addr = strdup(ip_addr->addr))) + goto no_memory; + + ip_addr_ret->prefix = ip_addr->prefix; + ip_addr_ret->type = ip_addr->type; + } + } + + return 0; + +no_memory: + 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->ip_addrs.ip_addrs_len; j++) { + remote_domain_ip_addr *ip_addr = + &(iface_ret->ip_addrs.ip_addrs_val[j]); + VIR_FREE(ip_addr->addr); + } + } + VIR_FREE(ret->ifaces.ifaces_val); + } + virReportOOMError(); + return -1; +} + +static int +remoteDispatchDomainInterfacesAddresses( + virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + remote_domain_interfaces_addresses_args *args, + remote_domain_interfaces_addresses_ret *ret) +{ + int rv = -1; + virDomainPtr dom = NULL; + virDomainInterfacePtr ifaces = NULL; + unsigned int ifaces_count = 0; + struct daemonClientPrivate *priv = + virNetServerClientGetPrivateData(client); + + if (!priv->conn) { + virNetError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + if (!(dom = get_nonnull_domain(priv->conn, args->dom))) + goto cleanup; + + if (virDomainInterfacesAddresses(dom, &ifaces, + &ifaces_count, args->flags) < 0) + goto cleanup; + + if (remoteSerializeDomainInterfacePtr(ifaces, ifaces_count, ret) < 0) + goto cleanup; + + rv = 0; + +cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + if (dom) + virDomainFree(dom); + if (ifaces) { + unsigned int i, j; + + for (i = 0; i < ifaces_count; i++) { + VIR_FREE(ifaces[i].name); + VIR_FREE(ifaces[i].hwaddr); + for (j = 0; j < ifaces[i].ip_addrs_count; j++) + VIR_FREE(ifaces[i].ip_addrs[j].addr); + VIR_FREE(ifaces[i].ip_addrs); + } + VIR_FREE(ifaces); + } + return rv; +} diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 299cd69..43d7049 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -4810,6 +4810,100 @@ done: return rv; } +static int +remoteDomainInterfacesAddresses(virDomainPtr dom, + virDomainInterfacePtr *ifaces, + unsigned int *ifaces_count, + unsigned int flags) +{ + int rv = -1; + remote_domain_interfaces_addresses_args args; + remote_domain_interfaces_addresses_ret ret; + struct private_data *priv = dom->conn->privateData; + int i, j; + + memset(&ret, 0, sizeof(ret)); + args.flags = flags; + make_nonnull_domain(&args.dom, dom); + + remoteDriverLock(priv); + + if (call(dom->conn, priv, 0, REMOTE_PROC_DOMAIN_INTERFACES_ADDRESSES, + (xdrproc_t)xdr_remote_domain_interfaces_addresses_args, + (char *)&args, + (xdrproc_t)xdr_remote_domain_interfaces_addresses_ret, + (char *)&ret) == -1) { + goto done; + } + + if (ret.ifaces.ifaces_len && + VIR_ALLOC_N(*ifaces, ret.ifaces.ifaces_len) < 0) { + virReportOOMError(); + goto cleanup; + } + + *ifaces_count = ret.ifaces.ifaces_len; + + for (i = 0; i < *ifaces_count; i++) { + virDomainInterfacePtr iface = &((*ifaces)[i]); + remote_domain_interface *iface_ret = &(ret.ifaces.ifaces_val[i]); + + if (!(iface->name = strdup(iface_ret->name))) { + virReportOOMError(); + goto cleanup; + } + + if (iface_ret->hwaddr && + !(iface->hwaddr = strdup(*iface_ret->hwaddr))) { + virReportOOMError(); + goto cleanup; + } + + iface->ip_addrs_count = iface_ret->ip_addrs.ip_addrs_len; + + if (iface->ip_addrs_count) { + if (VIR_ALLOC_N(iface->ip_addrs, iface->ip_addrs_count) < 0) { + virReportOOMError(); + goto cleanup; + } + + for (j = 0; j < iface->ip_addrs_count; j++) { + virDomainIPAddressPtr ip_addr = &(iface->ip_addrs[j]); + remote_domain_ip_addr *ip_addr_ret = &(iface_ret->ip_addrs.ip_addrs_val[j]); + + if (!(ip_addr->addr = strdup(ip_addr_ret->addr))) { + virReportOOMError(); + goto cleanup; + } + ip_addr->prefix = ip_addr_ret->prefix; + ip_addr->type = ip_addr_ret->type; + } + } + } + + rv = 0; + +cleanup: + if (rv < 0) { + for (i = 0; i < *ifaces_count; i++) { + VIR_FREE((*ifaces)[i].name); + VIR_FREE((*ifaces)[i].hwaddr); + for (j = 0; j < (*ifaces)[i].ip_addrs_count; j++) + VIR_FREE((*ifaces)[i].ip_addrs[j].addr); + VIR_FREE((*ifaces)[i].ip_addrs); + } + VIR_FREE(*ifaces); + } + xdr_free((xdrproc_t)xdr_remote_domain_interfaces_addresses_ret, + (char *) &ret); +done: + remoteDriverUnlock(priv); + return rv; +} + + + + static void remoteDomainEventQueue(struct private_data *priv, virDomainEventPtr event) { @@ -5111,6 +5205,7 @@ static virDriver remote_driver = { .domainGetDiskErrors = remoteDomainGetDiskErrors, /* 0.9.10 */ .domainSetMetadata = remoteDomainSetMetadata, /* 0.9.10 */ .domainGetMetadata = remoteDomainGetMetadata, /* 0.9.10 */ + .domainInterfacesAddresses = remoteDomainInterfacesAddresses, /* 0.9.13 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index d8a6576..6f1949d 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -653,6 +653,27 @@ struct remote_domain_get_interface_parameters_ret { int nparams; }; +struct remote_domain_ip_addr { + int type; + remote_nonnull_string addr; + int prefix; +}; + +struct remote_domain_interface { + remote_nonnull_string name; + remote_string hwaddr; + remote_domain_ip_addr ip_addrs<>; +}; + +struct remote_domain_interfaces_addresses_args { + remote_nonnull_domain dom; + unsigned int flags; +}; + +struct remote_domain_interfaces_addresses_ret { + remote_domain_interface ifaces<>; +}; + struct remote_domain_memory_stats_args { remote_nonnull_domain dom; unsigned int maxStats; @@ -2782,7 +2803,9 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_PM_WAKEUP = 267, /* autogen autogen */ REMOTE_PROC_DOMAIN_EVENT_TRAY_CHANGE = 268, /* autogen autogen */ REMOTE_PROC_DOMAIN_EVENT_PMWAKEUP = 269, /* autogen autogen */ - REMOTE_PROC_DOMAIN_EVENT_PMSUSPEND = 270 /* autogen autogen */ + REMOTE_PROC_DOMAIN_EVENT_PMSUSPEND = 270, /* autogen autogen */ + + REMOTE_PROC_DOMAIN_INTERFACES_ADDRESSES = 271 /* skipgen skipgen */ /* * Notice how the entries are grouped in sets of 10 ? -- 1.7.8.5

--- python/libvirt-override-api.xml | 6 ++ python/libvirt-override.c | 116 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 0 deletions(-) diff --git a/python/libvirt-override-api.xml b/python/libvirt-override-api.xml index 0bafd21..d2ea831 100644 --- a/python/libvirt-override-api.xml +++ b/python/libvirt-override-api.xml @@ -469,5 +469,11 @@ <arg name='domain' type='virDomainPtr' info='a domain object'/> <arg name='flags' type='unsigned int' info='unused, always pass 0'/> </function> + <function name='virDomainInterfacesAddresses' file='python'> + <info>returns array of IP addresses for all domain interfaces</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 among with their HW and IP addresses"/> + </function> </symbols> </api> diff --git a/python/libvirt-override.c b/python/libvirt-override.c index 130e702..6d0287d 100644 --- a/python/libvirt-override.c +++ b/python/libvirt-override.c @@ -3829,6 +3829,121 @@ cleanup: return py_retval; } +static PyObject * +libvirt_virDomainInterfacesAddresses(PyObject *self ATTRIBUTE_UNUSED, + PyObject *args) +{ + PyObject *py_retval = VIR_PY_NONE; + virDomainPtr domain; + PyObject *pyobj_domain; + unsigned int flags; + virDomainInterfacePtr ifaces = NULL; + unsigned int ifaces_count = 0; + unsigned int 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; + ret = virDomainInterfacesAddresses(domain, &ifaces, &ifaces_count, flags); + 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->ip_addrs_count) { + if (!(py_ip_addrs = PyList_New(iface->ip_addrs_count))) { + { Py_DECREF(py_iface); } + goto no_memory; + } + } else { + py_ip_addrs = VIR_PY_NONE; + } + + for (j = 0; j < iface->ip_addrs_count; j++) { + virDomainIPAddressPtr ip_addr = &(iface->ip_addrs[j]); + PyObject *py_addr = PyDict_New(); + const char *type; + + if (!py_addr) { + { Py_DECREF(py_iface); } + { Py_DECREF(py_ip_addrs); } + goto no_memory; + } + + switch (ip_addr->type) { + case VIR_IP_ADDR_TYPE_IPV4: + type = "ipv4"; + break; + case VIR_IP_ADDR_TYPE_IPV6: + type = "ipv6"; + break; + default: + type = "unknown"; + break; + } + + 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"), + PyString_FromString(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: + for (i = 0; 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 + * retyrning PY_NONE or equivalent */ + if (full_free) { + VIR_FREE(ifaces[i].name); + VIR_FREE(ifaces[i].hwaddr); + for (j = 0; j < ifaces[i].ip_addrs_count; j++) + VIR_FREE(ifaces[i].ip_addrs[j].addr); + } + VIR_FREE(ifaces[i].ip_addrs); + } + 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 @@ -5709,6 +5824,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 *) "virDomainInterfacesAddresses", libvirt_virDomainInterfacesAddresses, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL} }; -- 1.7.8.5

--- examples/python/Makefile.am | 2 +- examples/python/README | 1 + examples/python/domipaddrs.py | 50 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletions(-) create mode 100644 examples/python/domipaddrs.py diff --git a/examples/python/Makefile.am b/examples/python/Makefile.am index b04d126..b51f729 100644 --- a/examples/python/Makefile.am +++ b/examples/python/Makefile.am @@ -4,4 +4,4 @@ EXTRA_DIST= \ README \ consolecallback.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..d895740 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 among with their HW 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 100644 index 0000000..74c0716 --- /dev/null +++ b/examples/python/domipaddrs.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# domipaddrds - print IP addresses for running domain + +import libvirt +import sys + +def usage(): + print "Usage: %s [URI] DOMAIN" % sys.argv[0] + print " Print IP addresses assigned to domain interfaces" + +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.openReadOnly(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.interfacesAddresses(0) +if (ifaces == None): + print "Failed to get domain interfaces" + sys.exit(0) + +print " {0:10} {1:17} {2}".format("Interface", "HW address", "IP Address") + +for (name, val) in ifaces.iteritems(): + print " {0:10} {1:17}".format(name, val['hwaddr']), + + if (val['ip_addrs'] <> None): + print " ", + for addr in val['ip_addrs']: + print "{0}/{1} ".format(addr['addr'], addr['prefix']), + + print -- 1.7.8.5

On 06/08/2012 04:04 PM, Michal Privoznik wrote:
This feature has been requested for a very long time. However, I'm very glad to see this patch :-) we had to wait for guest agent to obtain reliable results as Yeah, it's a good way to get guest IP by GA, the disadvantage is we have to depend on GA installation the guest, sometimes, it probably is a trouble thing, for example, I use virt-v2v to convert a existing ESX VM into libvirt then the ESX VM impossible had GA installation ago, if I want to automate to finish all of stuff, but I don't know VM IP address, so I can't automatically install GA in the converted VM, it may be a special case for you.
However, 'tcpdump' or 'nmap' can do the same thing without GA installation in the guest, maybe, we may have optional solution for how to get guest IP, of course, it's just my idea.
user might create totally different structure of interfaces than seen from outside (e.g. bonding, virtual interfaces, etc.). That's the main reason why sniffing for domain traffic can return bogus results. Fortunately, qemu guest agent implement requested part for a while so nothing holds us back anymore.
To make matters worse, guest OS can assign whatever name to an interface and changing MAC inside guest isn't propagated to the host which in the end see original one.
Therefore, finding correlation between interface within guest and the host side end is left as exercise for mgmt applications.
This API is called virDomainInterfacesAddresses (okay, maybe too many plurals) and returns a dynamically allocated array of virDomainInterface struct. The great disadvantage once this gets released, it's written in stone and we cannot change or add an item into it. Therefore we might add a padding into it - something like reserved for future use. On the other hand, everything important is already there - what else we will want to add? :)
There are basically two approaches: 1) two APIs: one for list interfaces names, the other for querying addresses on singe interface
2) one API that returns everything
I've chosen the latter as it's race free (query for interface that has gone meanwhile).
Michal Privoznik (7): Introduce virDomainInterfacesAddresses API virsh: Expose virDomainInterfacesAddresses qemu_agent: Implement 'guest-network-get-interfaces' command handling qemu: Implement virDomainInterfacesAddresses remote: Implement virDomainInterfacesAddresses python: Expose virDomainInterfacesAddresses python: create example for dumping domain IP addresses
daemon/remote.c | 122 ++++++++++++++++++++++++++++++ examples/python/Makefile.am | 2 +- examples/python/README | 1 + examples/python/domipaddrs.py | 50 ++++++++++++ include/libvirt/libvirt.h.in | 32 ++++++++ python/generator.py | 1 + python/libvirt-override-api.xml | 6 ++ python/libvirt-override.c | 116 ++++++++++++++++++++++++++++ src/driver.h | 6 ++ src/libvirt.c | 101 +++++++++++++++++++++++++ src/libvirt_public.syms | 5 + src/qemu/qemu_agent.c | 158 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 4 + src/qemu/qemu_driver.c | 76 +++++++++++++++++++ src/remote/remote_driver.c | 95 +++++++++++++++++++++++ src/remote/remote_protocol.x | 25 ++++++- tools/virsh.c | 92 +++++++++++++++++++++++ tools/virsh.pod | 10 +++ 18 files changed, 900 insertions(+), 2 deletions(-) create mode 100644 examples/python/domipaddrs.py

On 08.06.2012 11:02, Alex Jia wrote:
On 06/08/2012 04:04 PM, Michal Privoznik wrote:
This feature has been requested for a very long time. However, I'm very glad to see this patch :-) we had to wait for guest agent to obtain reliable results as Yeah, it's a good way to get guest IP by GA, the disadvantage is we have to depend on GA installation the guest, sometimes, it probably is a trouble thing, for example, I use virt-v2v to convert a existing ESX VM into libvirt then the ESX VM impossible had GA installation ago, if I want to automate to finish all of stuff, but I don't know VM IP address, so I can't automatically install GA in the converted VM, it may be a special case for you.
However, 'tcpdump' or 'nmap' can do the same thing without GA installation in the guest, maybe, we may have optional solution for how to get guest IP, of course, it's just my idea.
I do agree. But a guest can be evil and send spoofed packets, or just run another guest inside which makes it hard to recognize which guest an IP belongs to. But I think this can be implemented anyway - but must make sure users know abut certain unreliability of such method. Michal

On 06/08/2012 05:13 PM, Michal Privoznik wrote:
On 08.06.2012 11:02, Alex Jia wrote:
On 06/08/2012 04:04 PM, Michal Privoznik wrote:
This feature has been requested for a very long time. However, I'm very glad to see this patch :-) we had to wait for guest agent to obtain reliable results as Yeah, it's a good way to get guest IP by GA, the disadvantage is we have to depend on GA installation the guest, sometimes, it probably is a trouble thing, for example, I use virt-v2v to convert a existing ESX VM into libvirt then the ESX VM impossible had GA installation ago, if I want to automate to finish all of stuff, but I don't know VM IP address, so I can't automatically install GA in the converted VM, it may be a special case for you.
However, 'tcpdump' or 'nmap' can do the same thing without GA installation in the guest, maybe, we may have optional solution for how to get guest IP, of course, it's just my idea. I do agree. But a guest can be evil and send spoofed packets, or just Indeed. run another guest inside which makes it hard to recognize which guest an IP belongs to. But I think this can be implemented anyway - but must It should be not a question, because we may easy to know guest's MAC address then map MAC address into IP address. make sure users know abut certain unreliability of such method.
Michal

On Fri, Jun 08, 2012 at 11:13:53AM +0200, Michal Privoznik wrote:
On 08.06.2012 11:02, Alex Jia wrote:
On 06/08/2012 04:04 PM, Michal Privoznik wrote:
This feature has been requested for a very long time. However, I'm very glad to see this patch :-) we had to wait for guest agent to obtain reliable results as Yeah, it's a good way to get guest IP by GA, the disadvantage is we have to depend on GA installation the guest, sometimes, it probably is a trouble thing, for example, I use virt-v2v to convert a existing ESX VM into libvirt then the ESX VM impossible had GA installation ago, if I want to automate to finish all of stuff, but I don't know VM IP address, so I can't automatically install GA in the converted VM, it may be a special case for you.
However, 'tcpdump' or 'nmap' can do the same thing without GA installation in the guest, maybe, we may have optional solution for how to get guest IP, of course, it's just my idea.
I do agree. But a guest can be evil and send spoofed packets, or just run another guest inside which makes it hard to recognize which guest an
Can't the guest agent lie about the machine's interfaces too? Cheers, -- Guido
IP belongs to. But I think this can be implemented anyway - but must make sure users know abut certain unreliability of such method.
Michal
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On 06/08/2012 02:04 AM, Michal Privoznik wrote:
This feature has been requested for a very long time. However, we had to wait for guest agent to obtain reliable results as user might create totally different structure of interfaces than seen from outside (e.g. bonding, virtual interfaces, etc.). That's the main reason why sniffing for domain traffic can return bogus results. Fortunately, qemu guest agent implement requested part for a while so nothing holds us back anymore.
How hard would it be to wire this API up to _also_ have the option of using our nwfilter IP learning code (first packet detection mode has been here for a while, and DHCP snooping mode was just added)? Use of the flags parameter should make it possible to force which method we attempt, use of flags==0 chooses the best method possible (GA if present, otherwise fall back to nwfilter IP learning).
This API is called virDomainInterfacesAddresses (okay, maybe too many plurals) and returns a dynamically allocated array of virDomainInterface struct. The great disadvantage once this gets released, it's written in stone and we cannot change or add an item into it. Therefore we might add a padding into it - something like reserved for future use. On the other hand, everything important is already there - what else we will want to add? :)
But that's why we invented virTypedParameter. If you would return an allocated array of virTypedParameter instead of a hard-coded virDomainInterface struct, then you have the flexibility to add new named parameters down the road without ABI concerns. I haven't looked closely at your proposal yet, but just reading this one paragraph makes me think we need to rework it into using typed parameters. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Fri, Jun 08, 2012 at 06:13:43AM -0600, Eric Blake wrote:
On 06/08/2012 02:04 AM, Michal Privoznik wrote:
This feature has been requested for a very long time. However, we had to wait for guest agent to obtain reliable results as user might create totally different structure of interfaces than seen from outside (e.g. bonding, virtual interfaces, etc.). That's the main reason why sniffing for domain traffic can return bogus results. Fortunately, qemu guest agent implement requested part for a while so nothing holds us back anymore.
How hard would it be to wire this API up to _also_ have the option of using our nwfilter IP learning code (first packet detection mode has been here for a while, and DHCP snooping mode was just added)? Use of the flags parameter should make it possible to force which method we attempt, use of flags==0 chooses the best method possible (GA if present, otherwise fall back to nwfilter IP learning).
Yes, we should do this in the same way we handled the virDomainShutdown and virDomainReboot APIs enum { VIR_DOMAIN_INTERFACE_ADDRS_DEFAULT, VIR_DOMAIN_INTERFACE_ADDRS_GUEST_AGENT, VIR_DOMAIN_INTERFACE_ADDRS_ARP_SNOOP, VIR_DOMAIN_INTERFACE_ADDRS_DHCP_SNOOP, }
This API is called virDomainInterfacesAddresses (okay, maybe too many plurals) and returns a dynamically allocated array of virDomainInterface struct. The great disadvantage once this gets released, it's written in stone and we cannot change or add an item into it. Therefore we might add a padding into it - something like reserved for future use. On the other hand, everything important is already there - what else we will want to add? :)
But that's why we invented virTypedParameter. If you would return an allocated array of virTypedParameter instead of a hard-coded virDomainInterface struct, then you have the flexibility to add new named parameters down the road without ABI concerns. I haven't looked closely at your proposal yet, but just reading this one paragraph makes me think we need to rework it into using typed parameters.
Hmm, yes, using virTypedParameter could be quite a nice way to make this more flexible, without going all the way to using a XML document. 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 06/08/2012 08:13 AM, Eric Blake wrote:
This feature has been requested for a very long time. However, we had to wait for guest agent to obtain reliable results as user might create totally different structure of interfaces than seen from outside (e.g. bonding, virtual interfaces, etc.). That's the main reason why sniffing for domain traffic can return bogus results. Fortunately, qemu guest agent implement requested part for a while so nothing holds us back anymore. How hard would it be to wire this API up to _also_ have the option of using our nwfilter IP learning code (first packet detection mode has been here for a while, and DHCP snooping mode was just added)? Use of
On 06/08/2012 02:04 AM, Michal Privoznik wrote: the flags parameter should make it possible to force which method we attempt, use of flags==0 chooses the best method possible (GA if present, otherwise fall back to nwfilter IP learning).
I would have to change some things in the still outstanding patch 5/5 to provide calls to pull an array of virIPLease (= virSocketAddr + time_t) from the DHCP Snooping and Learning code rather tan having them write their stuff into a buffer. I'll fix this. This should then make it a lot easier to pull that data. Either method may return no array if it has not been activated. Stefan

On Fri, Jun 08, 2012 at 10:04:31AM +0200, Michal Privoznik wrote:
This feature has been requested for a very long time. However, we had to wait for guest agent to obtain reliable results as user might create totally different structure of interfaces than seen from outside (e.g. bonding, virtual interfaces, etc.). That's the main reason why sniffing for domain traffic can return bogus results. Fortunately, qemu guest agent implement requested part for a while so nothing holds us back anymore.
To make matters worse, guest OS can assign whatever name to an interface and changing MAC inside guest isn't propagated to the host which in the end see original one.
Therefore, finding correlation between interface within guest and the host side end is left as exercise for mgmt applications.
This API is called virDomainInterfacesAddresses (okay, maybe too many plurals) and returns a dynamically allocated array of virDomainInterface struct. The great disadvantage once this gets released, it's written in stone and we cannot change or add an item into it. Therefore we might add a padding into it - something like reserved for future use. On the other hand, everything important is already there - what else we will want to add? :)
How about returning an XML document instead of a struct? We've been burned by structs in the past...
There are basically two approaches: 1) two APIs: one for list interfaces names, the other for querying addresses on singe interface
2) one API that returns everything
I've chosen the latter as it's race free (query for interface that has gone meanwhile).
Michal Privoznik (7): Introduce virDomainInterfacesAddresses API virsh: Expose virDomainInterfacesAddresses qemu_agent: Implement 'guest-network-get-interfaces' command handling qemu: Implement virDomainInterfacesAddresses remote: Implement virDomainInterfacesAddresses python: Expose virDomainInterfacesAddresses python: create example for dumping domain IP addresses
daemon/remote.c | 122 ++++++++++++++++++++++++++++++ examples/python/Makefile.am | 2 +- examples/python/README | 1 + examples/python/domipaddrs.py | 50 ++++++++++++ include/libvirt/libvirt.h.in | 32 ++++++++ python/generator.py | 1 + python/libvirt-override-api.xml | 6 ++ python/libvirt-override.c | 116 ++++++++++++++++++++++++++++ src/driver.h | 6 ++ src/libvirt.c | 101 +++++++++++++++++++++++++ src/libvirt_public.syms | 5 + src/qemu/qemu_agent.c | 158 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 4 + src/qemu/qemu_driver.c | 76 +++++++++++++++++++ src/remote/remote_driver.c | 95 +++++++++++++++++++++++ src/remote/remote_protocol.x | 25 ++++++- tools/virsh.c | 92 +++++++++++++++++++++++ tools/virsh.pod | 10 +++ 18 files changed, 900 insertions(+), 2 deletions(-) create mode 100644 examples/python/domipaddrs.py
-- 1.7.8.5
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On 08.06.2012 15:26, Dave Allan wrote:
On Fri, Jun 08, 2012 at 10:04:31AM +0200, Michal Privoznik wrote:
This feature has been requested for a very long time. However, we had to wait for guest agent to obtain reliable results as user might create totally different structure of interfaces than seen from outside (e.g. bonding, virtual interfaces, etc.). That's the main reason why sniffing for domain traffic can return bogus results. Fortunately, qemu guest agent implement requested part for a while so nothing holds us back anymore.
To make matters worse, guest OS can assign whatever name to an interface and changing MAC inside guest isn't propagated to the host which in the end see original one.
Therefore, finding correlation between interface within guest and the host side end is left as exercise for mgmt applications.
This API is called virDomainInterfacesAddresses (okay, maybe too many plurals) and returns a dynamically allocated array of virDomainInterface struct. The great disadvantage once this gets released, it's written in stone and we cannot change or add an item into it. Therefore we might add a padding into it - something like reserved for future use. On the other hand, everything important is already there - what else we will want to add? :)
How about returning an XML document instead of a struct? We've been burned by structs in the past...
If we decide to go this way, then we should return JSON which is easier to parse within C code. However, the most easy to use is current proposal IMO. Michal

On 06/08/2012 08:39 AM, Michal Privoznik wrote:
This API is called virDomainInterfacesAddresses (okay, maybe too many plurals) and returns a dynamically allocated array of virDomainInterface struct. The great disadvantage once this gets released, it's written in stone and we cannot change or add an item into it. Therefore we might add a padding into it - something like reserved for future use. On the other hand, everything important is already there - what else we will want to add? :)
How about returning an XML document instead of a struct? We've been burned by structs in the past...
If we decide to go this way, then we should return JSON which is easier to parse within C code. However, the most easy to use is current proposal IMO.
We have a history of returning XML for anything that needs to be easily extensible. An application compiling against libvirt.so already has to know how to parse XML, but currently does not have to know how to parse JSON. I'd much rather stick with XML than make life harder for applications to learn two formats. I agree that XPath notation is a bit harder than direct field access, but I think the goal of future extensibility outweighs the goal of ease-of-representation if the simpler representation will lock us into an API nightmare. -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Fri, Jun 08, 2012 at 04:39:06PM +0200, Michal Privoznik wrote:
On 08.06.2012 15:26, Dave Allan wrote:
On Fri, Jun 08, 2012 at 10:04:31AM +0200, Michal Privoznik wrote:
This feature has been requested for a very long time. However, we had to wait for guest agent to obtain reliable results as user might create totally different structure of interfaces than seen from outside (e.g. bonding, virtual interfaces, etc.). That's the main reason why sniffing for domain traffic can return bogus results. Fortunately, qemu guest agent implement requested part for a while so nothing holds us back anymore.
To make matters worse, guest OS can assign whatever name to an interface and changing MAC inside guest isn't propagated to the host which in the end see original one.
Therefore, finding correlation between interface within guest and the host side end is left as exercise for mgmt applications.
This API is called virDomainInterfacesAddresses (okay, maybe too many plurals) and returns a dynamically allocated array of virDomainInterface struct. The great disadvantage once this gets released, it's written in stone and we cannot change or add an item into it. Therefore we might add a padding into it - something like reserved for future use. On the other hand, everything important is already there - what else we will want to add? :)
How about returning an XML document instead of a struct? We've been burned by structs in the past...
If we decide to go this way, then we should return JSON which is easier to parse within C code. However, the most easy to use is current proposal IMO.
I agree with Eric that we shouldn't introduce a new text based data format, since we already use XML everywhere. If someone really likes XML, it ought to be possible to write a general purpose API which transforms any XML document into a JSON document & vica-verca. 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 Fri, Jun 08, 2012 at 09:26:35AM -0400, Dave Allan wrote:
On Fri, Jun 08, 2012 at 10:04:31AM +0200, Michal Privoznik wrote:
This feature has been requested for a very long time. However, we had to wait for guest agent to obtain reliable results as user might create totally different structure of interfaces than seen from outside (e.g. bonding, virtual interfaces, etc.). That's the main reason why sniffing for domain traffic can return bogus results. Fortunately, qemu guest agent implement requested part for a while so nothing holds us back anymore.
To make matters worse, guest OS can assign whatever name to an interface and changing MAC inside guest isn't propagated to the host which in the end see original one.
Therefore, finding correlation between interface within guest and the host side end is left as exercise for mgmt applications.
This API is called virDomainInterfacesAddresses (okay, maybe too many plurals) and returns a dynamically allocated array of virDomainInterface struct. The great disadvantage once this gets released, it's written in stone and we cannot change or add an item into it. Therefore we might add a padding into it - something like reserved for future use. On the other hand, everything important is already there - what else we will want to add? :)
How about returning an XML document instead of a struct? We've been burned by structs in the past...
The answer isn't entirely clearcut. I think it depends on what we envision the scope of the APIs to be. If we think this is strictly limited to IP address discovery then I think a struct is satisfactory. If we think we'll want to provide more details like type of NIC, MTU size, etc, then we'd want an XML document. Personally I think we should pretty strictly limit the scope of the APIs to just IP address lists, leaving anything else upto general purpose guest agents (eg Matahari like thing). 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 :|
participants (7)
-
Alex Jia
-
Daniel P. Berrange
-
Dave Allan
-
Eric Blake
-
Guido Günther
-
Michal Privoznik
-
Stefan Berger