diff --git a/migration/Makefile.objs b/migration/Makefile.objs
index a4f3bafd86..f4bb6c5803 100644
--- a/migration/Makefile.objs
+++ b/migration/Makefile.objs
@@ -1,4 +1,4 @@
-common-obj-y += migration.o socket.o fd.o exec.o
+common-obj-y += migration.o socket.o fd.o inline-fd.o exec.o
 common-obj-y += tls.o channel.o savevm.o
 common-obj-y += colo.o colo-failover.o
 common-obj-y += vmstate.o vmstate-types.o page_cache.o
diff --git a/migration/inline-fd.c b/migration/inline-fd.c
new file mode 100644
index 0000000000..90a0dc079f
--- /dev/null
+++ b/migration/inline-fd.c
@@ -0,0 +1,89 @@
+/*
+ * QEMU live migration via generic fd passed with command
+ *
+ * Copyright Yandex, Inc. 2019
+ *
+ * Authors:
+ *  Yury Kotov <yury-kotov@yandex-team.ru>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "channel.h"
+#include "inline-fd.h"
+#include "monitor/monitor.h"
+#include "io/channel-util.h"
+#include "trace.h"
+
+
+void inline_fd_start_outgoing_migration(MigrationState *s, Error **errp)
+{
+    QIOChannel *ioc;
+    int fd;
+
+    if (!cur_mon) {
+        error_setg(errp, "Monitor is disabled");
+        return;
+    }
+
+    fd = monitor_recv_fd(cur_mon, errp);
+    if (fd == -1) {
+        return;
+    }
+
+    trace_migration_inline_fd_outgoing(fd);
+    ioc = qio_channel_new_fd(fd, errp);
+    if (!ioc) {
+        close(fd);
+        return;
+    }
+
+    qio_channel_set_name(QIO_CHANNEL(ioc), "migration-infd-outgoing");
+    migration_channel_connect(s, ioc, NULL, NULL);
+    object_unref(OBJECT(ioc));
+}
+
+static gboolean inline_fd_accept_incoming_migration(QIOChannel *ioc,
+                                                    GIOCondition condition,
+                                                    gpointer opaque)
+{
+    migration_channel_process_incoming(ioc);
+    object_unref(OBJECT(ioc));
+    return G_SOURCE_REMOVE;
+}
+
+void inline_fd_start_incoming_migration(Error **errp)
+{
+    QIOChannel *ioc;
+    int fd;
+
+    if (!cur_mon) {
+        error_setg(errp, "Monitor is disabled");
+        return;
+    }
+
+    fd = monitor_recv_fd(cur_mon, errp);
+    if (fd == -1) {
+        return;
+    }
+
+    trace_migration_inline_fd_incoming(fd);
+    ioc = qio_channel_new_fd(fd, errp);
+    if (!ioc) {
+        close(fd);
+        return;
+    }
+
+    qio_channel_set_name(QIO_CHANNEL(ioc), "migration-infd-incoming");
+    qio_channel_add_watch(ioc,
+                          G_IO_IN,
+                          inline_fd_accept_incoming_migration,
+                          NULL,
+                          NULL);
+}
diff --git a/migration/inline-fd.h b/migration/inline-fd.h
new file mode 100644
index 0000000000..5b23ce314d
--- /dev/null
+++ b/migration/inline-fd.h
@@ -0,0 +1,22 @@
+/*
+ * QEMU live migration via generic fd passed with command
+ *
+ * Copyright Yandex, Inc. 2019
+ *
+ * Authors:
+ *  Yury Kotov <yury-kotov@yandex-team.ru>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#ifndef QEMU_MIGRATION_INLINE_FD_H
+#define QEMU_MIGRATION_INLINE_FD_H
+
+void inline_fd_start_incoming_migration(Error **errp);
+void inline_fd_start_outgoing_migration(MigrationState *s, Error **errp);
+
+#endif
diff --git a/migration/migration.c b/migration/migration.c
index 609e0df5d0..7b9fafe218 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -19,6 +19,7 @@
 #include "migration/blocker.h"
 #include "exec.h"
 #include "fd.h"
+#include "inline-fd.h"
 #include "socket.h"
 #include "rdma.h"
 #include "ram.h"
@@ -364,6 +365,13 @@ void qemu_start_incoming_migration(const char *uri, Error **errp)
         unix_start_incoming_migration(p, errp);
     } else if (strstart(uri, "fd:", &p)) {
         fd_start_incoming_migration(p, errp);
+    } else if (strstart(uri, "inline-fd:", &p)) {
+        if (!*p) {
+            inline_fd_start_incoming_migration(errp);
+        } else {
+            error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "uri",
+                       "an empty path for 'inline-fd:' protocol");
+        }
     } else {
         error_setg(errp, "unknown migration protocol: %s", uri);
     }
@@ -1924,6 +1932,13 @@ void qmp_migrate(const char *uri, bool has_blk, bool blk,
         unix_start_outgoing_migration(s, p, &local_err);
     } else if (strstart(uri, "fd:", &p)) {
         fd_start_outgoing_migration(s, p, &local_err);
+    } else if (strstart(uri, "inline-fd:", &p)) {
+        if (!*p) {
+            inline_fd_start_outgoing_migration(s, &local_err);
+        } else {
+            error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "uri",
+                       "an empty path for 'inline-fd:' protocol");
+        }
     } else {
         error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "uri",
                    "a valid migration protocol");
diff --git a/migration/trace-events b/migration/trace-events
index de2e136e57..1afd11eab5 100644
--- a/migration/trace-events
+++ b/migration/trace-events
@@ -261,6 +261,10 @@ migration_exec_incoming(const char *cmd) "cmd=%s"
 migration_fd_outgoing(int fd) "fd=%d"
 migration_fd_incoming(int fd) "fd=%d"
 
+# inline-fd.c
+migration_inline_fd_outgoing(int fd) "fd=%d"
+migration_inline_fd_incoming(int fd) "fd=%d"
+
 # socket.c
 migration_socket_incoming_accepted(void) ""
 migration_socket_outgoing_connected(const char *hostname) "hostname=%s"
