Patchwork Migration via unix sockets.

login
register
mail settings
Submitter Chris Lalancette
Date Aug. 5, 2009, 3:24 p.m.
Message ID <1249485869-23590-1-git-send-email-clalance@redhat.com>
Download mbox | patch
Permalink /patch/30799/
State Superseded
Headers show

Comments

Chris Lalancette - Aug. 5, 2009, 3:24 p.m.
Implement migration via unix sockets.  While you can fake this using
exec and netcat, this involves forking another process and is
generally not very nice.  By doing this directly in qemu, we can avoid
the copy through the external nc command.  This is useful for
implementations (such as libvirt) that want to do "secure" migration;
we pipe the data on the sending side into the unix socket, libvirt
picks it up, encrypts it, and transports it, and then on the remote
side libvirt decrypts it, dumps it to another unix socket, and
feeds it into qemu.

The implementation is straightforward and looks very similar to
migration-exec.c and migration-tcp.c

Signed-off-by: Chris Lalancette <clalance@redhat.com>
---
 Makefile         |    2 +-
 migration-unix.c |  216 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 migration.c      |    4 +
 migration.h      |    6 ++
 4 files changed, 227 insertions(+), 1 deletions(-)
 create mode 100644 migration-unix.c
Chris Lalancette - Aug. 10, 2009, 10:23 a.m.
Chris Lalancette wrote:
> Implement migration via unix sockets.  While you can fake this using
> exec and netcat, this involves forking another process and is
> generally not very nice.  By doing this directly in qemu, we can avoid
> the copy through the external nc command.  This is useful for
> implementations (such as libvirt) that want to do "secure" migration;
> we pipe the data on the sending side into the unix socket, libvirt
> picks it up, encrypts it, and transports it, and then on the remote
> side libvirt decrypts it, dumps it to another unix socket, and
> feeds it into qemu.
> 
> The implementation is straightforward and looks very similar to
> migration-exec.c and migration-tcp.c

ping?

