[libvirt] [PATCH v3 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. We agreed on this in previous versions. 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 | 124 +++++++++++++++++++++++++++++++ examples/python/Makefile.am | 2 +- examples/python/README | 1 + examples/python/domipaddrs.py | 50 +++++++++++++ include/libvirt/libvirt.h.in | 36 +++++++++ python/generator.py | 1 + python/libvirt-override-api.xml | 6 ++ python/libvirt-override.c | 115 ++++++++++++++++++++++++++++ src/driver.h | 5 + src/libvirt.c | 98 ++++++++++++++++++++++++ src/libvirt_public.syms | 1 + src/qemu/qemu_agent.c | 156 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 3 + src/qemu/qemu_driver.c | 76 +++++++++++++++++++ src/remote/remote_driver.c | 88 ++++++++++++++++++++++ src/remote/remote_protocol.x | 24 ++++++- src/remote_protocol-structs | 24 ++++++ tools/virsh-domain-monitor.c | 112 ++++++++++++++++++++++++++++ tools/virsh.pod | 11 +++ 19 files changed, 931 insertions(+), 2 deletions(-) create mode 100644 examples/python/domipaddrs.py -- 1.7.8.6

This API returns dynamically allocated array of IP addresses for all domain interfaces. --- include/libvirt/libvirt.h.in | 35 +++++++++++++++ python/generator.py | 1 + src/driver.h | 5 ++ src/libvirt.c | 98 ++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 1 + 5 files changed, 140 insertions(+), 0 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index d21d029..d5d131a 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -1713,6 +1713,41 @@ 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 */ +}; + +typedef enum { + VIR_DOMAIN_INTERFACE_ADDRS_DEFAULT = 0, /* hypervisor choice */ +} virDomainInterfacesAddressesFlags; + +int virDomainInterfacesAddresses (virDomainPtr dom, + virDomainInterfacePtr *ifaces, + unsigned int flags); + /* Management of domain block devices */ int virDomainBlockPeek (virDomainPtr dom, diff --git a/python/generator.py b/python/generator.py index 6559ece..7c1a51d 100755 --- a/python/generator.py +++ b/python/generator.py @@ -427,6 +427,7 @@ skip_impl = ( 'virDomainGetDiskErrors', 'virConnectUnregisterCloseCallback', 'virConnectRegisterCloseCallback', + 'virDomainInterfacesAddresses', ) qemu_skip_impl = ( diff --git a/src/driver.h b/src/driver.h index aab9766..4460dc9 100644 --- a/src/driver.h +++ b/src/driver.h @@ -404,6 +404,10 @@ typedef int const char *device, virTypedParameterPtr params, int *nparams, unsigned int flags); +typedef int + (*virDrvDomainInterfacesAddresses) (virDomainPtr dom, + virDomainInterfacePtr *ifaces, + unsigned int flags); typedef int (*virDrvDomainMemoryStats) @@ -1012,6 +1016,7 @@ struct _virDriver { virDrvDomainSnapshotListNames domainSnapshotListNames; virDrvDomainListAllSnapshots domainListAllSnapshots; virDrvDomainSnapshotNumChildren domainSnapshotNumChildren; + virDrvDomainInterfacesAddresses domainInterfacesAddresses; virDrvDomainSnapshotListChildrenNames domainSnapshotListChildrenNames; virDrvDomainSnapshotListAllChildren domainSnapshotListAllChildren; virDrvDomainSnapshotLookupByName domainSnapshotLookupByName; diff --git a/src/libvirt.c b/src/libvirt.c index 3c4bf8c..7cb1c58 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -7380,6 +7380,104 @@ error: } /** + * virDomainInterfacesAddresses: + * @dom: domain object + * @ifaces: array of @dom interfaces + * @flags: bitwise-OR of virDomainInterfacesAddressesFlags + * + * 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 then returned. + * + * Note that for some combination of @flags and 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; + * int ifaces_count = 0; + * int i, j; + * virDomainPtr dom = ... obtain a domain here ...; + * + * ifaces_count = virDomainInterfacesAddresses(dom, &ifaces, 0); + * if (ifaces_count < 0) { + * reportError(); + * 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: the number of item in @ifaces, + * -1 in case of error + */ +int +virDomainInterfacesAddresses (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__); + goto error; + } + + conn = dom->conn; + + virCheckNonNullArgGoto(ifaces, error); + + if (conn->driver->domainInterfacesAddresses) { + int ret; + ret = conn->driver->domainInterfacesAddresses(dom, ifaces, flags); + if (ret < 0) + goto error; + return ret; + } + + virLibDomainError(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 e3ba119..8649f04 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -549,6 +549,7 @@ LIBVIRT_0.10.0 { virDomainGetHostname; virConnectRegisterCloseCallback; virConnectUnregisterCloseCallback; + virDomainInterfacesAddresses; } LIBVIRT_0.9.13; # .... define new API here using predicted next version number .... -- 1.7.8.6

--- tools/virsh-domain-monitor.c | 93 ++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 10 +++++ 2 files changed, 103 insertions(+), 0 deletions(-) diff --git a/tools/virsh-domain-monitor.c b/tools/virsh-domain-monitor.c index 151a8d0..fb6fe23 100644 --- a/tools/virsh-domain-monitor.c +++ b/tools/virsh-domain-monitor.c @@ -1683,6 +1683,98 @@ cleanup: } #undef FILTER +/* "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; + 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; + } + + ifaces_count = virDomainInterfacesAddresses(dom, &ifaces, flags); + if (ifaces_count < 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; +} + static const vshCmdDef domMonitoringCmds[] = { {"domblkerror", cmdDomBlkError, opts_domblkerror, info_domblkerror, 0}, {"domblkinfo", cmdDomblkinfo, opts_domblkinfo, info_domblkinfo, 0}, @@ -1690,6 +1782,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 35613c4..daf5889 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -605,6 +605,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.6

