// SPDX-License-Identifier: GPL-2.0-or-later /* PASST - Plug A Simple Socket Transport * for qemu/UNIX domain socket mode * * PASTA - Pack A Subtle Tap Abstraction * for network namespace/tap device mode * * migrate.c - Migration sections, layout, and routines * * Copyright (c) 2025 Red Hat GmbH * Author: Stefano Brivio */ #include #include #include "util.h" #include "ip.h" #include "passt.h" #include "inany.h" #include "flow.h" #include "flow_table.h" #include "repair.h" #include "migrate.h" /* Current version of migration data */ #define MIGRATE_VERSION 1 /* Magic identifier for migration data */ #define MIGRATE_MAGIC 0xB1BB1D1B0BB1D1B0 /* Migration header to send from source */ static struct migrate_header header = { .magic = htonll_constant(MIGRATE_MAGIC), .version = htonl_constant(MIGRATE_VERSION), }; /** * migrate_send_block() - Migration stage handler to send verbatim data * @c: Execution context * @stage: Migration stage * @fd: Migration fd * * Sends the buffer in @stage->iov over the migration channel. */ __attribute__((__unused__)) static int migrate_send_block(struct ctx *c, const struct migrate_stage *stage, int fd) { (void)c; if (write_remainder(fd, &stage->iov, 1, 0) < 0) return errno; return 0; } /** * migrate_recv_block() - Migration stage handler to receive verbatim data * @c: Execution context * @stage: Migration stage * @fd: Migration fd * * Reads the buffer in @stage->iov from the migration channel. * * #syscalls:vu readv */ __attribute__((__unused__)) static int migrate_recv_block(struct ctx *c, const struct migrate_stage *stage, int fd) { (void)c; if (read_remainder(fd, &stage->iov, 1, 0) < 0) return errno; return 0; } #define DATA_STAGE(v) \ { \ .name = #v, \ .source = migrate_send_block, \ .target = migrate_recv_block, \ .iov = { &(v), sizeof(v) }, \ } /* Stages for version 1 */ static const struct migrate_stage stages_v1[] = { { .name = "flow pre", .target = NULL, }, { .name = "flow post", .source = NULL, }, { 0 }, }; /* Set of data versions */ static const struct migrate_version versions[] = { { 1, stages_v1, }, { 0 }, }; /** * migrate_source() - Migration as source, send state to hypervisor * @c: Execution context * @fd: File descriptor for state transfer * * Return: 0 on success, positive error code on failure */ static int migrate_source(struct ctx *c, int fd) { const struct migrate_version *v = versions + ARRAY_SIZE(versions) - 1; const struct migrate_stage *s; int ret; ret = write_all_buf(fd, &header, sizeof(header)); if (ret) { err("Can't send migration header: %s, abort", strerror_(ret)); return ret; } for (s = v->s; *s->name; s++) { if (!s->source) continue; debug("Source side migration: %s", s->name); if ((ret = s->source(c, s, fd))) { err("Source migration stage %s: %s, abort", s->name, strerror_(ret)); return ret; } } return 0; } /** * migrate_target_read_header() - Read header in target * @fd: Descriptor for state transfer * * Return: version number on success, 0 on failure with errno set */ static uint32_t migrate_target_read_header(int fd) { struct migrate_header h; if (read_all_buf(fd, &h, sizeof(h))) return 0; debug("Source magic: 0x%016" PRIx64 ", version: %u", be64toh(h.magic), ntohl_constant(h.version)); if (ntohll_constant(h.magic) != MIGRATE_MAGIC || !ntohl(h.version)) { errno = EINVAL; return 0; } return ntohl(h.version); } /** * migrate_target() - Migration as target, receive state from hypervisor * @c: Execution context * @fd: File descriptor for state transfer * * Return: 0 on success, positive error code on failure */ static int migrate_target(struct ctx *c, int fd) { const struct migrate_version *v; const struct migrate_stage *s; uint32_t id; int ret; id = migrate_target_read_header(fd); if (!id) { ret = errno; err("Migration header check failed: %s, abort", strerror_(ret)); return ret; } for (v = versions; v->id && v->id == id; v++); if (!v->id) { err("Unsupported version: %u", id); return -ENOTSUP; } for (s = v->s; *s->name; s++) { if (!s->target) continue; debug("Target side migration: %s", s->name); if ((ret = s->target(c, s, fd))) { err("Target migration stage %s: %s, abort", s->name, strerror_(ret)); return ret; } } return 0; } /** * set_migration_watch() - Add the migration file descriptor to epoll * @c: Execution context * @fd: File descriptor to add * @target: Are we the target of the migration? */ static void set_migration_watch(const struct ctx *c, int fd, bool target) { union epoll_ref ref = { .type = EPOLL_TYPE_DEVICE_STATE, .fd = fd, }; struct epoll_event ev = { 0 }; ev.data.u64 = ref.u64; ev.events = target ? EPOLLIN : EPOLLOUT; epoll_ctl(c->epollfd, EPOLL_CTL_ADD, ref.fd, &ev); } /** * migrate_init() - Set up things necessary for migration * @c: Execution context */ void migrate_init(struct ctx *c) { c->device_state_fd = -1; c->device_state_result = -1; } /** * migrate_close() - Close migration channel * @c: Execution context */ void migrate_close(struct ctx *c) { if (c->device_state_fd != -1) { debug("Closing migration channel, fd: %d", c->device_state_fd); epoll_del(c, c->device_state_fd); close(c->device_state_fd); c->device_state_fd = -1; c->device_state_result = -1; } } /** * migrate_request() - Request a migration of device state * @c: Execution context * @fd: fd to transfer state * @target: Are we the target of the migration? */ void migrate_request(struct ctx *c, int fd, bool target) { debug("Migration requested, fd: %d (was %d)", fd, c->device_state_fd); if (c->device_state_fd != -1) migrate_close(c); c->device_state_fd = fd; set_migration_watch(c, c->device_state_fd, target); } /** * migrate_handler() - Send/receive passt internal state to/from QEMU * @c: Execution context * @events: epoll events */ void migrate_handler(struct ctx *c, uint32_t events) { int rc = EIO; debug("migrate_handler fd %d events %x", c->device_state_fd, events); if (events & EPOLLOUT) rc = migrate_source(c, c->device_state_fd); else if (events & EPOLLIN) rc = migrate_target(c, c->device_state_fd); /* EPOLLHUP without EPOLLIN/EPOLLOUT, or EPOLLERR? Migration failed */ migrate_close(c); c->device_state_result = rc; }