> 
> Signed-off-by: Chris Lalancette <clalance@redhat.com>
> ---
>  Makefile         |    2 +-
>  migration-unix.c |  216 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  migration.c      |    4 +
>  migration.h      |    6 ++
>  4 files changed, 227 insertions(+), 1 deletions(-)
>  create mode 100644 migration-unix.c
> 
> diff --git a/Makefile b/Makefile
> index d3f999e..c5763b7 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -110,7 +110,7 @@ obj-$(CONFIG_BRLAPI) += baum.o
>  LIBS+=$(BRLAPI_LIBS)
>  
>  obj-$(CONFIG_WIN32) += tap-win32.o
> -obj-$(CONFIG_POSIX) += migration-exec.o
> +obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o
>  
>  ifdef CONFIG_COREAUDIO
>  AUDIO_PT = y
> diff --git a/migration-unix.c b/migration-unix.c
> new file mode 100644
> index 0000000..a26587a
> --- /dev/null
> +++ b/migration-unix.c
> @@ -0,0 +1,216 @@
> +/*
> + * QEMU live migration via Unix Domain Sockets
> + *
> + * Copyright Red Hat, Inc. 2009
> + *
> + * Authors:
> + *  Chris Lalancette <clalance@redhat.com>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2.  See
> + * the COPYING file in the top-level directory.
> + *
> + */
> +
> +#include "qemu-common.h"
> +#include "qemu_socket.h"
> +#include "migration.h"
> +#include "qemu-char.h"
> +#include "sysemu.h"
> +#include "buffered_file.h"
> +#include "block.h"
> +
> +//#define DEBUG_MIGRATION_UNIX
> +
> +#ifdef DEBUG_MIGRATION_UNIX
> +#define dprintf(fmt, ...) \
> +    do { printf("migration-unix: " fmt, ## __VA_ARGS__); } while (0)
> +#else
> +#define dprintf(fmt, ...) \
> +    do { } while (0)
> +#endif
> +
> +static int unix_errno(FdMigrationState *s)
> +{
> +    return errno;
> +}
> +
> +static int unix_write(FdMigrationState *s, const void * buf, size_t size)
> +{
> +    return write(s->fd, buf, size);
> +}
> +
> +static int unix_close(FdMigrationState *s)
> +{
> +    dprintf("unix_close\n");
> +    if (s->fd != -1) {
> +        close(s->fd);
> +        s->fd = -1;
> +    }
> +    return 0;
> +}
> +
> +static void unix_wait_for_connect(void *opaque)
> +{
> +    FdMigrationState *s = opaque;
> +    int val, ret;
> +    socklen_t valsize = sizeof(val);
> +
> +    dprintf("connect completed\n");
> +    do {
> +        ret = getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (void *) &val, &valsize);
> +    } while (ret == -1 && (s->get_error(s)) == EINTR);
> +
> +    if (ret < 0) {
> +        migrate_fd_error(s);
> +        return;
> +    }
> +
> +    qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL);
> +
> +    if (val == 0)
> +        migrate_fd_connect(s);
> +    else {
> +        dprintf("error connecting %d\n", val);
> +        migrate_fd_error(s);
> +    }
> +}
> +
> +MigrationState *unix_start_outgoing_migration(const char *path,
> +					      int64_t bandwidth_limit,
> +					      int detach)
> +{
> +    FdMigrationState *s;
> +    struct sockaddr_un addr;
> +    int ret;
> +
> +    addr.sun_family = AF_UNIX;
> +    snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path);
> +
> +    s = qemu_mallocz(sizeof(*s));
> +
> +    s->get_error = unix_errno;
> +    s->write = unix_write;
> +    s->close = unix_close;
> +    s->mig_state.cancel = migrate_fd_cancel;
> +    s->mig_state.get_status = migrate_fd_get_status;
> +    s->mig_state.release = migrate_fd_release;
> +
> +    s->state = MIG_STATE_ACTIVE;
> +    s->mon_resume = NULL;
> +    s->bandwidth_limit = bandwidth_limit;
> +    s->fd = socket(PF_UNIX, SOCK_STREAM, 0);
> +    if (s->fd < 0) {
> +        dprintf("Unable to open socket");
> +        goto err_after_alloc;
> +    }
> +
> +    socket_set_nonblock(s->fd);
> +
> +    if (!detach)
> +        migrate_fd_monitor_suspend(s);
> +
> +    do {
> +        ret = connect(s->fd, (struct sockaddr *)&addr, sizeof(addr));
> +        if (ret == -1)
> +	    ret = -(s->get_error(s));
> +
> +        if (ret == -EINPROGRESS || ret == -EWOULDBLOCK)
> +	    qemu_set_fd_handler2(s->fd, NULL, NULL, unix_wait_for_connect, s);
> +    } while (ret == -EINTR);
> +
> +    if (ret < 0 && ret != -EINPROGRESS && ret != -EWOULDBLOCK) {
> +        dprintf("connect failed\n");
> +        goto err_after_open;
> +    } else if (ret >= 0)
> +        migrate_fd_connect(s);
> +
> +    return &s->mig_state;
> +
> +err_after_open:
> +    close(s->fd);
> +
> +err_after_alloc:
> +    qemu_free(s);
> +    return NULL;
> +}
> +
> +static void unix_accept_incoming_migration(void *opaque)
> +{
> +    struct sockaddr_un addr;
> +    socklen_t addrlen = sizeof(addr);
> +    int s = (unsigned long)opaque;
> +    QEMUFile *f;
> +    int c, ret;
> +
> +    do {
> +        c = accept(s, (struct sockaddr *)&addr, &addrlen);
> +    } while (c == -1 && socket_error() == EINTR);
> +
> +    dprintf("accepted migration\n");
> +
> +    if (c == -1) {
> +        fprintf(stderr, "could not accept migration connection\n");
> +        return;
> +    }
> +
> +    f = qemu_fopen_socket(c);
> +    if (f == NULL) {
> +        fprintf(stderr, "could not qemu_fopen socket\n");
> +        goto out;
> +    }
> +
> +    ret = qemu_loadvm_state(f);
> +    if (ret < 0) {
> +        fprintf(stderr, "load of migration failed\n");
> +        goto out_fopen;
> +    }
> +    qemu_announce_self();
> +    dprintf("successfully loaded vm state\n");
> +
> +    /* we've successfully migrated, close the server socket */
> +    qemu_set_fd_handler2(s, NULL, NULL, NULL, NULL);
> +    close(s);
> +
> +out_fopen:
> +    qemu_fclose(f);
> +out:
> +    close(c);
> +}
> +
> +int unix_start_incoming_migration(const char *path)
> +{
> +    struct sockaddr_un un;
> +    int sock;
> +
> +    dprintf("Attempting to start an incoming migration\n");
> +
> +    sock = socket(PF_UNIX, SOCK_STREAM, 0);
> +    if (sock < 0) {
> +        fprintf(stderr, "Could not open unix socket: %s\n", strerror(errno));
> +        return -EINVAL;
> +    }
> +
> +    memset(&un, 0, sizeof(un));
> +    un.sun_family = AF_UNIX;
> +    snprintf(un.sun_path, sizeof(un.sun_path), "%s", path);
> +
> +    unlink(un.sun_path);
> +    if (bind(sock, (struct sockaddr*) &un, sizeof(un)) < 0) {
> +        fprintf(stderr, "bind(unix:%s): %s\n", un.sun_path, strerror(errno));
> +        goto err;
> +    }
> +    if (listen(sock, 1) < 0) {
> +        fprintf(stderr, "listen(unix:%s): %s\n", un.sun_path, strerror(errno));
> +        goto err;
> +    }
> +
> +    qemu_set_fd_handler2(sock, NULL, unix_accept_incoming_migration, NULL,
> +			 (void *)(unsigned long)sock);
> +
> +    return 0;
> +
> +err:
> +    close(sock);
> +
> +    return -EINVAL;
> +}
> diff --git a/migration.c b/migration.c
> index ee64d41..34e2bc1 100644
> --- a/migration.c
> +++ b/migration.c
> @@ -43,6 +43,8 @@ void qemu_start_incoming_migration(const char *uri)
>  #if !defined(WIN32)
>      else if (strstart(uri, "exec:", &p))
>          exec_start_incoming_migration(p);
> +    else if (strstart(uri, "unix:", &p))
> +        unix_start_incoming_migration(p);
>  #endif
>      else
>          fprintf(stderr, "unknown migration protocol: %s\n", uri);
> @@ -58,6 +60,8 @@ void do_migrate(Monitor *mon, int detach, const char *uri)
>  #if !defined(WIN32)
>      else if (strstart(uri, "exec:", &p))
>          s = exec_start_outgoing_migration(p, max_throttle, detach);
> +    else if (strstart(uri, "unix:", &p))
> +        s = unix_start_outgoing_migration(p, max_throttle, detach);
>  #endif
>      else
>          monitor_printf(mon, "unknown migration protocol: %s\n", uri);
> diff --git a/migration.h b/migration.h
> index 37c7f8e..0ed1fcb 100644
> --- a/migration.h
> +++ b/migration.h
> @@ -73,6 +73,12 @@ MigrationState *tcp_start_outgoing_migration(const char *host_port,
>  					     int64_t bandwidth_limit,
>  					     int detach);
>  
> +int unix_start_incoming_migration(const char *path);
> +
> +MigrationState *unix_start_outgoing_migration(const char *path,
> +					      int64_t bandwidth_limit,
> +					      int detach);
> +
>  void migrate_fd_monitor_suspend(FdMigrationState *s);
>  
>  void migrate_fd_error(FdMigrationState *s);
Avi Kivity - Aug. 10, 2009, 11:34 a.m.
On 08/10/2009 01:23 PM, Chris Lalancette wrote:
> Chris Lalancette wrote:
>    
>> Implement migration via unix sockets.  While you can fake this using
>> exec and netcat, this involves forking another process and is
>> generally not very nice.  By doing this directly in qemu, we can avoid
>> the copy through the external nc command.  This is useful for
>> implementations (such as libvirt) that want to do "secure" migration;
>> we pipe the data on the sending side into the unix socket, libvirt
>> picks it up, encrypts it, and transports it, and then on the remote
>> side libvirt decrypts it, dumps it to another unix socket, and
>> feeds it into qemu.
>>
>> The implementation is straightforward and looks very similar to
>> migration-exec.c and migration-tcp.c
>>      
>
> ping?
>    

