// 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 "migrate.h" #include "repair.h" /* Current version of migration data */ #define MIGRATE_VERSION 1 /* Magic as we see it and as seen with reverse endianness */ #define MIGRATE_MAGIC 0xB1BB1D1B0BB1D1B0 #define MIGRATE_MAGIC_SWAPPED 0xB0D1B1B01B1DBBB1 /* Migration header to send from source */ static union migrate_header header = { /* Immutable part of header structure: keep these two sections at the * beginning, because they are enough to identify a version regardless * of metadata. */ .magic = MIGRATE_MAGIC, .version = htonl_constant(MIGRATE_VERSION), /* End of immutable part of header structure */ .time_t_size = htonl_constant(sizeof(time_t)), .flow_size = htonl_constant(sizeof(union flow)), .flow_sidx_size = htonl_constant(sizeof(struct flow_sidx)), .voidp_size = htonl_constant(sizeof(void *)), }; /** * migrate_send_block() - Migration stage handler to send verbatim data * @c: Execution context * @m: Migration metadata * @stage: Migration stage * @fd: Migration fd * * Sends the buffer in @stage->iov over the migration channel. */ static int migrate_send_block(struct ctx *c, struct migrate_meta *m, const struct migrate_stage *stage, int fd) { (void)c; (void)m; 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 * @m: Migration metadata * @stage: Migration stage * @fd: Migration fd * * Reads the buffer in @stage->iov from the migration channel. * * #syscalls:vu readv */ static int migrate_recv_block(struct ctx *c, struct migrate_meta *m, const struct migrate_stage *stage, int fd) { (void)c; (void)m; 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", .source = flow_migrate_source_pre, .target = NULL, }, DATA_STAGE(flow_first_free), DATA_STAGE(flowtab), DATA_STAGE(flow_hashtab), { .name = "flow post", .source = NULL, }, }; /* Set of data versions */ static const struct migrate_version versions[] = { { 1, stages_v1, ARRAY_SIZE(stages_v1), }, }; /** * 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 */ int migrate_source(struct ctx *c, int fd) { struct migrate_meta m; unsigned i; int rc; rc = write_all_buf(fd, &header, sizeof(header)); if (rc) { err("Failed to send migration header: %s, abort", strerror_(rc)); return rc; } for (i = 0; i < ARRAY_SIZE(stages_v1); i++) { const struct migrate_stage *stage = &stages_v1[i]; if (!stage->source) continue; debug("Source side migration: %s", stage->name); if ((rc = stage->source(c, &m, stage, fd))) { err("Source migration failed stage %s: %s, abort", stage->name, strerror_(rc)); return rc; } } return 0; } /** * migrate_target_read_header() - Set metadata in target from source header * @fd: Descriptor for state transfer * @m: Migration metadata, filled on return * * Return: 0 on success, error code on failure */ static int migrate_target_read_header(int fd, struct migrate_meta *m) { union migrate_header h; unsigned i; if (read_all_buf(fd, &h, sizeof(h))) return errno; debug("Source magic: 0x%016" PRIx64 ", sizeof(void *): %u, version: %u", h.magic, ntohl(h.voidp_size), ntohl(h.version)); m->version = ntohl(h.version); m->v = NULL; for (i = 0; i < ARRAY_SIZE(versions); i++) { if (versions[i].version == m->version) m->v = &versions[i]; } if (!m->v) return ENOTSUP; if (h.magic == MIGRATE_MAGIC) m->bswap = false; else if (h.magic == MIGRATE_MAGIC_SWAPPED) m->bswap = true; else return ENOTSUP; if (ntohl(h.voidp_size) == 4) m->source_64b = false; else if (ntohl(h.voidp_size) == 8) m->source_64b = true; else return ENOTSUP; if (ntohl(h.time_t_size) == 4) m->time_64b = false; else if (ntohl(h.time_t_size) == 8) m->time_64b = true; else return ENOTSUP; m->flow_size = ntohl(h.flow_size); m->flow_sidx_size = ntohl(h.flow_sidx_size); return 0; } /** * 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 */ int migrate_target(struct ctx *c, int fd) { struct migrate_meta m; unsigned i; int rc; rc = migrate_target_read_header(fd, &m); if (rc) { err("Migration header check failed: %s, abort", strerror_(rc)); return rc; } for (i = 0; i < m.v->nstages; i++) { const struct migrate_stage *stage = &m.v->stages[i]; if (!stage->target) continue; debug("Target side migration: %s", stage->name); if ((rc = stage->target(c, &m, stage, fd))) { err("Target migration failed stage %s: %s, abort", stage->name, strerror_(rc)); return rc; } } return 0; } /** * 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; repair_sock_init(c); } /** * 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", c->device_state_fd); if (c->device_state_fd != -1) migrate_close(c); c->device_state_fd = fd; } /** * migrate_handler() - Send/receive passt internal state to/from QEMU * @c: Execution context */ void migrate_handler(struct ctx *c) { int rc; if (c->device_state_fd < 0) return; debug("migrate_handler fd %d target %d", c->device_state_fd, c->migrate_target); if (c->migrate_target) rc = migrate_target(c, c->device_state_fd); else rc = migrate_source(c, c->device_state_fd); migrate_close(c); c->device_state_result = rc; }