// 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" /* Magic identifier for migration data */ #define MIGRATE_MAGIC 0xB1BB1D1B0BB1D1B0 /** * struct migrate_seen_addrs_v1 - Migratable guest addresses for v1 state stream * @addr6: Observed guest IPv6 address * @addr6_ll: Observed guest IPv6 link-local address * @addr4: Observed guest IPv4 address * @mac: Observed guest MAC address */ struct migrate_seen_addrs_v1 { struct in6_addr addr6; struct in6_addr addr6_ll; struct in_addr addr4; unsigned char mac[ETH_ALEN]; } __attribute__((packed)); static int seen_addrs_source_v1(struct ctx *c, const struct migrate_stage *stage, int fd) { struct migrate_seen_addrs_v1 addrs = { .addr6 = c->ip6.addr_seen, .addr6_ll = c->ip6.addr_ll_seen, .addr4 = c->ip4.addr_seen, }; (void)stage; memcpy(addrs.mac, c->guest_mac, sizeof(addrs.mac)); return write_all_buf(fd, &addrs, sizeof(addrs)); } static int seen_addrs_target_v1(struct ctx *c, const struct migrate_stage *stage, int fd) { struct migrate_seen_addrs_v1 addrs; (void)stage; if (read_all_buf(fd, &addrs, sizeof(addrs))) return errno; c->ip6.addr_seen = addrs.addr6; c->ip6.addr_ll_seen = addrs.addr6_ll; c->ip4.addr_seen = addrs.addr4; memcpy(c->guest_mac, addrs.mac, sizeof(c->guest_mac)); return 0; } /* Stages for version 1 */ static const struct migrate_stage stages_v1[] = { { .name = "seen addresses", .source = seen_addrs_source_v1, .target = seen_addrs_target_v1, }, { 0 }, }; /* Supported encoding versions, from latest (most preferred) to oldest */ static const struct migrate_version versions[] = { { 1, stages_v1, }, { 0 }, }; /* Current encoding version */ #define CURRENT_VERSION (&versions[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 = CURRENT_VERSION; const struct migrate_header header = { .magic = htonll_constant(MIGRATE_MAGIC), .version = htonl(v->id), .compat_version = htonl(v->id), }; 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 structure on success, NULL on failure with errno set */ static const struct migrate_version *migrate_target_read_header(int fd) { const struct migrate_version *v; struct migrate_header h; uint32_t id, compat_id; if (read_all_buf(fd, &h, sizeof(h))) return NULL; id = ntohl(h.version); compat_id = ntohl(h.compat_version); debug("Source magic: 0x%016" PRIx64 ", version: %u, compat: %u", ntohll(h.magic), id, compat_id); if (ntohll(h.magic) != MIGRATE_MAGIC || !id || !compat_id) { err("Invalid incoming device state"); errno = EINVAL; return NULL; } for (v = versions; v->id; v++) if (v->id <= id && v->id >= compat_id) return v; errno = ENOTSUP; err("Unsupported device state version: %u", id); return NULL; } /** * 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; int ret; v = migrate_target_read_header(fd); if (!v) return errno; 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; } /** * migrate_init() - Set up things necessary for migration * @c: Execution context */ void migrate_init(struct ctx *c) { c->device_state_result = -1; } /** * migrate_close() - Close migration channel and connection to passt-repair * @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); close(c->device_state_fd); c->device_state_fd = -1; c->device_state_result = -1; } repair_close(c); } /** * 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; c->migrate_target = target; } /** * 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; } /** * migrate_finish() - Hack to connect() migrated sockets from "RARP" trigger * @c: Execution context */ void migrate_finish(struct ctx *c) { (void)c; /* HACK RARP: flow_migrate_target_post(c); */ }