It would be nice to support migration via arbitrary fd using the recent 
SCM_RIGHTS support.

Patch

diff --git a/Makefile b/Makefile
index d3f999e..c5763b7 100644
--- a/Makefile
+++ b/Makefile
@@ -110,7 +110,7 @@  obj-$(CONFIG_BRLAPI) += baum.o
 LIBS+=$(BRLAPI_LIBS)
 
 obj-$(CONFIG_WIN32) += tap-win32.o
-obj-$(CONFIG_POSIX) += migration-exec.o
+obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o
 
 ifdef CONFIG_COREAUDIO
 AUDIO_PT = y
diff --git a/migration-unix.c b/migration-unix.c
new file mode 100644
index 0000000..a26587a
--- /dev/null
+++ b/migration-unix.c
@@ -0,0 +1,216 @@ 
+/*
+ * QEMU live migration via Unix Domain Sockets
+ *
+ * Copyright Red Hat, Inc. 2009
+ *
+ * Authors:
+ *  Chris Lalancette <clalance@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu-common.h"
+#include "qemu_socket.h"
+#include "migration.h"
+#include "qemu-char.h"
+#include "sysemu.h"
+#include "buffered_file.h"
+#include "block.h"
+
+//#define DEBUG_MIGRATION_UNIX
+
+#ifdef DEBUG_MIGRATION_UNIX
+#define dprintf(fmt, ...) \
+    do { printf("migration-unix: " fmt, ## __VA_ARGS__); } while (0)
+#else
+#define dprintf(fmt, ...) \
+    do { } while (0)
+#endif
+
+static int unix_errno(FdMigrationState *s)
+{
+    return errno;
+}
+
+static int unix_write(FdMigrationState *s, const void * buf, size_t size)
+{
+    return write(s->fd, buf, size);
+}
+
+static int unix_close(FdMigrationState *s)
+{
+    dprintf("unix_close\n");
+    if (s->fd != -1) {
+        close(s->fd);
+        s->fd = -1;
+    }
+    return 0;
+}
+
+static void unix_wait_for_connect(void *opaque)
+{
+    FdMigrationState *s = opaque;
+    int val, ret;
+    socklen_t valsize = sizeof(val);
+
+    dprintf("connect completed\n");
+    do {
+        ret = getsockopt(s->fd, SOL_SOCKET, SO_ERROR, (void *) &val, &valsize);
+    } while (ret == -1 && (s->get_error(s)) == EINTR);
+
+    if (ret < 0) {
+        migrate_fd_error(s);
+        return;
+    }
+
+    qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL);
+
+    if (val == 0)
+        migrate_fd_connect(s);
+    else {
+        dprintf("error connecting %d\n", val);
+        migrate_fd_error(s);
+    }
+}
+
+MigrationState *unix_start_outgoing_migration(const char *path,
+					      int64_t bandwidth_limit,
+					      int detach)
+{
+    FdMigrationState *s;
+    struct sockaddr_un addr;
+    int ret;
+
+    addr.sun_family = AF_UNIX;
+    snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path);
+
+    s = qemu_mallocz(sizeof(*s));
+
+    s->get_error = unix_errno;
+    s->write = unix_write;
+    s->close = unix_close;
+    s->mig_state.cancel = migrate_fd_cancel;
+    s->mig_state.get_status = migrate_fd_get_status;
+    s->mig_state.release = migrate_fd_release;
+
+    s->state = MIG_STATE_ACTIVE;
+    s->mon_resume = NULL;
+    s->bandwidth_limit = bandwidth_limit;
+    s->fd = socket(PF_UNIX, SOCK_STREAM, 0);
+    if (s->fd < 0) {
+        dprintf("Unable to open socket");
+        goto err_after_alloc;
+    }
+
+    socket_set_nonblock(s->fd);
+
+    if (!detach)
+        migrate_fd_monitor_suspend(s);
+
+    do {
+        ret = connect(s->fd, (struct sockaddr *)&addr, sizeof(addr));
+        if (ret == -1)
+	    ret = -(s->get_error(s));
+
+        if (ret == -EINPROGRESS || ret == -EWOULDBLOCK)
+	    qemu_set_fd_handler2(s->fd, NULL, NULL, unix_wait_for_connect, s);
+    } while (ret == -EINTR);
+
+    if (ret < 0 && ret != -EINPROGRESS && ret != -EWOULDBLOCK) {
+        dprintf("connect failed\n");
+        goto err_after_open;
+    } else if (ret >= 0)
+        migrate_fd_connect(s);
+
+    return &s->mig_state;
+
+err_after_open:
+    close(s->fd);
+
+err_after_alloc:
+    qemu_free(s);
+    return NULL;
+}
+
+static void unix_accept_incoming_migration(void *opaque)
+{
+    struct sockaddr_un addr;
+    socklen_t addrlen = sizeof(addr);
+    int s = (unsigned long)opaque;
+    QEMUFile *f;
+    int c, ret;
+
+    do {
+        c = accept(s, (struct sockaddr *)&addr, &addrlen);
+    } while (c == -1 && socket_error() == EINTR);
+
+    dprintf("accepted migration\n");
+
+    if (c == -1) {
+        fprintf(stderr, "could not accept migration connection\n");
+        return;
+    }
+
+    f = qemu_fopen_socket(c);
+    if (f == NULL) {
+        fprintf(stderr, "could not qemu_fopen socket\n");
+        goto out;
+    }
+
+    ret = qemu_loadvm_state(f);
+    if (ret < 0) {
+        fprintf(stderr, "load of migration failed\n");
+        goto out_fopen;
+    }
+    qemu_announce_self();
+    dprintf("successfully loaded vm state\n");
+
+    /* we've successfully migrated, close the server socket */
+    qemu_set_fd_handler2(s, NULL, NULL, NULL, NULL);
+    close(s);
+
+out_fopen:
+    qemu_fclose(f);
+out:
+    close(c);
+}
+
+int unix_start_incoming_migration(const char *path)
+{
+    struct sockaddr_un un;
+    int sock;
+
+    dprintf("Attempting to start an incoming migration\n");
+
+    sock = socket(PF_UNIX, SOCK_STREAM, 0);
+    if (sock < 0) {
+        fprintf(stderr, "Could not open unix socket: %s\n", strerror(errno));
+        return -EINVAL;
+    }
+
+    memset(&un, 0, sizeof(un));
+    un.sun_family = AF_UNIX;
+    snprintf(un.sun_path, sizeof(un.sun_path), "%s", path);
+
+    unlink(un.sun_path);
+    if (bind(sock, (struct sockaddr*) &un, sizeof(un)) < 0) {
+        fprintf(stderr, "bind(unix:%s): %s\n", un.sun_path, strerror(errno));
+        goto err;
+    }
+    if (listen(sock, 1) < 0) {
+        fprintf(stderr, "listen(unix:%s): %s\n", un.sun_path, strerror(errno));
+        goto err;
+    }
+
+    qemu_set_fd_handler2(sock, NULL, unix_accept_incoming_migration, NULL,
+			 (void *)(unsigned long)sock);
+
+    return 0;
+
+err:
+    close(sock);
+
+    return -EINVAL;
+}
diff --git a/migration.c b/migration.c
index ee64d41..34e2bc1 100644
--- a/migration.c
+++ b/migration.c
@@ -43,6 +43,8 @@  void qemu_start_incoming_migration(const char *uri)
 #if !defined(WIN32)
     else if (strstart(uri, "exec:", &p))
         exec_start_incoming_migration(p);
