diff --git a/src/qemu/qemu_agent.c b/src/qemu/qemu_agent.c index 5fcc40f637..5f90b15bc9 100644 --- a/src/qemu/qemu_agent.c +++ b/src/qemu/qemu_agent.c @@ -1953,3 +1953,206 @@ qemuAgentGetFSInfo(qemuAgentPtr mon, virDomainFSInfoPtr **info, virJSONValueFree(reply); return ret; } + +/* + * qemuAgentGetInterfaces: + * @mon: Agent monitor + * @ifaces: pointer to an array of pointers pointing to interface objects + * + * Issue guest-network-get-interfaces to guest agent, which returns a + * list of interfaces of a running domain along with their IP and MAC + * addresses. + * + * Returns: number of interfaces on success, -1 on error. + */ +int +qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr **ifaces) +{ + int ret = -1; + size_t i, j; + int size = -1; + virJSONValuePtr cmd = NULL; + virJSONValuePtr reply = NULL; + virJSONValuePtr ret_array = NULL; + size_t ifaces_count = 0; + size_t addrs_count = 0; + virDomainInterfacePtr *ifaces_ret = NULL; + virHashTablePtr ifaces_store = NULL; + char **ifname = NULL; + + /* Hash table to handle the interface alias */ + if (!(ifaces_store = virHashCreate(ifaces_count, NULL))) { + virHashFree(ifaces_store); + return -1; + } + + if (!(cmd = qemuAgentMakeCommand("guest-network-get-interfaces", NULL))) + goto cleanup; + + if (qemuAgentCommand(mon, cmd, &reply, false, VIR_DOMAIN_QEMU_AGENT_COMMAND_BLOCK) < 0 || + qemuAgentCheckError(cmd, reply) < 0) { + goto cleanup; + } + + if (!(ret_array = virJSONValueObjectGet(reply, "return"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'return' field")); + goto cleanup; + } + + if ((size = virJSONValueArraySize(ret_array)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't return an array of interfaces")); + goto cleanup; + } + + for (i = 0; i < size; i++) { + virJSONValuePtr tmp_iface = virJSONValueArrayGet(ret_array, i); + virJSONValuePtr ip_addr_arr = NULL; + const char *hwaddr, *ifname_s, *name = NULL; + int ip_addr_arr_size; + virDomainInterfacePtr iface = NULL; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!tmp_iface) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent reply missing interface entry in array")); + goto error; + } + + /* interface name is required to be presented */ + name = virJSONValueObjectGetString(tmp_iface, "name"); + if (!name) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide 'name' field")); + goto error; + } + + /* Handle interface alias (:) */ + ifname = virStringSplit(name, ":", 2); + ifname_s = ifname[0]; + + iface = virHashLookup(ifaces_store, ifname_s); + + /* If the hash table doesn't contain this iface, add it */ + if (!iface) { + if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0) + goto error; + + if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + if (virHashAddEntry(ifaces_store, ifname_s, + ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + iface = ifaces_ret[ifaces_count - 1]; + iface->naddrs = 0; + + if (VIR_STRDUP(iface->name, ifname_s) < 0) + goto error; + + hwaddr = virJSONValueObjectGetString(tmp_iface, "hardware-address"); + if (!hwaddr) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent didn't provide" + " 'hardware-address' field")); + goto error; + } + + if (VIR_STRDUP(iface->hwaddr, hwaddr) < 0) + goto error; + } + + /* Has to be freed for each interface. */ + virStringFreeList(ifname); + + /* as well as IP address which - moreover - + * can be presented multiple times */ + ip_addr_arr = virJSONValueObjectGet(tmp_iface, "ip-addresses"); + if (!ip_addr_arr) + continue; + + if ((ip_addr_arr_size = virJSONValueArraySize(ip_addr_arr)) < 0) + /* Mmm, empty 'ip-address'? */ + goto error; + + /* If current iface already exists, continue with the count */ + addrs_count = iface->naddrs; + + for (j = 0; j < ip_addr_arr_size; j++) { + const char *type, *addr; + virJSONValuePtr ip_addr_obj = virJSONValueArrayGet(ip_addr_arr, j); + virDomainIPAddressPtr ip_addr; + + if (VIR_EXPAND_N(iface->addrs, addrs_count, 1) < 0) + goto error; + + ip_addr = &iface->addrs[addrs_count - 1]; + + /* Shouldn't happen but doesn't hurt to check neither */ + if (!ip_addr_obj) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("qemu agent reply missing IP addr in array")); + goto error; + } + + type = virJSONValueObjectGetString(ip_addr_obj, "ip-address-type"); + if (!type) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("qemu agent didn't provide 'ip-address-type'" + " field for interface '%s'"), name); + goto error; + } else if (STREQ(type, "ipv4")) { + ip_addr->type = VIR_IP_ADDR_TYPE_IPV4; + } else if (STREQ(type, "ipv6")) { + ip_addr->type = VIR_IP_ADDR_TYPE_IPV6; + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown ip address type '%s'"), + type); + goto error; + } + + addr = virJSONValueObjectGetString(ip_addr_obj, "ip-address"); + if (!addr) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("qemu agent didn't provide 'ip-address'" + " field for interface '%s'"), name); + goto error; + } + if (VIR_STRDUP(ip_addr->addr, addr) < 0) + goto error; + + if (virJSONValueObjectGetNumberUint(ip_addr_obj, "prefix", + &ip_addr->prefix) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("malformed 'prefix' field")); + goto error; + } + } + + iface->naddrs = addrs_count; + } + + *ifaces = ifaces_ret; + ifaces_ret = NULL; + ret = ifaces_count; + + cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + virHashFree(ifaces_store); + return ret; + + error: + if (ifaces_ret) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces_ret[i]); + } + VIR_FREE(ifaces_ret); + virStringFreeList(ifname); + + goto cleanup; +} diff --git a/src/qemu/qemu_agent.h b/src/qemu/qemu_agent.h index c98382840a..1cd5749403 100644 --- a/src/qemu/qemu_agent.h +++ b/src/qemu/qemu_agent.h @@ -108,4 +108,8 @@ int qemuAgentSetTime(qemuAgentPtr mon, long long seconds, unsigned int nseconds, bool sync); + +int qemuAgentGetInterfaces(qemuAgentPtr mon, + virDomainInterfacePtr **ifaces); + #endif /* __QEMU_AGENT_H__ */ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index f4b8dab697..329637dddf 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -171,6 +171,9 @@ static int qemuOpenFileAs(uid_t fallback_uid, gid_t fallback_gid, const char *path, int oflags, bool *needUnlink, bool *bypassSecurityDriver); +static int qemuGetDHCPInterfaces(virDomainPtr dom, + virDomainObjPtr vm, + virDomainInterfacePtr **ifaces); virQEMUDriverPtr qemu_driver = NULL; @@ -19571,6 +19574,177 @@ qemuDomainGetFSInfo(virDomainPtr dom, return ret; } +static int +qemuDomainInterfaceAddresses(virDomainPtr dom, + virDomainInterfacePtr **ifaces, + unsigned int source, + unsigned int flags) +{ + virQEMUDriverPtr driver = dom->conn->privateData; + qemuDomainObjPrivatePtr priv = NULL; + virDomainObjPtr vm = NULL; + int ret = -1; + + virCheckFlags(0, -1); + + if (!(vm = qemuDomObjFromDomain(dom))) + goto cleanup; + + priv = vm->privateData; + + if (virDomainInterfaceAddressesEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto cleanup; + } + + switch (source) { + case VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE: + ret = qemuGetDHCPInterfaces(dom, vm, ifaces); + break; + + case VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT: + if (priv->agentError) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("QEMU guest agent is not " + "available due to an error")); + goto cleanup; + } + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_QUERY) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("domain is not running")); + goto endjob; + } + + if (!priv->agent) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("QEMU guest agent is not configured")); + goto endjob; + } + + qemuDomainObjEnterAgent(vm); + ret = qemuAgentGetInterfaces(priv->agent, ifaces); + qemuDomainObjExitAgent(vm); + + endjob: + qemuDomainObjEndJob(driver, vm); + + break; + + default: + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("Unknown IP address data source %d"), + source); + break; + } + + cleanup: + if (vm) + virObjectUnlock(vm); + return ret; +} + +static int +qemuGetDHCPInterfaces(virDomainPtr dom, + virDomainObjPtr vm, + virDomainInterfacePtr **ifaces) +{ + int rv = -1; + int n_leases = 0; + size_t i, j; + size_t ifaces_count = 0; + virNetworkPtr network; + char macaddr[VIR_MAC_STRING_BUFLEN]; + virDomainInterfacePtr iface = NULL; + virNetworkDHCPLeasePtr *leases = NULL; + virDomainInterfacePtr *ifaces_ret = NULL; + + if (!dom->conn->networkDriver || + !dom->conn->networkDriver->networkGetDHCPLeases) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Network driver does not support DHCP lease query")); + return -1; + } + + for (i = 0; i < vm->def->nnets; i++) { + if (vm->def->nets[i]->type != VIR_DOMAIN_NET_TYPE_NETWORK) + continue; + + virMacAddrFormat(&(vm->def->nets[i]->mac), macaddr); + network = virNetworkLookupByName(dom->conn, + vm->def->nets[i]->data.network.name); + + if ((n_leases = virNetworkGetDHCPLeases(network, macaddr, + &leases, 0)) < 0) + goto error; + + if (n_leases) { + if (VIR_EXPAND_N(ifaces_ret, ifaces_count, 1) < 0) + goto error; + + if (VIR_ALLOC(ifaces_ret[ifaces_count - 1]) < 0) + goto error; + + iface = ifaces_ret[ifaces_count - 1]; + /* Assuming each lease corresponds to a separate IP */ + iface->naddrs = n_leases; + + if (VIR_ALLOC_N(iface->addrs, iface->naddrs) < 0) + goto error; + + if (VIR_STRDUP(iface->name, vm->def->nets[i]->ifname) < 0) + goto cleanup; + + if (VIR_STRDUP(iface->hwaddr, macaddr) < 0) + goto cleanup; + } + + for (j = 0; j < n_leases; j++) { + virNetworkDHCPLeasePtr lease = leases[j]; + virDomainIPAddressPtr ip_addr = &iface->addrs[j]; + + if (VIR_STRDUP(ip_addr->addr, lease->ipaddr) < 0) + goto cleanup; + + ip_addr->type = lease->type; + ip_addr->prefix = lease->prefix; + } + + for (j = 0; j < n_leases; j++) + virNetworkDHCPLeaseFree(leases[j]); + + VIR_FREE(leases); + } + + *ifaces = ifaces_ret; + ifaces_ret = NULL; + rv = ifaces_count; + + cleanup: + if (leases) { + for (i = 0; i < n_leases; i++) + virNetworkDHCPLeaseFree(leases[i]); + } + VIR_FREE(leases); + + return rv; + + error: + if (ifaces_ret) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces_ret[i]); + } + VIR_FREE(ifaces_ret); + + goto cleanup; +} static virHypervisorDriver qemuHypervisorDriver = { .name = QEMU_DRIVER_NAME, @@ -19775,6 +19949,7 @@ static virHypervisorDriver qemuHypervisorDriver = { .connectGetAllDomainStats = qemuConnectGetAllDomainStats, /* 1.2.8 */ .nodeAllocPages = qemuNodeAllocPages, /* 1.2.9 */ .domainGetFSInfo = qemuDomainGetFSInfo, /* 1.2.11 */ + .domainInterfaceAddresses = qemuDomainInterfaceAddresses, /* 1.2.14 */ }; diff --git a/tests/qemuagenttest.c b/tests/qemuagenttest.c index 54a45df687..d8d1e41ffd 100644 --- a/tests/qemuagenttest.c +++ b/tests/qemuagenttest.c @@ -723,6 +723,193 @@ testQemuAgentTimeout(const void *data) return ret; } +static const char testQemuAgentGetInterfacesResponse[] = + "{\"return\": " + " [" + " {\"name\":\"eth2\"," + " \"hardware-address\":\"52:54:00:36:2a:e5\"" + " }," + " {\"name\":\"eth1:0\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.10.91\"," + " \"prefix\":24" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::fc54:ff:fefe:4c4f\"," + " \"prefix\":64" + " }" + " ]," + " \"hardware-address\":\"52:54:00:d3:39:ee\"" + " }," + " {\"name\":\"eth0\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::5054:ff:fe89:ad35\"," + " \"prefix\":64" + " }," + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.102.142\"," + " \"prefix\":24" + " }," + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.234.152\"," + " \"prefix\":16" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::5054:ff:fec3:68bb\"," + " \"prefix\":64" + " }" + " ]," + " \"hardware-address\":\"52:54:00:89:ad:35\"" + " }," + " {\"name\":\"eth1\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"192.168.103.83\"," + " \"prefix\":32" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"fe80::5054:ff:fed3:39ee\"," + " \"prefix\":64" + " }" + " ]," + " \"hardware-address\":\"52:54:00:d3:39:ee\"" + " }," + " {\"name\":\"lo\"," + " \"ip-addresses\":" + " [" + " {\"ip-address-type\":\"ipv4\"," + " \"ip-address\":\"127.0.0.1\"," + " \"prefix\":8" + " }," + " {\"ip-address-type\":\"ipv6\"," + " \"ip-address\":\"::1\"," + " \"prefix\":128" + " }" + " ]," + " \"hardware-address\":\"00:00:00:00:00:00\"" + " }" + " ]" + "}"; + +static int +testQemuAgentGetInterfaces(const void *data) +{ + virDomainXMLOptionPtr xmlopt = (virDomainXMLOptionPtr)data; + qemuMonitorTestPtr test = qemuMonitorTestNewAgent(xmlopt); + size_t i; + int ret = -1; + int ifaces_count = 0; + virDomainInterfacePtr *ifaces = NULL; + + if (!test) + return -1; + + if (qemuMonitorTestAddAgentSyncResponse(test) < 0) + goto cleanup; + + if (qemuMonitorTestAddItem(test, "guest-network-get-interfaces", + testQemuAgentGetInterfacesResponse) < 0) + goto cleanup; + + if ((ifaces_count = qemuAgentGetInterfaces(qemuMonitorTestGetAgent(test), + &ifaces)) < 0) + goto cleanup; + + if (ifaces_count != 4) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "expected 4 interfaces, got %d", ret); + goto cleanup; + } + + if (STRNEQ(ifaces[0]->name, "eth2") || + STRNEQ(ifaces[1]->name, "eth1") || + STRNEQ(ifaces[2]->name, "eth0") || + STRNEQ(ifaces[3]->name, "lo")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for interface names"); + goto cleanup; + } + + if (STRNEQ(ifaces[0]->hwaddr, "52:54:00:36:2a:e5") || + STRNEQ(ifaces[1]->hwaddr, "52:54:00:d3:39:ee") || + STRNEQ(ifaces[2]->hwaddr, "52:54:00:89:ad:35") || + STRNEQ(ifaces[3]->hwaddr, "00:00:00:00:00:00")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for MAC addresses"); + goto cleanup; + } + + if (ifaces[0]->naddrs != 0 || + ifaces[1]->naddrs != 4 || + ifaces[2]->naddrs != 4 || + ifaces[3]->naddrs != 2) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for number of IP addresses"); + goto cleanup; + } + + if (ifaces[1]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[1]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[1]->addrs[2].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[1]->addrs[3].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[2]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[2]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[2]->addrs[2].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[2]->addrs[3].type != VIR_IP_ADDR_TYPE_IPV6 || + ifaces[3]->addrs[0].type != VIR_IP_ADDR_TYPE_IPV4 || + ifaces[3]->addrs[1].type != VIR_IP_ADDR_TYPE_IPV6) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for IP address types"); + goto cleanup; + } + + if (ifaces[1]->addrs[0].prefix != 24 || + ifaces[1]->addrs[1].prefix != 64 || + ifaces[1]->addrs[2].prefix != 32 || + ifaces[1]->addrs[3].prefix != 64 || + ifaces[2]->addrs[0].prefix != 64 || + ifaces[2]->addrs[1].prefix != 24 || + ifaces[2]->addrs[2].prefix != 16 || + ifaces[2]->addrs[3].prefix != 64 || + ifaces[3]->addrs[0].prefix != 8 || + ifaces[3]->addrs[1].prefix != 128) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for IP address prefix"); + goto cleanup; + } + + if (STRNEQ(ifaces[1]->addrs[0].addr, "192.168.10.91") || + STRNEQ(ifaces[1]->addrs[1].addr, "fe80::fc54:ff:fefe:4c4f") || + STRNEQ(ifaces[1]->addrs[2].addr, "192.168.103.83") || + STRNEQ(ifaces[1]->addrs[3].addr, "fe80::5054:ff:fed3:39ee") || + STRNEQ(ifaces[2]->addrs[0].addr, "fe80::5054:ff:fe89:ad35") || + STRNEQ(ifaces[2]->addrs[1].addr, "192.168.102.142") || + STRNEQ(ifaces[2]->addrs[2].addr, "192.168.234.152") || + STRNEQ(ifaces[2]->addrs[3].addr, "fe80::5054:ff:fec3:68bb") || + STRNEQ(ifaces[3]->addrs[0].addr, "127.0.0.1") || + STRNEQ(ifaces[3]->addrs[1].addr, "::1")) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "unexpected return values for IP address values"); + goto cleanup; + } + + ret = 0; + + cleanup: + qemuMonitorTestFree(test); + if (ifaces) { + for (i = 0; i < ifaces_count; i++) + virDomainInterfaceFree(ifaces[i]); + } + VIR_FREE(ifaces); + + return ret; +} static int mymain(void) @@ -753,6 +940,7 @@ mymain(void) DO_TEST(Shutdown); DO_TEST(CPU); DO_TEST(ArbitraryCommand); + DO_TEST(GetInterfaces); DO_TEST(Timeout); /* Timeout should always be called last */