This command returns an array of all guest interfaces among with their IP and HW addresses. --- src/qemu/qemu_agent.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_agent.h | 3 + 2 files changed, 159 insertions(+), 0 deletions(-) diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c index 14bf11b..2ada3fe 100644 --- a/src/qemu/qemu_agent.c +++ b/src/qemu/qemu_agent.c @@ -1410,3 +1410,159 @@ qemuAgentSuspend(qemuAgentPtr mon, virJSONValueFree(reply); return ret; } + +int +qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr *ifaces) +{ + 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"))) { + 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; + } + + if (size && 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) { + virReportError(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) { + virReportError(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 (ip_addr_arr_size && + 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) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("something has went really wrong")); + goto cleanup; + } + + 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 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 { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown ip address type '%s'"), + type); + goto cleanup; + } + + 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 cleanup; + } + if (!(ip_addr->addr = strdup(addr))) { + virReportOOMError(); + goto cleanup; + } + + if (virJSONValueObjectGetNumberInt(ip_addr_obj, "prefix", + &ip_addr->prefix) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed 'prefix' field")); + goto cleanup; + } + } + } + + ret = size; + +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 860e7e5..a971ca8 100644 --- a/src/qemu/qemu_agent.h +++ b/src/qemu/qemu_agent.h @@ -80,4 +80,7 @@ int qemuAgentFSThaw(qemuAgentPtr mon); int qemuAgentSuspend(qemuAgentPtr mon, unsigned int target); + +int qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr *ifaces); #endif /* __QEMU_AGENT_H__ */ -- 1.7.8.6

--- include/libvirt/libvirt.h.in | 1 + src/qemu/qemu_driver.c | 76 ++++++++++++++++++++++++++++++++++++++++++ tools/virsh-domain-monitor.c | 19 ++++++++++ tools/virsh.pod | 19 +++++----- 4 files changed, 106 insertions(+), 9 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index d5d131a..7f25a0a 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -1742,6 +1742,7 @@ struct _virDomainInterface { typedef enum { VIR_DOMAIN_INTERFACE_ADDRS_DEFAULT = 0, /* hypervisor choice */ + VIR_DOMAIN_INTERFACE_ADDRS_GUEST_AGENT = (1 << 0), /* use guest agent */ } virDomainInterfacesAddressesFlags; int virDomainInterfacesAddresses (virDomainPtr dom, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 270e4dd..f90ab41 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -13173,6 +13173,81 @@ qemuListAllDomains(virConnectPtr conn, return ret; } +static int +qemuDomainInterfacesAddresses(virDomainPtr dom, + virDomainInterfacePtr *ifaces, + unsigned int flags) +{ + struct qemud_driver *driver = dom->conn->privateData; + qemuDomainObjPrivatePtr priv = NULL; + virDomainObjPtr vm = NULL; + int ret = -1; + + virCheckFlags(VIR_DOMAIN_INTERFACE_ADDRS_DEFAULT | + VIR_DOMAIN_INTERFACE_ADDRS_GUEST_AGENT, -1); + + if (!ifaces) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("ifaces 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); + virReportError(VIR_ERR_NO_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + goto cleanup; + } + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + priv = vm->privateData; + + 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; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto endjob; + } + + qemuDomainObjEnterAgent(driver, vm); + ret = qemuAgentGetInterfaces(priv->agent, ifaces); + 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, @@ -13338,6 +13413,7 @@ static virDriver qemuDriver = { .domainPMSuspendForDuration = qemuDomainPMSuspendForDuration, /* 0.9.11 */ .domainPMWakeup = qemuDomainPMWakeup, /* 0.9.11 */ .domainGetCPUStats = qemuDomainGetCPUStats, /* 0.9.11 */ + .domainInterfacesAddresses = qemuDomainInterfacesAddresses, /* 0.10.0 */ }; diff --git a/tools/virsh-domain-monitor.c b/tools/virsh-domain-monitor.c index fb6fe23..b4a5088 100644 --- a/tools/virsh-domain-monitor.c +++ b/tools/virsh-domain-monitor.c @@ -1694,6 +1694,7 @@ static const vshCmdInfo info_domifaddr[] = { 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")}, + {"mode", VSH_OT_STRING, VSH_OFLAG_NONE, N_("querying mode: default|agent")}, {NULL, 0, 0, NULL} }; @@ -1703,6 +1704,7 @@ cmdDomIfAddr(vshControl *ctl, const vshCmd *cmd) virDomainPtr dom = NULL; const char *device = NULL; virDomainInterfacePtr ifaces = NULL; + const char *mode = NULL; int i, j, ifaces_count = 0; unsigned int flags = 0; bool ret = false; @@ -1717,6 +1719,23 @@ cmdDomIfAddr(vshControl *ctl, const vshCmd *cmd) goto cleanup; } + if (vshCommandOptString(cmd, "mode", &mode) < 0) { + vshError(ctl, "%s", _("Invalid type")); + return false; + } + + if (mode) { + if (STREQ(mode, "default")) { + flags |= VIR_DOMAIN_INTERFACE_ADDRS_DEFAULT; + } else if (STREQ(mode, "agent")) { + flags |= VIR_DOMAIN_INTERFACE_ADDRS_GUEST_AGENT; + } else { + vshError(ctl, _("Unknown mode %s value, " + "expecting 'default' or 'agent'"), mode); + return false; + } + } + ifaces_count = virDomainInterfacesAddresses(dom, &ifaces, flags); if (ifaces_count < 0) { vshError(ctl, _("Failed to query for interfaces addresses")); diff --git a/tools/virsh.pod b/tools/virsh.pod index daf5889..f4f7a33 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -605,15 +605,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<domifaddr> [I<--mode default,agent>] 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, upon +I<--mode> choice command may require a guest agent to be configured for the +queried domain under some drivers, notably qemu. Accepted values for I<--mode> +are 'default' (meaning hypervisor will choose the most suitable method) and +'agent' (guest agent is used). =item B<domifstat> I<domain> I<interface-device> -- 1.7.8.6

--- daemon/remote.c | 124 ++++++++++++++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 88 ++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 24 ++++++++- src/remote_protocol-structs | 24 ++++++++ 4 files changed, 259 insertions(+), 1 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index d25717c..4b44a5a 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -4112,3 +4112,127 @@ no_memory: virReportOOMError(); return -1; } + +static int +remoteSerializeDomainInterfacePtr(virDomainInterfacePtr ifaces, + int ifaces_count, + remote_domain_interfaces_addresses_ret *ret) +{ + int i, j; + + ret->ifaces.ifaces_len = ifaces_count; + + if (ifaces_count && + VIR_ALLOC_N(ret->ifaces.ifaces_val, ifaces_count) < 0) + goto no_memory; + + 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; + } + + iface_ret->ip_addrs.ip_addrs_len = iface->ip_addrs_count; + + if (iface->ip_addrs_count && + VIR_ALLOC_N(iface_ret->ip_addrs.ip_addrs_val, + iface->ip_addrs_count) < 0) + goto no_memory; + + 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; + 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; + + ifaces_count = virDomainInterfacesAddresses(dom, &ifaces, args->flags); + if (ifaces_count < 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) { + 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 b9e2127..4306789 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -5060,6 +5060,93 @@ done: return rv; } +static int +remoteDomainInterfacesAddresses(virDomainPtr dom, + virDomainInterfacePtr *ifaces, + 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; + } + + for (i = 0; i < ret.ifaces.ifaces_len; 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 && + 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 = ret.ifaces.ifaces_len; + +cleanup: + if (rv < 0) { + for (i = 0; i < ret.ifaces.ifaces_len; 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) { @@ -5367,6 +5454,7 @@ static virDriver remote_driver = { .domainSetMetadata = remoteDomainSetMetadata, /* 0.9.10 */ .domainGetMetadata = remoteDomainGetMetadata, /* 0.9.10 */ .domainGetHostname = remoteDomainGetHostname, /* 0.10.0 */ + .domainInterfacesAddresses = remoteDomainInterfacesAddresses, /* 0.10.0 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 200fe75..821d485 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; @@ -2854,7 +2875,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_LIST_ALL_SNAPSHOTS = 274, /* skipgen skipgen priority:high */ REMOTE_PROC_DOMAIN_SNAPSHOT_LIST_ALL_CHILDREN = 275, /* skipgen skipgen priority:high */ REMOTE_PROC_DOMAIN_EVENT_BALLOON_CHANGE = 276, /* autogen autogen */ - REMOTE_PROC_DOMAIN_GET_HOSTNAME = 277 /* autogen autogen */ + REMOTE_PROC_DOMAIN_GET_HOSTNAME = 277, /* autogen autogen */ + REMOTE_PROC_DOMAIN_INTERFACES_ADDRESSES = 278 /* skipgen skipgen */ /* * Notice how the entries are grouped in sets of 10 ? diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 8d09138..970f2d2 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -354,6 +354,29 @@ struct remote_domain_get_interface_parameters_ret { } params; 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; + struct { + u_int ip_addrs_len; + remote_domain_ip_addr * ip_addrs_val; + } ip_addrs; +}; +struct remote_domain_interfaces_addresses_args { + remote_nonnull_domain dom; + u_int flags; +}; +struct remote_domain_interfaces_addresses_ret { + struct { + u_int ifaces_len; + remote_domain_interface * ifaces_val; + } ifaces; +}; struct remote_domain_memory_stats_args { remote_nonnull_domain dom; u_int maxStats; @@ -2259,4 +2282,5 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_SNAPSHOT_LIST_ALL_CHILDREN = 275, REMOTE_PROC_DOMAIN_EVENT_BALLOON_CHANGE = 276, REMOTE_PROC_DOMAIN_GET_HOSTNAME = 277, + REMOTE_PROC_DOMAIN_INTERFACES_ADDRESSES = 278, }; -- 1.7.8.6

--- python/libvirt-override-api.xml | 6 ++ python/libvirt-override.c | 115 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 0 deletions(-) diff --git a/python/libvirt-override-api.xml b/python/libvirt-override-api.xml index 67ef36e..eb08151 100644 --- a/python/libvirt-override-api.xml +++ b/python/libvirt-override-api.xml @@ -487,5 +487,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 8b41dff..7081626 100644 --- a/python/libvirt-override.c +++ b/python/libvirt-override.c @@ -3982,6 +3982,120 @@ 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; + int ifaces_count = 0; + int i, j; + 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 = virDomainInterfacesAddresses(domain, &ifaces, flags); + LIBVIRT_END_ALLOW_THREADS; + if (ifaces_count < 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 @@ -5915,6 +6029,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.6

--- 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.6
participants (1)
-
Michal Privoznik