+    else if (strstart(uri, "unix:", &p))
+        unix_start_incoming_migration(p);
 #endif
     else
         fprintf(stderr, "unknown migration protocol: %s\n", uri);
@@ -58,6 +60,8 @@  void do_migrate(Monitor *mon, int detach, const char *uri)
 #if !defined(WIN32)
     else if (strstart(uri, "exec:", &p))
         s = exec_start_outgoing_migration(p, max_throttle, detach);
+    else if (strstart(uri, "unix:", &p))
+        s = unix_start_outgoing_migration(p, max_throttle, detach);
 #endif
     else
         monitor_printf(mon, "unknown migration protocol: %s\n", uri);
diff --git a/migration.h b/migration.h
index 37c7f8e..0ed1fcb 100644
--- a/migration.h
+++ b/migration.h
@@ -73,6 +73,12 @@  MigrationState *tcp_start_outgoing_migration(const char *host_port,
 					     int64_t bandwidth_limit,
 					     int detach);
 
+int unix_start_incoming_migration(const char *path);
+
+MigrationState *unix_start_outgoing_migration(const char *path,
+					      int64_t bandwidth_limit,
+					      int detach);
+
 void migrate_fd_monitor_suspend(FdMigrationState *s);
 
 void migrate_fd_error(FdMigrationState *s);