Use virURIParse in qemuMigrationPrepareDirect to allow parsing
IPv6 addresses, which would cause an 'incorrect :port' error message
before.
To be able to migrate over IPv6, QEMU needs to listen on [::] instead
of 0.0.0.0. This patch adds a call to getaddrinfo and sets the listen
address based on the result.
It also uses the same listen address for the NBD server.
This will break migration if a hostname does not resolve to the same
address family on both sides.
Bug:
https://bugzilla.redhat.com/show_bug.cgi?id=846013
---
Diff to V1:
* initialize uri_str
* reuse STRSKIP("tcp:") result instead of doing strlen on it
* print a warning instead of failing when the hostname can't be resolved
Diff to V2:
* freeaddrinfo
* separate the listen address to allow reuse in qemuMigrationStartNBDServer
src/qemu/qemu_migration.c | 77 ++++++++++++++++++++++++++++++++++++-----------
1 file changed, 59 insertions(+), 18 deletions(-)
diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
index cae58fa..ff9b959 100644
--- a/src/qemu/qemu_migration.c
+++ b/src/qemu/qemu_migration.c
@@ -22,7 +22,10 @@
#include <config.h>
+#include <netdb.h>
+#include <sys/socket.h>
#include <sys/time.h>
+#include <sys/types.h>
#ifdef WITH_GNUTLS
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
@@ -1103,12 +1106,12 @@ error:
*/
static int
qemuMigrationStartNBDServer(virQEMUDriverPtr driver,
- virDomainObjPtr vm)
+ virDomainObjPtr vm,
+ const char *listenAddr)
{
int ret = -1;
qemuDomainObjPrivatePtr priv = vm->privateData;
unsigned short port = 0;
- const char *listenAddr = "0.0.0.0";
char *diskAlias = NULL;
size_t i;
@@ -1981,6 +1984,7 @@ qemuMigrationPrepareAny(virQEMUDriverPtr driver,
const char *dom_xml,
const char *migrateFrom,
virStreamPtr st,
+ const char *listenAddr,
unsigned long flags)
{
virDomainDefPtr def = NULL;
@@ -2168,7 +2172,7 @@ done:
if (flags & VIR_MIGRATE_TUNNELLED)
VIR_DEBUG("NBD in tunnelled migration is currently not
supported");
else {
- if (qemuMigrationStartNBDServer(driver, vm) < 0) {
+ if (qemuMigrationStartNBDServer(driver, vm, listenAddr) < 0) {
/* error already reported */
goto endjob;
}
@@ -2271,7 +2275,7 @@ qemuMigrationPrepareTunnel(virQEMUDriverPtr driver,
*/
ret = qemuMigrationPrepareAny(driver, dconn, cookiein, cookieinlen,
cookieout, cookieoutlen, dname, dom_xml,
- "stdio", st, flags);
+ "stdio", st, NULL, flags);
return ret;
}
@@ -2292,9 +2296,14 @@ qemuMigrationPrepareDirect(virQEMUDriverPtr driver,
static int port = 0;
int this_port;
char *hostname = NULL;
+ char listenAddr[8];
char migrateFrom [64];
const char *p;
+ char *uri_str = NULL;
int ret = -1;
+ bool ipv6 = false;
+ struct addrinfo *info;
+ virURIPtr uri;
VIR_DEBUG("driver=%p, dconn=%p, cookiein=%s, cookieinlen=%d, "
"cookieout=%p, cookieoutlen=%p, uri_in=%s, uri_out=%p, "
@@ -2343,16 +2352,39 @@ qemuMigrationPrepareDirect(virQEMUDriverPtr driver,
* URI when passing it to the qemu monitor, so bad
* characters in hostname part don't matter.
*/
- if (!STRPREFIX(uri_in, "tcp:")) {
+ if (!(p = STRSKIP(uri_in, "tcp:"))) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("only tcp URIs are supported for KVM/QEMU"
" migrations"));
goto cleanup;
}
- /* Get the port number. */
- p = strrchr(uri_in, ':');
- if (p == strchr(uri_in, ':')) {
+ /* Convert uri_in to well-formed URI with // after tcp: */
+ if (!(STRPREFIX(uri_in, "tcp://"))) {
+ if (virAsprintf(&uri_str, "tcp://%s", p) < 0) {
+ virReportOOMError();
+ goto cleanup;
+ }
+ }
+
+ uri = virURIParse(uri_str ? uri_str : uri_in);
+ VIR_FREE(uri_str);
+
+ if (uri == NULL) {
+ virReportError(VIR_ERR_INVALID_ARG, _("unable to parse URI: %s"),
+ uri_in);
+ goto cleanup;
+ }
+
+ if (uri->server == NULL) {
+ virReportError(VIR_ERR_INVALID_ARG, _("missing host in migration"
+ " URI: %s"), uri_in);
+ goto cleanup;
+ } else {
+ hostname = uri->server;
+ }
+
+ if (uri->port == 0) {
/* Generate a port */
this_port = QEMUD_MIGRATION_FIRST_PORT + port++;
if (port == QEMUD_MIGRATION_NUM_PORTS)
@@ -2365,25 +2397,34 @@ qemuMigrationPrepareDirect(virQEMUDriverPtr driver,
}
} else {
- p++; /* definitely has a ':' in it, see above */
- this_port = virParseNumber(&p);
- if (this_port == -1 || p-uri_in != strlen(uri_in)) {
- virReportError(VIR_ERR_INVALID_ARG,
- "%s", _("URI ended with incorrect
':port'"));
- goto cleanup;
- }
+ this_port = uri->port;
}
}
+ if (getaddrinfo(hostname, NULL, NULL, &info)) {
+ VIR_WARN("unable to get address info for %s, defaulting to IPv4",
+ hostname);
+ } else {
+ ipv6 = info->ai_family == AF_INET6;
+ freeaddrinfo(info);
+ }
+
if (*uri_out)
VIR_DEBUG("Generated uri_out=%s", *uri_out);
- /* QEMU will be started with -incoming tcp:0.0.0.0:port */
- snprintf(migrateFrom, sizeof(migrateFrom), "tcp:0.0.0.0:%d", this_port);
+ /* QEMU will be started with -incoming tcp:0.0.0.0:port
+ * or -incoming tcp:[::]:port for IPv6 */
+ if (ipv6)
+ snprintf(listenAddr, sizeof(listenAddr), "[::]");
+ else
+ snprintf(listenAddr, sizeof(listenAddr), "0.0.0.0");
+
+ snprintf(migrateFrom, sizeof(migrateFrom),
+ "tcp:%s:%d", listenAddr, this_port);
ret = qemuMigrationPrepareAny(driver, dconn, cookiein, cookieinlen,
cookieout, cookieoutlen, dname, dom_xml,
- migrateFrom, NULL, flags);
+ migrateFrom, NULL, listenAddr, flags);
cleanup:
VIR_FREE(hostname);
if (ret != 0)
--
1.7.12.4